一、導(dǎo)讀
在linux內(nèi)核啟動(dòng)過程中,會(huì)向終端打印出很多的日志信息,從這些信息中可以得到許多內(nèi)核的行為。如果在啟動(dòng)階段出現(xiàn)了問題,那么很多的提示信息也會(huì)從終端打印出。
這些信息的輸出與具體模塊功能的執(zhí)行都?xì)w功于一個(gè)函數(shù):do_initcalls,本文將主要分析這個(gè)函數(shù)的執(zhí)行邏輯,且從這個(gè)函數(shù)延伸到linux各個(gè)子系統(tǒng)初始化背后的機(jī)制。
本文所有源碼分析基于linux內(nèi)核版本:4.1.15
二、do_initcalls
do_initcalls由do_basic_setup()調(diào)用:
do_basic_setup()由kernel_init()代表的內(nèi)核init線程函數(shù)間接調(diào)用(在kernel_init_freeable()被調(diào)用)。
在調(diào)用do_basic_setup之前,處理器已經(jīng)被初始化了,CPU子系統(tǒng)已經(jīng)啟動(dòng)并且運(yùn)行,內(nèi)存和進(jìn)程管理也工作正常,但是系統(tǒng)中的設(shè)備還沒有被初始化,故而do_basic_setup正作用于此,本文主要描述do_initcalls,所以不再進(jìn)而分析其他的函數(shù)。
do_initcalls在/init/main.c文件中實(shí)現(xiàn):
staticvoid__initdo_initcalls(void) { intlevel; for(level=0;level
函數(shù)中內(nèi)容比較少,是一個(gè)for循環(huán)結(jié)構(gòu),循環(huán)的對(duì)象是initcall_levels數(shù)組,該數(shù)組用于描述初始化調(diào)用的級(jí)別,定義如下:
externinitcall_t__initcall_start[]; externinitcall_t__initcall0_start[]; externinitcall_t__initcall1_start[]; externinitcall_t__initcall2_start[]; externinitcall_t__initcall3_start[]; externinitcall_t__initcall4_start[]; externinitcall_t__initcall5_start[]; externinitcall_t__initcall6_start[]; externinitcall_t__initcall7_start[]; externinitcall_t__initcall_end[]; staticinitcall_t*initcall_levels[]__initdata={ __initcall0_start, __initcall1_start, __initcall2_start, __initcall3_start, __initcall4_start, __initcall5_start, __initcall6_start, __initcall7_start, __initcall_end, };
從上述代碼可見,initcall_levels數(shù)組中的元素為initcall_t類型的指針,回到do_initcalls()函數(shù)中,該函數(shù)的核心操作是:按順序從__initcall0_start開始,到__initcall_end結(jié)束的節(jié)段(稱為初始化調(diào)用段)中取出不同段之間的函數(shù),并執(zhí)行。
存在這幾個(gè)初始化調(diào)用段之間的函數(shù)都是內(nèi)核中各個(gè)模塊的初始化函數(shù),而這些函數(shù)是如何加入到初始化調(diào)用段中的呢?又是如何設(shè)置調(diào)用級(jí)別的,會(huì)在后文中描述到。在do_initcalls()函數(shù)中,會(huì)根據(jù)initcall_levels初始化調(diào)用級(jí)別的數(shù)量調(diào)用do_initcall_level(),該函數(shù)實(shí)現(xiàn)如下:
staticvoid__initdo_initcall_level(intlevel) { initcall_t*fn; strcpy(initcall_command_line,saved_command_line); parse_args(initcall_level_names[level], initcall_command_line,__start___param, __stop___param-__start___param, level,level, &repair_env_string); for(fn=initcall_levels[level];fn
從上述代碼可見,在函數(shù)的最后也是一個(gè)for循環(huán)結(jié)構(gòu),該循環(huán)的操作對(duì)象為函數(shù)指針,且會(huì)將對(duì)應(yīng)的函數(shù)指針傳遞到do_one_initcall中,在該函數(shù)執(zhí)行函數(shù)指針?biāo)赶虻暮瘮?shù):
三、構(gòu)造section并添加函數(shù)
(3-1)構(gòu)造初始化調(diào)用section
在linux內(nèi)核中,不同架構(gòu)(ARCH)下的kernel目錄中,都會(huì)有一個(gè)名為vmlinux.lds.S的鏈接腳本,初始化調(diào)用section的構(gòu)造則在這個(gè)鏈接腳本中完成。
本文以ARM32架構(gòu)為例
在/arch/arm/kernel/vmlinux.lds.S中的鏈接腳本中,.init.data輸出節(jié)段則需要INIT_CALLS作為輸入節(jié)段:
INIT_CALLS定義在/include/asm-generic/vmlinux.lds.h文件中:
而在內(nèi)核的makefile中有以下語(yǔ)句:
LDFLAGS_vmlinux+=-Tarch/$(ARCH)/kernel/vmlinux.lds.s
用于指定構(gòu)建linux內(nèi)核鏡像時(shí)所使用的鏈接腳本,基于此,則會(huì)構(gòu)造好初始化調(diào)用section。
當(dāng)初始化調(diào)用section構(gòu)造完成后,是如何向該section中添加函數(shù)的呢?繼續(xù)往下看。(3-2)向section中添加函數(shù)
向section中添加函數(shù)的本質(zhì)操作則是__define_initcall(),定義如下:
并且linux內(nèi)核基于__define_initcall()封裝出了多個(gè)宏定義接口,供內(nèi)核中各個(gè)模塊使用,接口如下:
__define_initcall()宏定義的本質(zhì)則是定義一個(gè)initcall_t函數(shù)指針類型的變量并命名為__initcall_##fn##id,其中fn為賦值給該變量的函數(shù)名稱,id為初始化調(diào)用級(jí)別,然后將fn賦值給該變量。接著就是最為重要的技術(shù)點(diǎn):使用__attribute__將該變量加入到命名為"initcall##id.init"的section中,其中id為初始化調(diào)用級(jí)別,所以將fn添加到初始化調(diào)用section中則是通過這一點(diǎn)實(shí)現(xiàn)。例如:如果有以下類似的代碼:
staticvoid__initshow_info(void) { printk("I'miriczhao ") } core_initcall(show_info);
經(jīng)過層層宏替換后,本質(zhì)上則變成:
staticinitcall_t__initcall_core_initcall1__used __attribute__((__section__(".initcall1.init")))=show_info;
四、總結(jié)
綜上,linux內(nèi)核中使用基于__define_initcall封裝出的多個(gè)接口API初始化內(nèi)核的各個(gè)模塊,使用這些API接口會(huì)將指定的函數(shù)放到名稱為.initcall##id.init的section中,id為初始化調(diào)用級(jí)別,內(nèi)核中定義了14種調(diào)用級(jí)別:分別為1~7和1s~7s(linux 3.0后增加的擴(kuò)展)。這些調(diào)用級(jí)別是按照先后順序依次排列的。
(4-1)linux內(nèi)核中,對(duì)于內(nèi)核的各個(gè)模塊的初始化,正是通過使用__define_initcall()的衍生宏定義API將初始化函數(shù)放置到__initcall##id.initsection中,不同模塊的初始化函數(shù)按照調(diào)用級(jí)別順序排列。在內(nèi)核啟動(dòng)階段,這些放置到這個(gè)section中的函數(shù)指針將被do_initcalls()按順序依次調(diào)用,進(jìn)而完成各個(gè)模塊的初始化操作。
linux內(nèi)核系統(tǒng)非常龐大,各個(gè)子系統(tǒng)也非常多,他們的初始化函數(shù)從源碼上是不需要在內(nèi)核啟動(dòng)過程中去主動(dòng)調(diào)用的,從設(shè)計(jì)上這一點(diǎn)也不現(xiàn)實(shí),隨著內(nèi)核功能的增加,越來(lái)越復(fù)雜的驅(qū)動(dòng)程序,從而linux內(nèi)核基于編譯器section技術(shù),設(shè)計(jì)了初始化調(diào)用機(jī)制,將各個(gè)模塊的初始化與linux內(nèi)核啟動(dòng)主線分離。
(4-2)當(dāng)使用基于__define_initcall封裝出的多個(gè)API接口時(shí),函數(shù)指針放置到哪個(gè)子section由具體的宏定義API接口的level參數(shù)確定,較小的level參數(shù)對(duì)應(yīng)的函數(shù)指針則被放置在前面。而位于同一個(gè)子section內(nèi)的函數(shù)指針順序不定,由編譯器按照編譯的順序隨機(jī)指定。所以,如果一個(gè)模塊的初始化函數(shù)想要越早被調(diào)用執(zhí)行,則需要有較小的調(diào)用級(jí)別。
審核編輯:劉清
-
處理器
+關(guān)注
關(guān)注
68文章
19409瀏覽量
231190 -
Arch
+關(guān)注
關(guān)注
0文章
18瀏覽量
9689 -
LINUX內(nèi)核
+關(guān)注
關(guān)注
1文章
316瀏覽量
21744
原文標(biāo)題:驚呆了,linux內(nèi)核中的這機(jī)制 | do_initcalls
文章出處:【微信號(hào):嵌入式小生,微信公眾號(hào):嵌入式小生】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
Linux內(nèi)核自解壓過程分析
![<b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>自解壓過程<b class='flag-5'>分析</b>](https://file1.elecfans.com/web2/M00/B3/45/wKgaomVysVSAas31AAAXBrUD8nI125.png)
linux內(nèi)核do_fork函數(shù)創(chuàng)建新進(jìn)程
Linux內(nèi)核源碼之我見——內(nèi)核源碼的分析方法
簡(jiǎn)單分析linux內(nèi)核中的結(jié)構(gòu)體使用方法
邏輯代數(shù)與邏輯函數(shù)
Linux內(nèi)核GPIO操作函數(shù)的詳解分析
Linux內(nèi)核熱補(bǔ)丁安全隱患的探索
![<b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>熱補(bǔ)丁安全隱患的探索](https://file.elecfans.com/web2/M00/17/48/poYBAGFjtbaAJ-M0AAAms77bcW4911.png)
Linux內(nèi)核系統(tǒng)調(diào)用概述及實(shí)現(xiàn)原理
![<b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>系統(tǒng)調(diào)用概述及實(shí)現(xiàn)原理](https://file.elecfans.com//web2/M00/43/A6/poYBAGJ_SBuAMDQdAAAvuAWJ-3k473.png)
linux內(nèi)核啟動(dòng)過程會(huì)執(zhí)行用戶空間的init進(jìn)程
Linux內(nèi)核模塊參數(shù)傳遞與sysfs文件系統(tǒng)
Linux內(nèi)核SoftIrq源代碼分析
萬(wàn)千設(shè)備,linux內(nèi)核如何知道?
![萬(wàn)千設(shè)備,<b class='flag-5'>linux</b><b class='flag-5'>內(nèi)核</b>如何知道?](https://file1.elecfans.com/web2/M00/8C/85/wKgaomSt-cSAVYdnAAAI6NkztOU769.png)
Linux內(nèi)核如何使用結(jié)構(gòu)體和函數(shù)指針?
![<b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>如何使用結(jié)構(gòu)體和<b class='flag-5'>函數(shù)</b>指針?](https://file1.elecfans.com/web2/M00/A1/C9/wKgZomT38MGANWMgAABk_lYUJ0w67.jpeg)
Linux驅(qū)動(dòng)是如何掛載的
![<b class='flag-5'>Linux</b>驅(qū)動(dòng)是如何掛載的](https://file1.elecfans.com/web2/M00/A6/84/wKgaomUVOzWAONMkAAARxqVa0VY507.jpg)
bootm命令的執(zhí)行流程
![bootm命令的<b class='flag-5'>執(zhí)行</b>流程](https://file1.elecfans.com/web2/M00/B2/84/wKgaomVtnMmAHQgoAACY0vJ9D4E749.jpg)
評(píng)論