内核启动流程分析(四)源码浅析
目錄
kernel(四)源碼淺析
- 建立工程
- 啟動簡析
- head.s
- 入口點
- 查詢處理器
- 查詢機器ID
- 啟動MMU
- 其他操作
- start_kernel
- 處理命令行
- 分區
kernel(四)源碼淺析
建立工程
移除所有Arch,添加Arch/arm 下除了 Mach_xxx 開頭的,Mach_xxx 表示機器型號,添加2410,2440,剔除 Plat_xxx,加入plat-s3c24xx
Arch/arm/boot common configs kernel libmach-s3c2410 mach-s3c2440 plat-s3c24xxmm nwfpe oprofile tools vfp移除include目錄,先排除所有Asm相關,只加入asm-arm頂層文件以及2440相關的如下
include/ 排除所有 asm-xxxinclude/asm-arm/下添加 所有頂層以及以下目錄 arch-s3c2410 hardware mach plat-s3c24xx啟動簡析
uboot通過theKernel (0, bd->bi_arch_number, bd->bi_boot_params)啟動內核;中的第二個參數是機器ID,內核通過比對機器ID判斷是否支持啟動.gd->bd->bi_arch_number = MACH_TYPE_S3C2440;linux會這么做:
內核跳轉之前,Uboot設置內核的啟動參數.內核的參數是按照tag組織的.也就是在某個地址(0x30000100,在100ask24x0.c中定義),按照某種格式存儲,這種格式具體為【size....tagid....tag值】
head.s
我們發現在arch\arm\boot\compressed也存在一個head.S的文件,有些內核編譯出來比較大,他會以壓縮的形式存在也就是包含了自解壓的代碼,這個文件就是講壓縮的文件解壓,在這里不做分析。我們的入口為arch\arm\kernel\head.S
入口點
鏈接腳本有寫,也就是說_stext段為最早的入口點,搜索下head.S中的入口點.text.head
.text.head : { #先放所有文件的 .text.head 段_stext = .;_sinittext = .;*(.text.head)}查詢處理器
查看是否支持__lookup_processor_type
.section ".text.head", "ax".type stext, %function ENTRY(stext)msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode@ and irqs disabledmrc p15, 0, r9, c0, c0 @ get processor idbl __lookup_processor_type @ r5=procinfo r9=cpuidmovs r10, r5 @ invalid processor (r5=0)?beq __error_p @ yes, error 'p'bl __lookup_machine_type @ r5=machinfomovs r8, r5 @ invalid machine (r5=0)?beq __error_a @ yes, error 'a'bl __create_page_tables內核能夠支持哪些處理器,是在編譯內核時定義下來的。內核啟動時去讀寄存器:獲取 ID。看內核是否可以支持這個處理器。若能支持則繼續運行,不支持則跳到_error_p中去,這是個死循環
mrc p15, 0, r9, c0, c0 @ get processor id bl __lookup_processor_type @ r5=procinfo r9=cpuid movs r10, r5 @ invalid processor (r5=0)? beq __error_p @ yes, error 'p'查詢機器ID
如果不支持這個機器ID則跳轉到__error_a,這也是死循環
bl __lookup_machine_type @ r5=machinfo movs r8, r5 @ invalid machine (r5=0)? beq __error_a @ yes, error 'a'機器ID是存在R1的,因為theKernel (0, bd->bi_arch_number, bd->bi_boot_params)
3: .long ..long __arch_info_begin.long __arch_info_end@ __arch_info_begin 和 __arch_info_end 是在鏈接腳本中定義的 @ __arch_info_begin = .; @ *(.arch.info.init) @ __arch_info_end = .;/** Lookup machine architecture in the linker-build list of architectures.* Note that we can't use the absolute addresses for the __arch_info* lists since we aren't running with the MMU on (and therefore, we are* not in the correct address space). We have to calculate the offset.** r1 = machine architecture number* Returns:* r3, r4, r6 corrupted* r5 = mach_info pointer in physical address space*/.type __lookup_machine_type, %function __lookup_machine_type:adr r3, 3b @ r3= address of 3b,這個時候mmu還沒有啟動,是物理地址ldmia r3, {r4, r5, r6} @ r4=.,r5=__arch_info_begin,r6=__arch_info_end@ 這個.代表了3這個標號的虛擬地址sub r3, r3, r4 @ get offset between virt&phys 虛擬地址與物理地址的偏差 @將r5,r6轉換為實際的物理地址add r5, r5, r3 @ convert virt addresses to add r6, r6, r3 @ physical address space1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine typeteq r3, r1 @ matches loader number?beq 2f @ foundadd r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desccmp r5, r6blo 1bmov r5, #0 @ unknown machine 2: mov pc, lr首先是將虛擬地址轉換為物理地址,因為這個時候UBOOT 啟動內核時,MMU 還沒啟動,r3 這是實際存在的地址
3: .long ..long __arch_info_begin.long __arch_info_endadr r3, 3b接下來的r4, r5, r6都是虛擬地址了.?.代表虛擬地址。是標號為3的指令的虛擬地址.可以通過r3與.(虛擬地址)來計算偏差.
sub r3, r3, r4 @ r3=r3-r4,也就r3=物理地址-虛擬地址的偏差 實際物理地址=虛擬地址+r3即可add r5, r5, r3 @ convert virt addresses to add r6, r6, r3 @ physical address space查看下__arch_info_begin和__arch_info_end具體是什么,這個是在鏈接腳本定義如下的,也就是代表了一個段
__arch_info_begin = .;*(.arch.info.init) __arch_info_end = .;.arch.info.init在arch.h?中有定義 ,定義某個結構體(machine_desc)的段屬性
#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 \ };有如下應用
MACHINE_START(S3C2440, "SMDK2440")/* Maintainer: Ben Dooks <ben@fluff.org> */.phys_io = S3C2410_PA_UART,.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,.boot_params = S3C2410_SDRAM_PA + 0x100,.init_irq = s3c24xx_init_irq,.map_io = smdk2440_map_io,.init_machine = smdk2440_machine_init,.timer = &s3c24xx_timer, MACHINE_END展開看看
static const struct machine_desc __mach_desc_S3C2440 __used __attribute__((__section__(".arch.info.init"))) = { .nr = MACH_TYPE_S3C2440, .name = SMDK2440,.phys_io = S3C2410_PA_UART,.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,.boot_params = S3C2410_SDRAM_PA + 0x100,.init_irq = s3c24xx_init_irq,.map_io = smdk2440_map_io,.init_machine = smdk2440_machine_init,.timer = &s3c24xx_timer,};查看下machine_desc 這個結構體內容,可以發現支持多少單板,就有多少這個宏的使用
struct machine_desc {/** Note! The first four elements are used* by assembler code in head-armv.S*/unsigned int nr; /* architecture number */unsigned int phys_io; /* start of physical io */unsigned int io_pg_offst; /* byte offset for io * page tabe entry */const char *name; /* architecture name */unsigned long boot_params; /* tagged list */unsigned int video_start; /* start of video RAM */unsigned int video_end; /* end of video RAM */unsigned int reserve_lp0 :1; /* never has lp0 */unsigned int reserve_lp1 :1; /* never has lp1 */unsigned int reserve_lp2 :1; /* never has lp2 */unsigned int soft_reboot :1; /* soft reboot */void (*fixup)(struct machine_desc *,struct tag *, char **,struct meminfo *);void (*map_io)(void);/* IO mapping function */void (*init_irq)(void);struct sys_timer *timer; /* system tick timer */void (*init_machine)(void); };接下去就是從這個結構體讀取第一個參數nr也就是ID來逐個比較了.這個在內核中定義與uboot定義是一致的
#define MACH_TYPE_S3C2440 362啟動MMU
bl __create_page_tables @創建頁表 ldr r13, __switch_data @ address to jump to after,這是使能mmu后的跳轉地址@ mmu has been enabled adr lr, __enable_mmu @ return (PIC) address 使能mmu add pc, r10, #PROCINFO_INITFUNC@__enable_mmu 中會調用 __turn_mmu_on,最后 mov pc, r13 b __turn_mmu_on.align 5.type __turn_mmu_on, %function __turn_mmu_on:mov r0, r0mcr p15, 0, r0, c1, c0, 0 @ write control regmrc p15, 0, r3, c0, c0, 0 @ read id regmov r3, r3mov r3, r3mov pc, r13 @這個是關鍵,pc最后=r13=__switch_data其他操作
復制數據段,清bss段等操作
start_kernel
啟動mmu后會跳轉到__switch_data,如何跳到?__switch_data,在__enable_mmu?中會調用?__turn_mmu_on這個函數最后?mov pc, r13,在調用__enable_mmu?前是先賦值的ldr r13, __switch_data
ldr r13, __switch_data @ address to jump to after ....b start_kernel注意?這是內核的第一個 C 函數,接下來要處理UBOOT 傳輸的第三個啟動參數bd->bi_boot_params.這個文件在init/main.c,在以下函數處理參數
setup_arch(&command_line); setup_command_line(command_line);一覽流程如下
start_kernelsetup_arch //解析uboot傳入的參數,只是先存起來字符串setup_command_line //解析uboot傳入的參數,只是先存起來字符串parse_early_paramdo_early_param從__setup_start到__setup_end,調用early函數unknown_bootoptionobsolete_checksetup從__setup_start到__setup_end,調用非early函數rest_initkernel_initprepare_namespacemount_root //掛接根文件系統,init_post //執行應用程序注意: __setup_start和__setup_end是在鏈接腳本定義,它們中間包含的是所有文件的(.init.setup)段,這種段是通過一個宏來設置的,有以下宏定義:#define __setup(str, fn) \__setup_param(str, fn, fn, 0)#define __setup_param(str, unique_id, fn, early) \static char __setup_str_##unique_id[] __initdata = str; \static struct obs_kernel_param __setup_##unique_id \__attribute_used__ \__attribute__((__section__(".init.setup"))) \__attribute__((aligned((sizeof(long))))) \= { __setup_str_##unique_id, fn, early }將宏函數__setup("root=", root_dev_setup)進行宏展開得到:static char __setup_str_root_dev_setup[] __initdata = "root="; \static struct obs_kernel_param __setup_root_dev_setup \__attribute_used__ \__attribute__((__section__(".init.setup"))) \__attribute__((aligned((sizeof(long))))) \= { __setup_str_root_dev_setup, root_dev_setup, 0} 從上面可以看出宏函數__setup("root=", root_dev_setup);定義一個結構體變量,這個結構體的段屬性被強制設置為(".init.setup")段。setup_arch(解析tag)
這里是先查找mdesc這個結構體,這個結構體在上面分析機器ID的時候已經發現他保存了一系列參數.boot_params就是uboot存放參數的地址.然后在parse_tags(tags)處理具體的tag
if (mdesc->boot_params)tags = phys_to_virt(mdesc->boot_params);//在上面定義機器id結構體的時候,.boot_params = S3C2410_SDRAM_PA + 0x100,=0x30000100 #define S3C2410_CS6 (0x30000000) #define S3C2410_SDRAM_PA (S3C2410_CS6) //我們在uboot的時候存儲參數的地址也是這個 board_init -----gd->bd->bi_boot_params = 0x30000100; 然后開始處理tagsstatic const struct machine_desc __mach_desc_S3C2440 __used __attribute__((__section__(".arch.info.init"))) = { .nr = MACH_TYPE_S3C2440, .name = SMDK2440,.phys_io = S3C2410_PA_UART,.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,.boot_params = S3C2410_SDRAM_PA + 0x100,.init_irq = s3c24xx_init_irq,.map_io = smdk2440_map_io,.init_machine = smdk2440_machine_init,.timer = &s3c24xx_timer,};setup_command_line
所謂命令行,就是uboot設置的bootargs,linux通過getenv("bootargx")獲取參數,如果沒有設置這個參數,內部有一個默認參數. 這里只是將命令行復制到指定的數組,并沒有處理
char *from = default_command_line; //這是默認的命令行參數static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE; #define CONFIG_CMDLINE "root=/dev/hda1 ro init=/bin/bash console=ttySAC0"memcpy(boot_command_line, from, COMMAND_LINE_SIZE); boot_command_line[COMMAND_LINE_SIZE-1] = '\0'; parse_cmdline(cmdline_p, from); // // cmdline_p 是 傳遞的參數,用作拷貝 // from 是默認參數掛載根文件系統
創建一個線程,可以理解為運行程序kernel_init
rest_initkernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);kernel_init>prepare_namespace>mount_root 掛在根文件系統>init_post(); 執行應用程序處理命令行
uboot設置命令tag,多了參數commandline,源自環境變量bootargs查看下環境變量bootargs,使用print查看,也可搜索下代碼
"bootargs=" CONFIG_BOOTARGS "\0" //include/configs/100ask24x0.h #define CONFIG_BOOTARGS "noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0"- root=/dev/mtdblock3表示根文件系統從第四個FLASH分區開始(從0開始計數)可以往上看分區空間
- init=/linuxrc指示第一個應用程序
- console=ttySAC0,內核打印信息從串口0 打印
我們需要知道ROOT_DEV是什么,可以看到在函數prepare_namespace中有saved_root_name存儲這個這個數組.
if (saved_root_name[0]) {root_device_name = saved_root_name;if (!strncmp(root_device_name, "mtd", 3)) {mount_block_root(root_device_name, root_mountflags);goto out;}ROOT_DEV = name_to_dev_t(root_device_name);if (strncmp(root_device_name, "/dev/", 5) == 0)root_device_name += 5; }搜索下saved_root_name,root_dev_setup對齊賦值,再繼續查找函數的引用,只有一個宏使用了它
static int __init root_dev_setup(char *line) {strlcpy(saved_root_name, line, sizeof(saved_root_name));return 1; }解析下這個宏__setup("root=", root_dev_setup);
#define __setup(str, fn) \__setup_param(str, fn, fn, 0)#define __setup_param(str, unique_id, fn, early) \static char __setup_str_##unique_id[] __initdata = str; \static struct obs_kernel_param __setup_##unique_id \__attribute_used__ \__attribute__((__section__(".init.setup"))) \__attribute__((aligned((sizeof(long))))) \= { __setup_str_##unique_id, fn, early }static char __setup_str_root_dev_setup[] __initdata = "root="; static struct obs_kernel_param __setup_root_dev_setup __attribute_used____attribute__((__section__(".init.setup"))) __attribute__((aligned((sizeof(long))))) ={__setup_str_root_dev_setup,root_dev_setup,root_dev_setup,0}這個結構體的原型如下 struct obs_kernel_param {const char *str;int (*setup_func)(char *);int early; };最終大概分析一下也就是定義了一個char數組和一個有特殊段屬性.init.setup的結構體.注意這里的early是0.這個段屬性肯定是在lds中定義,搜索下這個段的起始和結束地址的調用情況.
__setup_start = .;*(.init.setup)__setup_end = .;obsolete_checksetupdo_early_paramstatic int __init do_early_param(char *param, char *val) {struct obs_kernel_param *p;for (p = __setup_start; p < __setup_end; p++) {if (p->early && strcmp(param, p->str) == 0) {if (p->setup_func(val) != 0)printk(KERN_WARNING"Malformed early option '%s'\n", param);}}/* We accept everything at this stage. */return 0; }static int __init obsolete_checksetup(char *line) {struct obs_kernel_param *p;int had_early_param = 0;p = __setup_start;do {int n = strlen(p->str);if (!strncmp(line, p->str, n)) {if (p->early) {/* Already done in parse_early_param?* (Needs exact match on param part).* Keep iterating, as we can have early* params and __setups of same names 8( */if (line[n] == '\0' || line[n] == '=')had_early_param = 1;} else if (!p->setup_func) {printk(KERN_WARNING "Parameter %s is obsolete,"" ignored\n", p->str);return 1;} else if (p->setup_func(line + n))return 1;}p++;} while (p < __setup_end);return had_early_param; }可以看出obsolete_checksetup先判斷這個結構的early屬性,為0則執行setup_func方法,這符合我們的這個宏__setup("root=", root_dev_setup);
結論:掛接根文件系統的參數是由命令行給出的,內核函數去分析這個命令行,去賦值ROOT_DEV
分區
分區表是沒有的,是代碼里面寫死的,我們可以啟動內核的時候發現有以下輸出
Creating 4 MTD partitions on "NAND 256MiB 3,3V 8-bit": 0x00000000-0x00040000 : "bootloader" 0x00040000-0x00060000 : "params" 0x00060000-0x00260000 : "kernel" 0x00260000-0x10000000 : "root"可以搜索這個"bootloader"發現在arm/plat-s3c24xx/common-smdk.c下面定義
static struct mtd_partition smdk_default_nand_part[] = {[0] = {.name = "bootloader",.size = 0x00040000,.offset = 0,},[1] = {.name = "params",.offset = MTDPART_OFS_APPEND,.size = 0x00020000,},[2] = {.name = "kernel",.offset = MTDPART_OFS_APPEND,.size = 0x00200000,},[3] = {.name = "root",.offset = MTDPART_OFS_APPEND,.size = MTDPART_SIZ_FULL,} };MTDPART_OFS_APPEND?這個 offset 意思是緊接著上面一個分區的意思
總結
?
轉載:https://www.cnblogs.com/zongzi10010/p/10023696.html
總結
以上是生活随笔為你收集整理的内核启动流程分析(四)源码浅析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 内核启动流程分析(三)makefile
- 下一篇: 简单BootLoader