Linux启动流程之arm (一)
作者:互联网
老的内核版本,不带dts内核:
1. 机器 ID,启动参数
启动文件head.S,主要完成如下几件事:
- (0)判断是否支持此CPU
- (1)如何比较机器ID是:(判断是否支持单板)
- (3)创建页表。
- (4)使能MMU。
- (5)跳转到
start_kernel
(它就是内核的第一个 C 函数)
2.分析内核源代码
1.通过make uImage V=1
详细查看内核编译时的最后一条命令可知。
- 内核中排布的第一个文件是:
arch/arm/kernel/head.S
- 链接脚本:
arch/arm/kernel/vmlinux.lds
UBOOT启动时首先在内存里设置了一大堆参数。
接着启动内核:
theKernel(0, bd->bi_arch_number, bd->bi_boot_params);
theKernel 就是内核的入口地址。有3 个参数。
- 参1为0
r0
- 参2为机器ID
r1
machine numbers
:linux/arch/arm/tools/mach-types
- 参3是上面那些参数所存放的地址。
r2
所以,内核一上来肯定要处理这些参数。
3.内核启动
内核启动:最终目标是就运行应用程序。对于Linux 来说应用程序在根文件系统中,要挂接。
3.1.老内核,处理UBOOT 传入的参数
内核中排布的第一个文件是:arch/arm/kernel/head.S
arch/arm/kernel/head.S
arch/arm/boot/compressed/head.S
内核编译出来后比较大,可以压缩很小。在压缩过的内核前部加一段代码“自解压代码”。
这样内核运行时,先运行“自解压代码”。然后再执行解压缩后的内核。
我们看不用解压的head.S文件
mrc p15, 0, r9, c0, c0 @get processor id
bl __lookup_processor_type @r5=procinfo r9=cpuid
__lookup_processor_type
查找处理器类型内核能够支持哪些处理器,是在编译内核时定义下来的。内核启动时去读寄存器:获取ID。
__lookup_processor_type:
adr r3, __lookup_processor_type_data
ldmia r3, {r4 - r6}
sub r3, r3, r4 @ get offset between virt & phys
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
1: ldmia r5, {r3, r4} @ value, mask
and r4, r4, r9 @ mask wanted bits
teq r3, r4
beq 2f
add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)
cmp r5, r6
blo 1b
mov r5, #0 @ unknown processor
2: mov pc, lr
ENDPROC(__lookup_processor_type)
注意:前面的内容是连续的。能够对虚拟地址到物理地址进行线性映射。虚拟地址+虚拟到物理地址的偏移=物理地址
首先 r3 等于__lookup_processor_type_data
的地址如下。这是实际存在的地址。UBOOT 启动内核时,MMU 还没启动。所以这是物理地址。
ldmia r3, {r4 - r6}
,将r3依次赋值给r4-r6,这是相当于long类型数组r4=. ,r5=__proc_info_begin,r6=__proc_info_end,size = . - __lookup_processor_type_data
是虚拟地址sub r3, r3, r4
: r4 等于“.”虚拟地址,r3是物理地址,得到virt和phys之间的偏移. r3最后等于offsetadd r5, r5, r3
和add r6, r6, r3
:它们加上这个偏差值后,就变成了__proc_info_begin
和__proc_info_end
的真正物理地址了。
__lookup_processor_type_data
内容如下:
/*
* Look in <asm/procinfo.h> for information about the __proc_info structure.
*/
.align 2
.type __lookup_processor_type_data, %object
__lookup_processor_type_data:
.long .
.long __proc_info_begin
.long __proc_info_end
.size __lookup_processor_type_data, . - __lookup_processor_type_data
其中__proc_info_begin
,__proc_info_end
没有在内核源码中定义,是在 链接脚本 中定义的(vmlinux.lds)。
__proc_info_begin = .;
__arch_info_begin = .;
__proc_info_end = .;
中间夹着 *(.arch .info .init)
,*
表示所有文件。这里是指所有文件的 .arch .info .init
段。(架构、信息、初始化)即架构相关的初始化信息全放在这里。它开始地址
是__arch_info_begin,结束地址是 __arch_info_end。这两个地址上从. = (0xc0000000) + 0x00008000
(虚拟地址)一路的增涨下来的。
. = (0xc0000000) + 0x00008000;
.text.haed: {
_stext = .;
_sinittext = .;
*(.text.head)
}
.init : { /*Init code and data*/
*(.init.text)
_einittext = .;
__proc_info_begin = .;
__arch_info_begin = .;
__proc_info_end = .;
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
_tagtable_begin = .;
*(.taglist.init)
_tagtable_end = .;
}
详细看下节内核
看处理器ID 后,看内核是否可以支持这个处理器。若能支持则继续运行,不支持则跳到“_error_p”中去:
beq __error_p @yes , error 'p'
adr r0, str_p1
bl printascii
mov r0, r9
bl printhex8
adr r0, str_p2
bl printascii
b __error
str_p1: .asciz "\nError: unrecognized/unsupported processor variant (0x"
str_p2: .asciz ").\n"
.align
__error:
1: mov r0, r0
b 1b
__error这是个死循环。
一个编译好的内核能支持哪些单板,都是定下来的。(make CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm vexpress_defconfig
编译内核时候制定)内核上电后会检测下看是否支持当前的
单板。若可以支持则继续往下跑,不支持则 __error_a 跳到死循环。
内核支持多少单板,就有多少个这种以 “MACHINE_START”开头,以“MACHINE_END”
定义起来的代码。每种单板都有机器ID(结构体中的nr),机器ID 是整数。
UBOOT 传来这个参数:
theKernel(0, bd->bi_arch_numer, bd->bi_boot_params);
与内核的这部分刚好对应(如下部分),内核将这部分代码编译进去了,就支持这段代码定义的 单板。上面这段代码中的结构体有
一个属性,它的段被强制设置到了体被放在 vmlinux.lds 定义的:
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
2410,2440,qt2410 的单板代码都强制放在这个地方。内核启动时,会从 __arch_info_begin = . 开始读,读到 __arch_info_end = . 一个一个的将单板信息取出来。将里面的机器ID 和UBOOT 传进来的机器ID 比较。相同则表示内核支持这个
单板。
下面就是比较机器ID了。(内核中的和UBOOT 传进来的)看arch\arm\kernel\head-common.S
从“__lookup_machine_type:”中可知
r5=__arch_info_begin
1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type
- r5 是: __arch_info_begin
- r1 是UBOOT 传来的参数:bi_arch_number
teq r3, r1 @ matches loader number?
beq 2f @ found
add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
cmp r5, r6
blo 1b
mov r5, #0 @ unknown machine
2: mov pc, lr
最后比较成功后,会回到:head.S
bl __lookup_machine_type @r5 = machinfo
movs r8, r5 @invliad machine (r5=0)?
以上 单板机器ID 比较完成。
3.2 新内核,处理UBOOT 传入的参数
r2
决定使用atags还是使用dtb(CONFIG_OF_FLATTREE)
- ATAG_CORE:探测RAM前16k内存,要求指针对齐.
- CONFIG_OF_FLATTR:如果使能,则接收dtb地址
head.S->head_common.S __vet_atags:
# r2 either valid atags pointer, valid dtb pointer, or zero
__vet_atags:
tst r2, #0x3 @ aligned?
bne 1f
ldr r5, [r2, #0]
#ifdef CONFIG_OF_FLATTREE
ldr r6, =OF_DT_MAGIC @ is it a DTB?
cmp r5, r6
beq 2f
#endif
cmp r5, #ATAG_CORE_SIZE @ is first tag ATAG_CORE?
cmpne r5, #ATAG_CORE_SIZE_EMPTY
bne 1f
ldr r5, [r2, #4]
ldr r6, =ATAG_CORE
cmp r5, r6
bne 1f
2: mov pc, lr @ atag/dtb pointer is ok
1: mov r2, #0
mov pc, lr
ENDPROC(__vet_atags)
4.内核
4.1 老内核定义
知道上面这个关系后,我们则一定要知道在代码里面谁定义了.arch.info.init 这些东西。在内核中搜索它们。grep *.arch.info.init * -rR
查它们的定义:include/asm-arm/mach/arch.h +53
/*
* Set of macros to define architecture features. This is built into
* a table by the linker.
*/
#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,
#define MACHINE_END \
};
例如:sm2440
MACHINE_START(S3C2440, "SMDK2440")
/* Maintainer: Ben Dooks <ben-linux@fluff.org> */
.atag_offset = 0x100,
.init_irq = s3c2440_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.init_time = smdk2440_init_time,
MACHINE_END
上段代码展开:
static const struct machine_desc __mach_desc_S3C2440
__used
__attributed__((__section__(".arch.info.init"))) = {
.nr = MACH_TYPE_S3C2440,
.name = "SMDK2440",
.phys_io = S3C2410_PA_UART,
.io_pg_offset = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAW_PA + 0x100,
.init_irq = s3c24xx_init_irq,
.map_io = _smkd2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
};
4.2 新内核定义
// init/main.c
setup_arch(&cmd_line);
// arch/arm/kernel/setup.c
void __init setup_arch(char **cmd_line_p)
{
mdesc = setup_machine_fdt(__atags_pointer);
}
//arch/arm/kernel/devtree.c
struct machine_desc *__init setup_machine_fdt(unsigned int dt_phys)
{
struct boot_param_header *devtree = phys_to_virt(dt_phys);
/* check device tree validity */
if (be32_to_cpu(devtree->magic) != OF_DT_HEADER)
return NULL;
/* Search the mdescs for the 'best' compatible value match */
initial_boot_params = devtree;
dt_root = of_get_flat_dt_root();
for_each_machine_desc(mdesc) {
score = of_flat_dt_match(dt_root, mdesc->dt_compat);
if (score > 0 && score < mdesc_score) {
mdesc_best = mdesc;
mdesc_score = score;
}
}
}
- 其中:
__atags_pointer
r2 dtb地址 dt_root
和initial_boot_params
由__atags_pointer计算得到,即相当于从r2传入
for_each_machine_desc:对应宏
/*
* Machine type table - also only accessible during boot
*/
extern struct machine_desc __arch_info_begin[], __arch_info_end[];
#define for_each_machine_desc(p) \
for (p = __arch_info_begin; p < __arch_info_end; p++)
这里mdesc对应lds中变量,即DT_MACHINE_START
中的信息
//linux\arch\arm\mach-at91\at91rm9200.c
static void __init at91rm9200_dt_device_init(void)
{
//注意这里解析 dts
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
arm_pm_idle = at91rm9200_idle;
arm_pm_restart = at91rm9200_restart;
at91rm9200_pm_init();
}
static const char *at91rm9200_dt_board_compat[] __initconst = {
"atmel,at91rm9200",
NULL
};
DT_MACHINE_START(at91rm9200_dt, "Atmel AT91RM9200")
.init_time = at91rm9200_dt_timer_init,
.map_io = at91_map_io,
.init_machine = at91rm9200_dt_device_init,
.dt_compat = at91rm9200_dt_board_compat,
MACHINE_END
看看结构体:machine_desc
struct machine_desc {
unsigned int nr; /* architecture number 机器ID*/
const char *name; /* architecture name */
unsigned long atag_offset; /* tagged list (relative) */
const char *const *dt_compat; /* array of device tree
* 'compatible' strings */
unsigned int nr_irqs; /* number of IRQs */
#ifdef CONFIG_ZONE_DMA
phys_addr_t dma_zone_size; /* size of DMA-able area */
#endif
unsigned int video_start; /* start of video RAM */
unsigned int video_end; /* end of video RAM */
unsigned char reserve_lp0 :1; /* never has lp0 */
unsigned char reserve_lp1 :1; /* never has lp1 */
unsigned char reserve_lp2 :1; /* never has lp2 */
enum reboot_mode reboot_mode; /* default restart mode */
unsigned l2c_aux_val; /* L2 cache aux value */
unsigned l2c_aux_mask; /* L2 cache aux mask */
void (*l2c_write_sec)(unsigned long, unsigned);
struct smp_operations *smp; /* SMP operations */
bool (*smp_init)(void);
void (*fixup)(struct tag *, char **);
void (*dt_fixup)(void);
void (*init_meminfo)(void);
void (*reserve)(void);/* reserve mem blocks */
void (*map_io)(void);/* IO mapping function */
void (*init_early)(void);
void (*init_irq)(void);
void (*init_time)(void);
void (*init_machine)(void);
void (*init_late)(void);
void (*restart)(enum reboot_mode, const char *);
};
3.3 创建页表
bl __create_page_tables
内核的链接地址从虚拟地址
. = (0xc00000000) + 0x00008000;
开始。这个地址并不代表真实存在的内存。我们的内存是从 0x3000 0000 开始的。故这里面要建立一个页表,启动 MMU 。
3.4 使能MMU
ldr r13, __switch_data @ address to jump to after mmu has been enabled
adr lr, __enbable_mmu @ return (PIC) address
add pc, r10, #PROCINFO_INITFUNC
mmu has been enabled 当MMU 使能后,会跳到 __switch_data 中去。
如何跳到 __switch_data ,则看:__enable_mmu
.type __switch_data, %object
__switch_data:
.long __mmap_switched
.long __data_loc @ r4
.long __data_start @ r5
.long __bss_start @ r6
.long __end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long cr_alignment @ r6
.long init_thread_union + THREAD_START_SP @ sp
在head_common.S 文件中,__switch_data 后是:__mmap_switched
__mmap_switched:
adr r3, __switched_data + 4
ldmia r3!, {r4, r5, r6, r7}
cmd r4, r5 @ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4] , #4
strne fp, [r5] , #4
bne 1b
mov fp, #0 @ clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6], #4
bcc 1b
ldmia r3, {r4, r4, r6, sp}
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
bic r4, f0, #CR_A
stmia r6, {r0, r4}
b start_kernel @跳转到第一个C函数中来处理UBOOT传来的参数
4.start_kernel(它就是内核的第一个C函数)
UBOOT 传进来的启动参数,参2:机器ID 在head.Skh 中会比较。
参3:传进来的参数,就是在这个第一个C函数start_kernel 中处理
分析第一个C 函数 start_kernel :在 main.c 文件中
asmlinkage __visible void __init start_kernel(void)
标签:__,info,r5,流程,init,内核,Linux,arch,arm 来源: https://www.cnblogs.com/ellabrain/p/15449949.html