Linux启动流程之arm (一)
2021/10/24 7:09:38
本文主要是介绍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)
这篇关于Linux启动流程之arm (一)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-12如何创建可引导的 ESXi USB 安装介质 (macOS, Linux, Windows)
- 2024-11-08linux的 vi编辑器中搜索关键字有哪些常用的命令和技巧?-icode9专业技术文章分享
- 2024-11-08在 Linux 的 vi 或 vim 编辑器中什么命令可以直接跳到文件的结尾?-icode9专业技术文章分享
- 2024-10-22原生鸿蒙操作系统HarmonyOS NEXT(HarmonyOS 5)正式发布
- 2024-10-18操作系统入门教程:新手必看的基本操作指南
- 2024-10-18初学者必看:操作系统入门全攻略
- 2024-10-17操作系统入门教程:轻松掌握操作系统基础知识
- 2024-09-11Linux部署Scrapy学习:入门级指南
- 2024-09-11Linux部署Scrapy:入门级指南
- 2024-08-21【Linux】分区向左扩容的方法