在Linux移植之make uImage編譯過程分析中已經提到了uImage是一個壓縮的包并且內含壓縮程序,可以進行自解壓。自解壓完成之后內核代碼從物理地址為0x30008000處開始運行。下面分析在進入C之前內核做的一些工作,以下是內核啟動過程中打印出來的信息,其中Uncompressing Linux就是在自解壓代碼。make uImage編譯的最后也給出了鏈接腳本arch/arm/kernel/vmlinux.lds,以及鏈接的順序arch/arm/kernel/head.o?是第一個。
?
分析arch/arm/kernel/vmlinux.lds可以知道程序入口的地址是stext,并且是.text.head段
277 OUTPUT_ARCH(arm) 278 ENTRY(stext)291 . = (0xc0000000) + 0x00008000; 292 293 .text.head : { 294 _stext = .; 295 _sinittext = .; 296 *(.text.head) 297 }
打開arch/arm/kernel/head.s。可見內核運行的第一條代碼就是第79行的代碼,從這條開始分析,首先將CPU設置為管理模式,并且關閉所有中斷;然后獲得CPU的id。
76 .section ".text.head", "ax" //.text.head段 77 .type stext, %function 78 ENTRY(stext) //入口地址stext 79 msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode//確保進入了管理模式 80 @ and irqs disabled //并且禁止中斷 81 mrc p15, 0, r9, c0, c0 @ get processor id //獲得處理器的CPU id,并且存入 r9中 82 bl __lookup_processor_type @ r5=procinfo r9=cpuid //調用函數,輸入參數r9=cpuid。返回值r5=procinfo 83 movs r10, r5 @ invalid processor (r5=0)?//如果不支持當前CPU,則r5=0 84 beq __error_p @ yes, error 'p' //如果r5=0,則打印錯誤 85 bl __lookup_machine_type @ r5=machinfo //調用函數,r5=返回值machinfo 86 movs r8, r5 @ invalid machine (r5=0)? //如果不支持當前單板,則返回r5=0 87 beq __error_a @ yes, error 'a' //如果r5=0,則打印錯誤 88 bl __create_page_tables//創建一級頁表以建立虛擬地址到物理地址的映射關系,后面再研究
接著調用__lookup_processor_type,它位于arch\arm\kernel\head-common.S。它的功能是比較當前CPU的id與內核支持的CPU的id是否相符合。這段代碼在.proc.info.init段中從__proc_info_begin開始到__proc_info_end結束,尋找符合當前CPU的ID號的proc_info_list結構
145 .type __lookup_processor_type, %function 146 __lookup_processor_type: 147 adr r3, 3f //r3 = 第178行代碼的物理地址 148 ldmda r3, {r5 - r7} //將r3地址開始的3個地址的內容賦給 r5、r6、r7 ;r5=__proc_info_begin,r6=__proc_info_end 149 sub r3, r3, r7 @ get offset between virt&phys//r3=r3-r7,即物理地址與虛擬地址的差值 150 add r5, r5, r3 @ convert virt addresses to//r5=__proc_info_begind對應的物理地址 151 add r6, r6, r3 @ physical address space //r6=__proc_info_end對應的物理地址 152 1: ldmia r5, {r3, r4} @ value, mask//r3、r4等于proc_info_list結構中的cpu_val、cpu_mask 153 and r4, r4, r9 @ mask wanted bits//r4=r4&r9=cpu_mask&傳入的cpuid 154 teq r3, r4 //比較 155 beq 2f //如果相等,則找到對應的proc_info_list結構,跳到160行 156 add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)//r5指向下一個proc_info_list結構 157 cmp r5, r6 //是否已經比較完所有proc_info_list 158 blo 1b //沒有則繼續比較 159 mov r5, #0 @ unknown processor//比較完畢,但是沒有找到匹配的proc_info_list結構,r5=0 160 2: mov pc, lr//返回,返回的值為r5=proc_info_list176 .long __proc_info_begin 177 .long __proc_info_end 178 3: .long .//.表示當前這條代碼鏈接后的虛擬地址 179 .long __arch_info_begin 180 .long __arch_info_end
其中__proc_info_begin、__proc_info_end被定義在arch\arm\kernel\vmlinux.lds中,它的意思是內核源碼中有被定義為.proc.info.init的內容,它的起始地址是__proc_info_begin,結束地址為__proc_info_end。
299 .init : { /* Init code and data */ 230 *(.init.text) 231 _einittext = .; 232 __proc_info_begin = .; 233 *(.proc.info.init) 234 __proc_info_end = .;
接著看到proc_info_list結構的內容,它被定義在include\asm-arm\Procinfo.h中
29 struct proc_info_list { 30 unsigned int cpu_val; 31 unsigned int cpu_mask; 32 unsigned long __cpu_mm_mmu_flags; /* used by head.S */ 33 unsigned long __cpu_io_mmu_flags; /* used by head.S */ 34 unsigned long __cpu_flush; /* used by head.S */ 35 const char *arch_name; 36 const char *elf_name; 37 unsigned int elf_hwcap; 38 const char *cpu_name; 39 struct processor *proc; 40 struct cpu_tlb_fns *tlb; 41 struct cpu_user_fns *user; 42 struct cpu_cache_fns *cache; 43 };
接著找到對于當前內核支持的proc_info_list 定義,它在arch\arm\mm\proc-arm920.S 中。對于S3C2410、S3C2440芯片來說CPU ID都是0x41129200。cpu_val的值為0x41009200、cpu_mask的值為0xff00fff0,剛好匹配。
.section ".proc.info.init", #alloc, #execinstr448 .type __arm920_proc_info,#object 449 __arm920_proc_info: 450 .long 0x41009200//cpu_val值 451 .long 0xff00fff0//cpu_mask值 452 .long PMD_TYPE_SECT | \ 453 PMD_SECT_BUFFERABLE | \ 454 PMD_SECT_CACHEABLE | \ 455 PMD_BIT4 | \ 456 PMD_SECT_AP_WRITE | \ 457 PMD_SECT_AP_READ 458 .long PMD_TYPE_SECT | \ 459 PMD_BIT4 | \ 460 PMD_SECT_AP_WRITE | \ 461 PMD_SECT_AP_READ 462 b __arm920_setup 463 .long cpu_arch_name 464 .long cpu_elf_name 465 .long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB 466 .long cpu_arm920_name 467 .long arm920_processor_functions 468 .long v4wbi_tlb_fns 469 .long v4wb_user_fns 470 #ifndef CONFIG_CPU_DCACHE_WRITETHROUGH 471 .long arm920_cache_fns 472 #else 473 .long v4wt_cache_fns 474 #endif 475 .size __arm920_proc_info, . - __arm920_proc_info
繼續回到arch/arm/kernel/head.s往下分析,看到第83行,調用完__lookup_processor_type后r5的值變為執向找到的proc_info_list 結構的地址。所以第83行與第84行比較r5是否為0,如果為0說明沒有找到符合當前CPU的ID號,則打印錯誤。接著到85行,調用__lookup_machine_type,它同樣位于arch\arm\kernel\head-common.S中,它的功能是比較當前單板的id與內核支持的單板的id是否相符合。這段代碼在.arch.info.init段中從__arch_info_begin開始到__arch_info_end結束,尋找符合當前單板的ID號的machine_desc結構
176 .long __proc_info_begin 177 .long __proc_info_end 178 3: .long .//.表示當前這條代碼鏈接后的虛擬地址 179 .long __arch_info_begin 180 .long __arch_info_end193 .type __lookup_machine_type, %function 194 __lookup_machine_type: 195 adr r3, 3b //r3=第178行的物理地址 196 ldmia r3, {r4, r5, r6} //r4=r3。r5=__proc_info_end,r6=__proc_info_begin,取得的是虛擬地址 197 sub r3, r3, r4 @ get offset between virt&phys//r3=r3-r4,取得物理地址與虛擬地址的偏差 198 add r5, r5, r3 @ convert virt addresses to//r5=r5+r3,取得物理地址__proc_info_end 199 add r6, r6, r3 @ physical address space //r6=r6+r3,取得物理地址__proc_info_begin 200 1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type //r3=取得單板的編號 201 teq r3, r1 @ matches loader number?//比較r3與r1是否相等,即linux是否支持uboot傳入的單板 202 beq 2f @ found //如果相等,則跳到207行,找到支持的單板,返回 203 add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc//r5執向下一個machine_desc結構 204 cmp r5, r6 //是否已經比較完machine_desc結構? 205 blo 1b //如果沒有比較完,則跳到200行繼續比較 206 mov r5, #0 @ unknown machine //如果所有machine_desc都比較完了,r5=0 207 2: mov pc, lr //返回
其中__arch_info_begin、__arch_info_end被定義在arch\arm\kernel\vmlinux.lds中,它的意思是內核源碼中有被定義為.arch.info.init的內容,它的起始地址是__arch_info_begin,結束地址為__arch_info_end。
305 __arch_info_begin = .; 306 *(.arch.info.init) 307 __arch_info_end = .;
接著看到machine_desc結構的內容,它被定義在include\asm-arm\mach\Arch.h?中
17 struct machine_desc { 18 /* 19 * Note! The first four elements are used 20 * by assembler code in head-armv.S 21 */ 22 unsigned int nr; /* architecture number */ //單板的編號,是從內核傳過來的編號 r1 23 unsigned int phys_io; /* start of physical io */ 24 unsigned int io_pg_offst; /* byte offset for io 25 * page tabe entry */ 26 27 const char *name; /* architecture name */ 28 unsigned long boot_params; /* tagged list *///boo傳過來的tag標記的位置,也是從內核傳過來的 r2 29 30 unsigned int video_start; /* start of video RAM */ 31 unsigned int video_end; /* end of video RAM */ 32 33 unsigned int reserve_lp0 :1; /* never has lp0 */ 34 unsigned int reserve_lp1 :1; /* never has lp1 */ 35 unsigned int reserve_lp2 :1; /* never has lp2 */ 36 unsigned int soft_reboot :1; /* soft reboot */ 37 void (*fixup)(struct machine_desc *, 38 struct tag *, char **, 39 struct meminfo *); 40 void (*map_io)(void);/* IO mapping function *///IO映射函數,移植時需要關注 41 void (*init_irq)(void); 42 struct sys_timer *timer; /* system tick timer */ 43 void (*init_machine)(void); 44 };
接著需要找到對于當前內核支持的machine_desc定義,在include\asm-arm\mach\Arch.h 中有如下宏定義,它表示在.arch.info.init段存入一個machine_desc 的結構體,名稱為
__mach_desc_type,結構體內.nr、.name初始化為MACH_TYPE_type、_name
50 #define MACHINE_START(_type,_name) \ 51 static const struct machine_desc __mach_desc_##_type \ 52 __used \ 53 __attribute__((__section__(".arch.info.init"))) = { \ 54 .nr = MACH_TYPE_##_type, \ 55 .name = _name, 56 57 #define MACHINE_END \ 58 };
接著找調用MACHINE_START這個宏的文件,在arch\arm\mach-s3c2440\Mach-smdk2440.c 找到了,所以單板的ID為MACH_TYPE_S3C2440,它被定義在include\asm-arm\Mach-types.h中
#define MACH_TYPE_S3C2440? ? ? ? ? ? ? 362。與UBOOT傳入的參數相符合。
339 MACHINE_START(S3C2440, "SMDK2440") 340 /* Maintainer: Ben Dooks <ben@fluff.org> */ 341 .phys_io = S3C2410_PA_UART, 342 .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc, 343 .boot_params = S3C2410_SDRAM_PA + 0x100, 344 345 .init_irq = s3c24xx_init_irq, 346 .map_io = smdk2440_map_io, 347 .init_machine = smdk2440_machine_init, 348 .timer = &s3c24xx_timer, 349 MACHINE_END
繼續來看MACHINE_START(S3C2440, "SMDK2440")這個宏,在里面有許多和開發板相關的設置,比如說smdk2440_map_io,它被定義在arch\arm\mach-s3c2440\Mach-smdk2440.c中,在Linux移植之移植步驟中提到過想要移植成功,必須修改327行代碼,將晶振的設置改為12000000。還有其它的一些配置就不一一列舉了。
324 static void __init smdk2440_map_io(void) 325 { 326 s3c24xx_init_io(smdk2440_iodesc, ARRAY_SIZE(smdk2440_iodesc)); 327 s3c24xx_init_clocks(12000000);//根據開發板合適的晶振配置 328 s3c24xx_init_uarts(smdk2440_uartcfgs, ARRAY_SIZE(smdk2440_uartcfgs)); 329 }
回到arch/arm/kernel/head.s接著往下看,86、87行判斷__lookup_machine_type是否成功找到支持單板的machine_desc結構,如果沒找到則打印錯誤,88行是用來創建一級頁表以建立虛擬地址到物理地址的映射關系,這里不詳細分析。
繼續往下看,看到100行,其中r10的值為__arm920_proc_info所在地址,PROCINFO_INITFUNC為proc_info_list結構體的偏移量,具體為__cpu_flush,對應到__arm920_proc_info結構體內,pc的值就是b __arm920_setup這條語句所在地址,即執行b?__arm920_setup這條指令,__arm920_setup做一些MMU相關的初始化,在arch\arm\mm\proc-arm920.S中,這里不做細究。
97 ldr r13, __switch_data @ address to jump to after//r13是堆棧寄存器sp 98 @ mmu has been enabled 99 adr lr, __enable_mmu @ return (PIC) address //100行設置完成之后在使能MMU 100 add pc, r10, #PROCINFO_INITFUNC//調用__arm920_setup函數,應該跟MMU相關,后面再研究
b?__arm920_setup執行完畢返回之后執行的是arch/arm/kernel/head.s下的__enable_mmu 。
152 .type __enable_mmu, %function 153 __enable_mmu: ..... 174 b __turn_mmu_on187 .align 5 188 .type __turn_mmu_on, %function 189 __turn_mmu_on: 190 mov r0, r0 191 mrc p15, 0, r3, c0, c0, 0 @ read id reg 192 mov r3, r3 193 mov r3, r3 194 mov pc, r13//設置完MMU之后跳轉到__switch_data執行
__enable_mmu 執行完之后進入__switch_data執行,注意這時候的運行地址已經是初始化MMU之后的虛擬地址了。從15-24行可以看出pc=__mmap_switched,__mmap_switched的主要工作是將processor_id與__machine_arch_type初始化為當前MCU的編號與單板的編號
14 .type __switch_data, %object 15 __switch_data: 16 .long __mmap_switched 17 .long __data_loc @ r4 18 .long __data_start @ r5 19 .long __bss_start @ r6 20 .long _end @ r7 21 .long processor_id @ r4//之前找到的符合當前MCU的__arm920_proc_info結構體 22 .long __machine_arch_type @ r5//之前找到的符合單板的__mach_desc_S3C2440結構體 23 .long cr_alignment @ r6 24 .long init_thread_union + THREAD_START_SP @ sp 25 26 /* 27 * The following fragment of code is executed with the MMU on in MMU mode, 28 * and uses absolute addresses; this is not position independent. 29 * 30 * r0 = cp#15 control register 31 * r1 = machine ID 32 * r9 = processor ID 33 */ 34 .type __mmap_switched, %function 35 __mmap_switched://虛擬地址已經可以使用 36 adr r3, __switch_data + 4//r3=__data_loc所在的地址 37 38 ldmia r3!, {r4, r5, r6, r7}//r4=__data_loc所在地址;r5=__data_start所在地址依次類推 r3=__switch_data+4*4 39 cmp r4, r5 @ Copy data segment if needed //檢查是否有__data_loc段r4=r5說明沒有__data_loc 40 1: cmpne r5, r6 41 ldrne fp, [r4], #4 42 strne fp, [r5], #4 43 bne 1b 44 45 mov fp, #0 @ Clear BSS (and zero fp)//清0BSS段 46 1: cmp r6, r7 47 strcc fp, [r6],#4 48 bcc 1b 49 50 ldmia r3, {r4, r5, r6, sp}//r4=processor_id、r5=__machine_arch_type、r6=cr_alignment、sp=init_thread_union + THREAD_START_SP 51 str r9, [r4] @ Save processor ID//processor_id=r9 = proc_info_list.cpu_val = 0x41009200 52 str r1, [r5] @ Save machine type//__machine_arch_type=r1 = machine_desc .nr = MACH_TYPE_S3C2440 = 362 53 bic r4, r0, #CR_A @ Clear 'A' bit 54 stmia r6, {r0, r4} @ Save control register values 55 b start_kernel//跳轉到start_kernel C函數
最終執行b start_kernel,跳到C函數,這是第二階段的內容。
?