加入星計劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴散
  • 作品版權(quán)保護
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • fs_initcall函數(shù)介紹:
    • fs_initcall函數(shù)調(diào)用的層次:
    • 每個section空間排布情況是如何的?
    • 內(nèi)核執(zhí)行順序是?
    • 結(jié)語
  • 推薦器件
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

linux內(nèi)核initcall放置在各個section中函數(shù)執(zhí)行流程

01/08 10:40
3117
閱讀需 22 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

作者:良知猶存,轉(zhuǎn)載授權(quán)以及圍觀:歡迎關(guān)注微信公眾號:羽林君

linux以及嵌入式一些代碼,我們看到core_initcall、device_initcall等等需要鏈接器分配各個section,并且在啟動該模塊時候執(zhí)行。下面我們詳細追溯一下執(zhí)行過程。

fs_initcall函數(shù)介紹:

Linux內(nèi)核中的fs_initcall函數(shù):用于在引導過程中進行文件系統(tǒng)等初始化。

初始化注冊:當文件系統(tǒng)模塊被加載時,它使用fs_initcall宏注冊其初始化函數(shù)。該宏將初始化函數(shù)添加到__initcall_fs部分。

內(nèi)核引導過程:在引導過程中,在基本硬件初始化和內(nèi)存設(shè)置之后,內(nèi)核開始執(zhí)行初始化函數(shù)。

執(zhí)行fs_initcall函數(shù):fs_initcall函數(shù)按照其注冊順序依次執(zhí)行。這些函數(shù)初始化各種文件系統(tǒng)并執(zhí)行必要的設(shè)置任務(wù)。

文件系統(tǒng)初始化:每個fs_initcall函數(shù)負責設(shè)置和初始化特定的內(nèi)容。這可能涉及初始化數(shù)據(jù)結(jié)構(gòu)、注冊文件系統(tǒng)類型、準備緩存和其他相關(guān)任務(wù)。

完成和交接:一旦所有fs_initcall函數(shù)都執(zhí)行完畢,內(nèi)核會繼續(xù)完成引導過程,包括啟動用戶空間和初始化設(shè)備。

下面是一個簡單的示例代碼,展示了fs_initcall函數(shù)的使用和文件系統(tǒng)初始化的過程:

#include?<linux/init.h>
#include?<linux/module.h>

static?int?__init?my_filesystem_init(void)?{
????//?執(zhí)行文件系統(tǒng)特定的初始化任務(wù)
????printk(KERN_INFO?"My?Filesystem:?Initializingn");
????//?其他初始化操作...

????return?0;
}

fs_initcall(my_filesystem_init);

MODULE_LICENSE("GPL");

這其中my_filesystem_init函數(shù)被注冊為fs_initcall函數(shù)。當模塊加載時,該初始化函數(shù)將被執(zhí)行,完成特定文件系統(tǒng)的初始化任務(wù)。實際的文件系統(tǒng)模塊會包含更多復(fù)雜的初始化邏輯,這個例子只是用來展示fs_initcall函數(shù)的基本用法。

fs_initcall函數(shù)調(diào)用的層次:

在Linux內(nèi)核中,fs_initcall宏實際上是通過__define_initcall來定義的。下面是__define_initcall的定義:

#define?__define_initcall(fn,?id)?
????static?initcall_t?__initcall_##fn##id?__used?
????__attribute__((__section__(".initcall"?#id?".init")))?=?fn

這段代碼展示了__define_initcall的定義方式。在這里,__define_initcall宏創(chuàng)建了一個靜態(tài)的initcall_t類型變量,并將其放置在特定的.initcall節(jié)(section)中。這樣,在內(nèi)核初始化時,這些函數(shù)就會按照其在源代碼中出現(xiàn)的順序被依次調(diào)用。

fs_initcall實際上是通過__define_initcall宏來實現(xiàn)的,它們共同構(gòu)成了Linux內(nèi)核中初始化調(diào)用機制的一部分。

fs_initcall函數(shù)被放置的section的位置

在Linux內(nèi)核中,.initcall節(jié)(section)是通過鏈接腳本(linker script)定義的。鏈接腳本指定了可執(zhí)行文件的內(nèi)存布局,包括代碼、數(shù)據(jù)和其他段的放置位置。

對于.initcall節(jié)(section),它通常由鏈接腳本中的一些規(guī)則來定義。這個節(jié)用于存放初始化函數(shù)的地址,以便在內(nèi)核啟動時按照順序執(zhí)行這些初始化函數(shù)。

具體的定義可能會因內(nèi)核版本和架構(gòu)而異,但通??梢栽趦?nèi)核源代碼的arch/<architecture>/kernel/vmlinux.lds.S或類似的文件中找到相關(guān)的鏈接腳本定義。在這些文件中,我們可以看到下面的內(nèi)容:

.initcall.init?:?{
????INIT_CALLS
}

文件位置:linux-xxxarcharmkernelvmlinux.lds.S

文件位置:linux-xxxincludeasm-genericvmlinux.lds.h

???????????__initcall_start?=?.;
???????????.initcall.init?:?{
?????????????????*(.initcall1.init)
????????????????...
?????????????????*(.initcall7.init)
?????????????????}
???????????__initcall_end?=?.;

上面是INIT_CALLS對應(yīng)的函數(shù),*(.initcall##level##.init),這個函數(shù)就對應(yīng)了__define_initcall宏里面的__section__(".initcall" #id ".init"),繼續(xù)查看fs_initcall,對應(yīng)的level就是這個部分__define_initcall(fn, 5)的5。

上述示例中的INIT_CALLS通常會包含對.initcall節(jié)(section)的定義,規(guī)定了將哪些符號放入該節(jié)中。這些定義可能會隨著不同的內(nèi)核版本和架構(gòu)而有所不同,但其基本思想是相似的:將初始化函數(shù)的地址放入特定的節(jié)(section)中,以便在啟動時按順序執(zhí)行這些函數(shù)。

__define_initcall這個宏也是可以設(shè)置多個初始化函數(shù),并將它們放置在不同的.initcall節(jié)(section)中。

假設(shè)我們有兩個初始化函數(shù):init_function_1init_function_2,我們可以使用上述宏定義來將它們分別放置在不同的.initcall節(jié)(section)中。

//?定義多個初始化函數(shù)
static?void?__init?init_function_1(void)?{
????//?初始化函數(shù)1的內(nèi)容
}

static?void?__init?init_function_2(void)?{
????//?初始化函數(shù)2的內(nèi)容
}

//?使用?__define_initcall?宏定義來設(shè)置多個函數(shù)
__define_initcall(init_function_1,?1);
__define_initcall(init_function_2,?2);

在這個例子中,init_function_1被放置在.initcall1.init節(jié)(section)中,而init_function_2則被放置在.initcall2.init節(jié)(section)中。這樣,在內(nèi)核啟動時,這些函數(shù)就會按照其在源文件中出現(xiàn)的順序依次被調(diào)用。

通過使用帶有不同標識符的宏定義,可以將多個初始化函數(shù)放置在不同的.initcall節(jié)(section)中,從而實現(xiàn)按順序執(zhí)行多個初始化函數(shù)的目的。

以af_inet.c里面的fs_initcall(inet_init);fs_initcall(ipv4_offload_init);介紹放置的情況:怎么在section放置的

在這個例子中,fs_initcall宏用于將inet_initipv4_offload_init函數(shù)放置在.initcall.init節(jié)(section)中。這樣,在內(nèi)核啟動時,這些函數(shù)就會按照其在源文件中出現(xiàn)的順序依次被調(diào)用。

下面是簡化版本的代碼:

//?定義要初始化的函數(shù)
static?void?__init?inet_init(void)?{
????//?inet_init的初始化內(nèi)容
}

static?void?__init?ipv4_offload_init(void)?{
????//?ipv4_offload_init的初始化內(nèi)容
}

//?使用?fs_initcall?宏將函數(shù)放置在?.initcall.init?節(jié)(section)中
fs_initcall(inet_init);
fs_initcall(ipv4_offload_init);

上述代碼,inet_initipv4_offload_init函數(shù)會被放置在.initcall.init節(jié)(section)中,以便在內(nèi)核啟動時按照其在源文件中出現(xiàn)的順序依次被調(diào)用。

通過這個例子,我們明白了如何使用fs_initcall宏將這兩個函數(shù)放置在.initcall.init節(jié)(section)中.

每個section空間排布情況是如何的?

還是上面inet_initipv4_offload_init函數(shù)來介紹,由于fs_initcall宏使用了__attribute__((__section__(".initcall.init"))),這將導致這些函數(shù)被放置在.initcall.init節(jié)(section)中。這樣,在鏈接時,這些函數(shù)的地址將按照其在源文件中出現(xiàn)的順序排布在該特定的節(jié)(section)內(nèi)。

這些函數(shù)位于可執(zhí)行文件的內(nèi)存中的某個位置,它們的排布情況如下所示:

|---------------------|
|?????.text?section???|
|---------------------|
|????...?other?sections?...???|
|---------------------|
|??.initcall.init?section??|
|---------------------|
|???????inet_init??????|
|---------------------|
|??ipv4_offload_init???|
|---------------------|
|????...?other?functions?...???|
|---------------------|
|???????.data?section??|
|---------------------|
|??????...?other?sections?...????|
|---------------------|

在這個示例中,.initcall.init節(jié)(section)包含了inet_initipv4_offload_init函數(shù),它們會按照它們在源代碼中出現(xiàn)的順序排布在該節(jié)(section)中。這樣,在內(nèi)核啟動時,這些函數(shù)就會按照它們在.initcall.init節(jié)(section)中的排布順序依次被調(diào)用。

這里section的大小是隨機按照大小自動分配還是需要開發(fā)者設(shè)置好

在一般情況下,.initcall.init這樣的特殊節(jié)(section)的大小是由鏈接器自動分配的,而不是由開發(fā)者手動設(shè)置的。當鏈接器處理可執(zhí)行文件時,它會根據(jù)各個節(jié)(section)中的內(nèi)容以及鏈接腳本中的規(guī)則來確定每個節(jié)(section)的大小和排布。

對于.initcall.init節(jié)(section),其大小將取決于其中包含的初始化函數(shù)的數(shù)量和大小。鏈接器會根據(jù)這些函數(shù)的地址和大小來動態(tài)地分配空間,以便容納所有的初始化函數(shù)。

因此,開發(fā)者通常無需手動設(shè)置.initcall.init節(jié)(section)的大小。相反,鏈接器會根據(jù)實際情況自動進行分配,確保所有的初始化函數(shù)都能被正確地安置在這個特定的節(jié)(section)中,并且在內(nèi)核啟動時按照順序被調(diào)用。

如何自己設(shè)置section的大小

在一般情況下,開發(fā)者通常不需要手動設(shè)置節(jié)(section)的大小。鏈接器會根據(jù)鏈接腳本中的規(guī)則和可執(zhí)行文件中各個部分的大小自動進行分配。

如果我們有特殊需求,希望手動設(shè)置某個節(jié)(section)的大小,可以通過鏈接腳本來實現(xiàn)。在鏈接腳本中,我們可以定義節(jié)(section)的起始位置、大小以及其他屬性。

以下簡單的模板,在鏈接腳本中手動設(shè)置一個名為.my_section的節(jié)(section)的大小:

.my_section : {
    /* 定義節(jié)(section)的起始位置 */
    start = .;
    /* 設(shè)置節(jié)(section)的大小為固定值(例如0x1000)*/
    input_section(.text);
    input_section(.data);
    /* 其他內(nèi)容... */
    end = .;
} > RAM

在這個示例中,.my_section節(jié)(section)被手動設(shè)置為包含.text.data節(jié)(section)的內(nèi)容,并且其大小被設(shè)置為固定值。當鏈接器處理可執(zhí)行文件時,它將按照這些規(guī)則來分配空間并確定這個特定節(jié)(section)的大小。

需要注意的是,手動設(shè)置節(jié)(section)的大小可能需要對鏈接腳本和鏈接過程有深入的了解,因此在大多數(shù)情況下,開發(fā)者無需手動設(shè)置節(jié)(section)的大小,而是依賴于鏈接器自動進行分配。

這個是我實際應(yīng)用的一款芯片的鏈接修改:

FUN?0x400?(0x10000-0x400)
{
????;cpu.o?(+RO)
????xlib.a?(+RO)
}

上面這部分我使用的鏈接腳本中一部分內(nèi)容。為FUN的節(jié)(section)中,它的起始地址為0x10000,大小為0x400。

在這個節(jié)(section)中包含了兩個文件:cpu.oxlib.a,它們都被標記為只讀(Read-Only)。鏈接器會將這兩個文件的只讀部分放置在由FUN定義的地址范圍內(nèi)。

鏈接腳本用于指導鏈接器如何組織可執(zhí)行文件的各個部分,包括節(jié)(section)的排布和屬性。這個片段也是屬于鏈接腳本的一部分,這個里面鏈接器會將cpu.oxlib.a的只讀部分放置在從0x10000開始、大小為0x400的范圍內(nèi)。

只是一個demo示例,如果進一步操作這個鏈接腳本,我們要參考特定的鏈接器文檔以及相關(guān)的目標平臺和工具鏈的文檔,以確保正確地設(shè)置節(jié)(section)的屬性和排布。芯片之間區(qū)別挺大的。

內(nèi)核執(zhí)行順序是?

介紹完了section片段,我們再來說一下,這些函數(shù)的初始化位置以及執(zhí)行順序。

上面我們介紹了vmlinux.lds.S中的INIT_CALLS就是我們定義好的那些函數(shù),那他們怎么被調(diào)用的呢
在Linux內(nèi)核啟動過程中,INIT_CALLS(包括subsys_initcall,fs_initcall,device_initcall等)會在不同的階段被執(zhí)行。這些初始化調(diào)用是通過鏈接器腳本和特定的內(nèi)核宏來安排的。

具體來說,INIT_CALLS的執(zhí)行時機如下:

    1. 在內(nèi)核啟動的早期階段,

start_kernel

    1. 函數(shù)會調(diào)用

rest_init

    1. 。在

rest_init

    1. 中,會觸發(fā)

do_basic_setup

    1. 函數(shù)的執(zhí)行,其中包括對文件系統(tǒng)的基本設(shè)置。

    1. 在do_basic_setup
    1. 函數(shù)中,會調(diào)用do_initcalls
    1. 函數(shù)。

  1. 在do_initcalls函數(shù)中,各種初始化函數(shù)會按照鏈接器腳本中的順序被執(zhí)行。

  2. fs_initcall函數(shù)是其中之一。

    看到了執(zhí)行過程,其中是按照各個level進行調(diào)用的,而__define_initcall(level,fn)的作用就是指示編譯器把一些初始化函數(shù)的指針(即:函數(shù)起始地址)按照順序放置一個名為 .initcall.init 的section中,這個section又被分成了n個子section,它們按順序排列。在內(nèi)核初始化階段,這些放置到這個section中的函數(shù)指針將供do_initcalls() 按順序依次調(diào)用,來完成相應(yīng)初始化。

    而函數(shù)指針放置到的子section由宏定義的level確定,對應(yīng)level較小的子section位于較前面。而位于同一個子section內(nèi)的函數(shù)指針順序不定,將由編譯器按照編譯的順序隨機指定。同理,如果我們想先執(zhí)行一些定義的函數(shù),那就可以把它們放置于level比較小的定義中。

    結(jié)語

    這就是我自己對于linux內(nèi)核initcall放置在各個section中函數(shù)執(zhí)行流程的一些分享。如果大家有更好的想法,也歡迎大家加我好友交流。

推薦器件

更多器件
器件型號 數(shù)量 器件廠商 器件描述 數(shù)據(jù)手冊 ECAD模型 風險等級 參考價格 更多信息
ASEMPLV-100.000MHZ-LR-T 1 Abracon Corporation MEMS OSC XO 100.0000MHZ LVDS SMD
$4.89 查看

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

一個程序員,喜歡寫文章,還喜歡打籃球,也喜歡吉他鋼琴的駁雜之人。日常更新自己,分享包括但不限于C/C++、嵌入式、物聯(lián)網(wǎng)、Linux等編程學習筆記,同時,公眾號內(nèi)包含大量的學習資源。歡迎關(guān)注,一同交流學習,共同進步!