從MACHINE_START開始

學習AP20 kernel代碼的時候,看到文件kernel/arch/arm/mach-tegra/board-stingray.c

一個重要結構:

MACHINE_START(STINGRAY, "stingray")
.boot_params = 0x00000100,
.map_io = stingray_map_io,
.reserve = stingray_reserve,
.init_early = tegra_init_early,
.init_irq = tegra_init_irq,
.timer = &tegra_timer,
.init_machine = tegra_stingray_init,
MACHINE_END

MACHINE_START主要是定義了"struct machine_desc"的類型,放在 section(".arch.info.init"),是初始化數據,Kernel 起來之後將被丟棄。
其餘各個成員函數在setup_arch()中被賦值到內核結構體,在不同時期被調用:
start_kernel()--->setup_arch()--->do_initcalls()--->customize_machine()--->tegra_stingray_init()
1. .init_machine 在 arch/arm/kernel/setup.c 中被 customize_machine 調用,放在 arch_initcall() 段裏面,會自動按順序被調用。
2. .init_irq在start_kernel() --> init_IRQ() --> init_arch_irq()中被調用
3. .map_io 在 setup_arch() --> paging_init() --> devicemaps_init()中被調用
4. .timer是定義系統時鐘,定義TIMER4爲系統時鐘,在arch/arm/plat-s3c/time.c中體現。在start_kernel() --> time_init()中被調用。
5. .boot_params是bootloader向內核傳遞的參數的位置,這要和bootloader中參數的定義要一致。


可以參考這個blog


注:下面的內容是以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 <[email protected]> */
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()

 



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章