注:下面的內容是以linux-2.6.38和mini6410為例進行學習的。
玩過或者移植過arm-linux的都應該知道在/arch/arm目錄下有許多與具體處理器相關的目錄,當然對於6410的話所對應的目錄就是mach-s3c64xx,在里面找到與具體板子相關的文件mach-mini6410.c,沒錯,就是它。無論是出於想移植到新的內核還是出於想深入學習某一款arm等,對這個文件的學習是必不可少的。這個文件大部分內容是對平台設備(例如串口,LCD,Nand falsh等)的結構體初始化,在這個文件的最后有一個非常重要的宏:
1 MACHINE_START(MINI6410, "MINI6410") 2 /* Maintainer: Ben Dooks <ben-linux@fluff.org> */ 3 .boot_params = S3C64XX_PA_SDRAM + 0x100, 4 5 .init_irq = s3c6410_init_irq, 6 .map_io = mini6410_map_io, 7 .init_machine = mini6410_machine_init, 8 .timer = &s3c24xx_timer, 9 MACHINE_END
MINI6410這個宏在/arch/arm/tools/mach-types文件里定義:
mini6410 MACH_MINI6410 MINI6410 2520
MACHINE_START的定義在arch/arm/include/asm/mach/arch.h,如下:
1 #define MACHINE_START(_type,_name) \ 2 static const struct machine_desc __mach_desc_##_type \ 3 __used \ 4 __attribute__((__section__(".arch.info.init"))) = { \ 5 .nr = MACH_TYPE_##_type, \ 6 .name = _name, 7 8 #define MACHINE_END \ 9 };
噢,其實就是定義了一個struct machine_desc類型結構體變量,這個結構體還定義了其他一些成員,接下來着重關注.init_machine這個成員,它是一個函數指針,值為mini6410_machine_init,這個函數也在mach-mini6410.c中定義。內容是什么呢?呵呵,因為在這里只給出大體流程,具體內容先不分析。現在最關心的是這個結構體變量在哪里被調用,從而調用它里面的成員和成員函數呢?先來看/arch/arm/kernel/setup.c里面的setup_arch()函數:
1 void __init setup_arch(char **cmdline_p) 2 { 3 struct tag *tags = (struct tag *)&init_tags; 4 struct machine_desc *mdesc; 5 char *from = default_command_line; 6 7 unwind_init(); 8 9 setup_processor(); 10 mdesc = setup_machine(machine_arch_type); 11 machine_desc = mdesc; 12 machine_name = mdesc->name;
......................
這個函數在/init/main.c的start_kernel()函數里被調用。看到第10行,這里的setup_machine()函數的作用就是找到我們想要的struct machine_desc類型的變量,也就是在mach-mini6410.c里定義那個變量。函數的參數machine_arch_type的值是什么呢?繼續看:
1 #ifdef CONFIG_MACH_MINI6410 2 # ifdef machine_arch_type 3 # undef machine_arch_type 4 # define machine_arch_type __machine_arch_type 5 # else 6 # define machine_arch_type MACH_TYPE_MINI6410 7 # endif 8 # define machine_is_mini6410() (machine_arch_type == MACH_TYPE_MINI6410) 9 #else 10 # define machine_is_mini6410() (0) 11 #endif
第6行,MACH_TYPE_MINI6410宏為:
#define MACH_TYPE_MINI6410 2520
也就是說參數machine_arch_type的值為2520。在setup_machine()函數里主要調用了lookup_machine_type()函數來查找對應的type,應該是出於效率的原因,這個函數是通過匯編實現的,在此就不給出具體代碼了。
到這里,知道了在/init/main.c的start_kernel()函數里調用了setup_arch(),在setup_arch()里找到了具體的struct machine_desc類型的變量,但是在哪里通過這個變量調用里面的成員或成員函數的呢?繼續找。還是在setup.c里,看到了這樣一個函數:
1 static int __init customize_machine(void) 2 { 3 /* customizes platform devices, or adds new ones */ 4 if (machine_desc->init_machine) 5 machine_desc->init_machine(); 6 return 0; 7 } 8 arch_initcall(customize_machine);
終於看到了,成員函數init_machine就是在這里被調用的。但是它沒有被顯式調用,而是放在了arch_initcall這個宏里,去看看它怎么定義的:
#define arch_initcall(fn) __define_initcall("3",fn,3)
再看__define_initcall宏:
1 #define __define_initcall(level,fn,id) \ 2 static initcall_t __initcall_##fn##id __used \ 3 __attribute__((__section__(".initcall" level ".init"))) = fn
嗯,它被鏈接到了.initcall段里,現在簡單看看/arch/arm/kernel/vmlinux.lds這個鏈接腳本里關於initcall的定義:
1 __initcall_start = .; 2 *(.initcallearly.init) __early_initcall_end = .; 3 *(.initcall0.init) *(.initcall0s.init) 4 *(.initcall1.init) *(.initcall1s.init) 5 *(.initcall2.init) *(.initcall2s.init) 6 *(.initcall3.init) *(.initcall3s.init) 7 *(.initcall4.init) *(.initcall4s.init) 8 *(.initcall5.init) *(.initcall5s.init) 9 *(.initcallrootfs.init) 10 *(.initcall6.init) *(.initcall6s.init) 11 *(.initcall7.init) *(.initcall7s.init) 12 __initcall_end = .;
可以看到customize_machine()被放到了.initcall3.init里。說了那么多定義,究竟它在哪里被調用啊?好吧,它是在/init/main.c里一個叫do_initcalls()的函數里被調用,去看看唄:
1 extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[]; 2 3 static void __init do_initcalls(void) 4 { 5 initcall_t *fn; 6 7 for (fn = __early_initcall_end; fn < __initcall_end; fn++) 8 do_one_initcall(*fn); 9 }
看到第1行,很熟悉吧。在for循環里依次調用了從__early_initcall_end開始到__initcall_end結束的所有函數。customize_machine()也是在其間被調用。
好了,到這里差不多該結束了,最后總結一下這些函數調用順序:
start_kernel()--->setup_arch()--->do_initcalls()--->customize_machine()--->mini6410_machine_init()