ARM-Linux核心移植之(一)——核心啟動流程分析
K-Style
轉載請註明來自于衡陽師範學院08電2 K-Style http://blog.csdn.net/ayangke,QQ:843308498 郵箱:yangkeemail@qq.com
核心版本:2.6.22 為什麼要採用這樣一個較低的版本進行移植了,因為韋東山大牛說了,低版本的才能學到東西,越是高版本需要移植時做的工作量越少,學的東西越少。
核心啟動分為三個階段,第一是運行head.S檔案和head-common.S,第三個階段是允許第二是運行main.c檔案
對於ARM的處理器,核心第一個啟動的檔案是arc/arm/kernel下面的head.S檔案。當然arc/arm/boot/compress下面也有這個檔案,這個檔案和上面的檔案略有不同,當要產生壓縮的核心時zImage時,啟動的是後者,後者與前者不同的時,它前面的代碼是做自解壓的,後面的代碼都相同。我們這裡這分析arc/arm/kernel下面的head.S檔案。當head.S所作的工作完成後它會跳到init/目錄下跌的main.c的start_kernel函數開始執行。
第一階段:
首先截取部分head.S檔案
ENTRY(stext)
msr cpsr_c,#PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
@ andirqs disabled
mrc p15,0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10,r5 @ invalidprocessor (r5=0)?
beq __error_p @ yes, error 'p'
bl __lookup_machine_type @ r5=machinfo
movs r8,r5 @ invalidmachine (r5=0)?
beq __error_a @ yes, error 'a'
bl __create_page_tables
/*
*The following calls CPU specific code in a position independent
*manner. See arch/arm/mm/proc-*.S fordetails. r10 = base of
*xxx_proc_info structure selected by __lookup_machine_type
*above. On return, the CPU will be readyfor the MMU to be
*turned on, and r0 will hold the CPU control register value.
*/
ldr r13,__switch_data @ address to jump toafter
@ mmuhas been enabled
adr lr,__enable_mmu @ return (PIC)address
第一步,執行的是__lookup_processor_type,這個函數是檢查處理器型號,它讀取你的電路板的CPU型號與核心支援的處理器進行比較看是否能夠處理。這個我們不關心它的具體實現過程,因為現在主流處理器核心都提供了支援。
第二步,執行的是__lookup_machine_type,這個函數是來檢查機器型號的,它會讀取你bootloader傳進來的機器ID和他能夠處理的機器ID進行比較看是否能夠處理。核心的ID號定義在arc/arm/tool/mach_types檔案中MACH_TYPE_xxxx宏定義。核心究竟就如何檢查是否是它支援的機器的呢?實際上每個機器都會在/arc/arm/mach-xxxx/smdk-xxxx.c檔案中有個描述特定機器的資料結構,如下
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
MACHINE_START和 MACHINE_END實際上被展開成一個結構體
#defineMACHINE_START(_type,_name) \staticconst struct machine_desc __mach_desc_##_type \ __used \ __attribute__((__section__(".arch.info.init")))= { \ .nr =MACH_TYPE_##_type, \ .name =_name, #defineMACHINE_END \};
於是上面的資料結構就被展開為
staticconst 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__mach_desc結構,核心通過檢查每個machine_desc__mach_desc的nr號和bootloader傳上來的ID進行比較,如果相同,核心就認為支援該機器,而且核心在後面的工作中會調用該機器的machine_desc__mach_desc_結構中的方法進行一些初始化工作。
第三步,建立一級頁表。
第四步,在R13中儲存__switch_data 這個函數的地址,在第四步使能mmu完成後會跳到該函數執行。
第五步,執行的是__enable_mmu,它是使能MMU,這個函數調用了__turn_mmu_on函數,讓後在_turn_mmu_on在最後將第三步賦給R13的值傳給了PC指標 (mov pc, r13),於是核心開始跳到__switch_data這個函數開始執行。
我們再來看arch/arm/kenel/head-common.S這個檔案中的__switch_data函數
__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 /* * The following fragment of code is executedwith the MMU on in MMU mode, * and uses absolute addresses; this is notposition independent. * * r0 =cp#15 control register * r1 = machine ID * r9 = processor ID */ .type __mmap_switched,%function__mmap_switched: adr r3,__switch_data + 4 ldmia r3!,{r4, r5, r6, r7} cmp r4,r5 @ Copy datasegment if needed1: 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, r5, r6, sp} str r9, [r4] @ Save processor ID str r1, [r5] @ Save machine type bic r4,r0, #CR_A @ Clear 'A' bit stmia r6,{r0, r4} @ Save controlregister values b start_kernel
這個函數做的工作是,複製資料區段清楚BBS段,設定堆在指標,然後儲存處理器核心和機器核心等工作,最後跳到start_kernel函數。於是核心開始執行第二階段。
第二階段:
我們再來看init/目錄下的main.c的start_kernel函數,這裡我只了部分。
asmlinkage void __init start_kernel(void){ ……………………. …………………….. printk(KERN_NOTICE); printk(linux_banner); setup_arch(&command_line); setup_command_line(command_line); parse_early_param(); parse_args("Booting kernel",static_command_line, __start___param, __stop___param - __start___param, &unknown_bootoption);……………………………………………… init_IRQ(); pidhash_init(); init_timers(); hrtimers_init(); softirq_init(); timekeeping_init(); time_init(); profile_init();……………………………………………………… console_init();……………………………………………………………… rest_init();}
從上面可以看出start_kernel首先是列印核心資訊,然後對bootloader傳進來的一些參數進行處理,再接著執行各種各樣的初始化,在這其中會初始化控制台。最後會調用rest_init();
我們再來看rest_init()函數
static void noinline __init_refok rest_init(void)__releases(kernel_lock){int pid;kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);............}
他啟動了kernel_init這個函數,再來看kerne_init函數
static int __init kernel_init(void * unused){..............................if (!ramdisk_execute_command)ramdisk_execute_command = "/init";if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {ramdisk_execute_command = NULL;prepare_namespace();}/* * Ok, we have completed the initial bootup, and * we're essentially up and running. Get rid of the * initmem segments and start the user-mode stuff.. */init_post();return 0;}
kernel_init先調用了prepare_namespace();然後調用了init_post函數
void __init prepare_namespace(void){..........................mount_root();.....................}
可以看出prepare_namespace調用了mount_root掛接根檔案系統。接著kernel_init再執行init_post
static int noinline init_post(void){......................................./*開啟dev/console控制台,並設定為標準輸入、輸出*/if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)printk(KERN_WARNING "Warning: unable to open an initial console.\n");(void) sys_dup(0);(void) sys_dup(0);if (ramdisk_execute_command) {run_init_process(ramdisk_execute_command);printk(KERN_WARNING "Failed to execute %s\n",ramdisk_execute_command);}/* * We try each of these until one succeeds. * * The Bourne shell can be used instead of init if we are * trying to recover a really broken machine. *///如果bootloader指定了init參數,則啟動init參數指定的進程if (execute_command) {run_init_process(execute_command);printk(KERN_WARNING "Failed to execute %s. Attempting ""defaults...\n", execute_command);}//如果沒有指定init參數,則分別帶sbin、etc、bin目錄下啟動init進程run_init_process("/sbin/init");run_init_process("/etc/init");run_init_process("/bin/init");run_init_process("/bin/sh");panic("No init found. Try passing init= option to kernel.");}
注意上面的run_init_process的會等待init進程返回才往後面執行,所有它一旦找到一個init可執行檔檔案它將一去不複返。
綜上,核心啟動的過程大致為以下幾步:
1.檢查CPU和機器類型
2.進行堆棧、MMU等其他程式運行關鍵的東西進行初始化
3.列印核心資訊
4.執行各種模組的初始化
5.掛接根檔案系統
6.啟動第一個init進程