<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Linux內(nèi)核是如何巧妙的初始化各個模塊的

          共 6026字,需瀏覽 13分鐘

           ·

          2021-01-29 07:41



          相信很多在研究linux內(nèi)核源碼的同學,經(jīng)常會發(fā)現(xiàn)一些模塊的初始化函數(shù)找不到調(diào)用者,比如下面的網(wǎng)絡模塊的初始化函數(shù):


          // net/ipv4/af_inet.cstatic int __init inet_init(void){????????...        /*         *      Set the IP module up?????????*/        ip_init();
          /* Setup TCP slab cache for open requests. */ tcp_init();
          /* Setup UDP memory threshold */ udp_init(); ...}fs_initcall(inet_init);


          即使你在整個內(nèi)核代碼中搜索,也找不到任何地方調(diào)用這個函數(shù),那這個函數(shù)到底是怎么調(diào)用的呢?


          秘密就在這個函數(shù)之后的一行代碼里:


          fs_initcall( inet_init);


          在該行代碼中,fs_initcall是一個宏,具體定義如下:


          // include/linux/init.h#define ___define_initcall(fn, id, __sec) \        static initcall_t __initcall_##fn##id __used \                __attribute__((__section__(#__sec ".init"))) = fn;...#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)...#define fs_initcall(fn)                 __define_initcall(fn, 5)


          在該宏展開后,上面宏調(diào)用的結(jié)果,大致像下面這個樣子:


          static?initcall_t?__initcall_inet_init5?__attribute__((__section__(".initcall5.init")))?=?inet_init;


          由上可見,fs_initcall宏最終是定義了一個靜態(tài)變量,該變量的類型是initcall_t,值是宏參數(shù)表示的函數(shù)地址。


          initcall_t類型的定義如下:


          typedef int (*initcall_t)(void);


          由上可見,initcall_t是一個函數(shù)指針類型,它定義的變量會指向一個函數(shù),該函數(shù)的參數(shù)要為空,返回類型要為int。


          我們可以再看下上面的 inet_init 方法,該方法確實符合這些要求。


          綜上可知,fs_initcall宏定義了一個變量?__initcall_inet_init5,它的類型為initcall_t,它的值為inet_init函數(shù)的地址。


          到這里我相信很多同學會想,linux內(nèi)核一定是通過這個變量來調(diào)用inet_init函數(shù)的,對嗎?


          對,也不對。


          對是因為內(nèi)核確實是通過該變量指向的內(nèi)存來獲取inet_init方法的地址并調(diào)用該方法的。


          不對是因為內(nèi)核并不是通過上面的__initcall_inet_init5變量來訪問這個內(nèi)存的。


          那不用這個變量,還能通過其他方式訪問這個內(nèi)存嗎?


          當然可以,這正是linux內(nèi)核設計的巧妙之處。


          我們再來看下上面的宏展開之后,靜態(tài)變量__initcall_inet_init5的定義,在該定義中有如下的一些代碼:


          __attribute__((__section__(".initcall5.init")))


          該部分代碼并不屬于c語言標準,而是gcc對c語言的擴展,它的作用是聲明該變量屬于 .initcall5.init這個section。


          所謂section,我們可以簡單的理解為對程序所占內(nèi)存區(qū)域的一種布局和規(guī)劃,比如我們常見的 section有 .text用來存放我們的代碼,.data或.bss用來存放我們的變量。


          通過這些section的定義,我們可以把程序中的相關(guān)功能放到同一塊內(nèi)存區(qū)域中,這樣來方便內(nèi)存管理。


          除了這些默認的section之外,我們還可以通過gcc的attribute來自定義section,這樣我們就可以把相關(guān)的函數(shù)或變量放到相同的section中了。


          比如上面的__initcall_inet_init5變量就屬于.initcall5.init這個自定義section。


          在定義了這些section之后,我們可以在鏈接腳本中告訴linker,這些section在內(nèi)存中的位置及布局是什么樣子的。


          對于x86平臺來說,內(nèi)核的鏈接腳本是:


          arch/x86/kernel/vmlinux.lds.S


          在該腳本中,對.initcall5.init等這些section做了相關(guān)定義,具體邏輯如下:


          // include/asm-generic/vmlinux.lds.h#define INIT_CALLS_LEVEL(level)                                         \                __initcall##level##_start = .;                          \                KEEP(*(.initcall##level##.init))                        \                KEEP(*(.initcall##level##s.init))                       \
          #define INIT_CALLS \ __initcall_start = .; \ KEEP(*(.initcallearly.init)) \ INIT_CALLS_LEVEL(0) \ INIT_CALLS_LEVEL(1) \ INIT_CALLS_LEVEL(2) \ INIT_CALLS_LEVEL(3) \ INIT_CALLS_LEVEL(4) \ INIT_CALLS_LEVEL(5) \ INIT_CALLS_LEVEL(rootfs) \ INIT_CALLS_LEVEL(6) \ INIT_CALLS_LEVEL(7) \ __initcall_end = .;


          由上可見,initcall相關(guān)的section有很多,我們上面例子中的.initcall5.init只是其中一個,除此之外還有 .initcall0.init,.initcall1.init等等這些section。


          這些section都是通過宏INIT_CALLS_LEVEL來定義其處理規(guī)則的,相同level的section被放到同一塊內(nèi)存區(qū)域,不同level的section的內(nèi)存區(qū)域按level大小依次連接在一起。


          對于上面的__initcall_inet_init5變量來說,它的section是.initcall5.init,它的level是5。


          假設我們還有其他方法調(diào)用了宏fs_initcall,那該宏為該方法定義的靜態(tài)變量所屬的section也是.initcall5.init,level也是5。


          由于該變量和__initcall_inet_init5變量所屬的initcall的level都相同,所以它們被連續(xù)放在同一塊內(nèi)存區(qū)域里。


          也就是說,這些level為5的靜態(tài)變量所占的內(nèi)存區(qū)域是連續(xù)的,又因為這些變量的類型都為initcall_t,所以它們正好構(gòu)成了一個類型為initcall_t的數(shù)組,而數(shù)組的起始地址也在INIT_CALLS_LEVEL宏中定義了,就是__initcall5_start。


          如果我們想要調(diào)用這些level為5的initcall,只要先拿到__initcall5_start地址,把其當成元素類型為initcall_t的數(shù)組的起始地址,然后遍歷數(shù)組中的元素,獲取該元素對應的函數(shù)指針,就可以通過該指針調(diào)用對應的函數(shù)了。


          來看下具體代碼:


          // init/main.cextern initcall_entry_t __initcall_start[];extern initcall_entry_t __initcall0_start[];extern initcall_entry_t __initcall1_start[];extern initcall_entry_t __initcall2_start[];extern initcall_entry_t __initcall3_start[];extern initcall_entry_t __initcall4_start[];extern initcall_entry_t __initcall5_start[];extern initcall_entry_t __initcall6_start[];extern initcall_entry_t __initcall7_start[];extern initcall_entry_t __initcall_end[];
          static initcall_entry_t *initcall_levels[] __initdata = { __initcall0_start, __initcall1_start, __initcall2_start, __initcall3_start, __initcall4_start, __initcall5_start, __initcall6_start, __initcall7_start, __initcall_end,};
          static void __init do_initcall_level(int level){????????initcall_entry_t?*fn;????????... for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++) do_one_initcall(initcall_from_entry(fn));}
          static void __init do_initcalls(void){ int level;
          for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) do_initcall_level(level);}


          在上面的代碼中,do_initcalls方法遍歷了所有的合法level,對于每個level,do_initcall_level方法又調(diào)用了該level里所有函數(shù)指針指向的函數(shù)。


          我們上面示例中的inet_init方法就屬于level 5,也是在這里被調(diào)用到的。


          linux內(nèi)核就是通過這種方式來調(diào)用各個模塊的初始化方法的,很巧妙吧。


          最后我們再來總結(jié)下:


          1. 在各模塊的初始化方法之后,一般都會調(diào)用一個類似于fs_initcall(inet_init)的宏,該宏的參數(shù)是該模塊的初始化方法的方法名。


          2. 該宏展開后的結(jié)果是定義一個靜態(tài)變量,該變量通過gcc的attribute來聲明其所屬的initcall level的section,比如inet_init方法對應的靜態(tài)變量就屬于.initcall5.init這個section。


          3. 在linux的鏈接腳本里,通過INIT_CALLS_LEVEL宏告知linker,將屬于同一level的所有靜態(tài)變量放到連續(xù)的一塊內(nèi)存中,組成一個元素類型為initcall_t的數(shù)組,該數(shù)組的起始地址放在類似__initcall5_start的變量中。


          4. 在內(nèi)核的初始化過程中,會通過調(diào)用 do_initcalls方法,遍歷各個level里的各個函數(shù)指針,然后調(diào)用該指針指向的方法,即各模塊的初始化方法。



          各個模塊的初始化方法就是這樣被調(diào)用的。


          希望你喜歡。



          良許個人微信


          添加良許個人微信即送3套程序員必讀資料


          → 精選技術(shù)資料共享

          → 高手如云交流社群





          本公眾號全部博文已整理成一個目錄,請在公眾號里回復「m」獲??!

          推薦閱讀:

          Zabbix 通過 API 監(jiān)控 k8s

          為 Linux 愛好者打造的極簡 Mac 終端

          Chrome 的小恐龍游戲,被我破解了...


          5T技術(shù)資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,單片機,樹莓派,等等。在公眾號內(nèi)回復「1024」,即可免費獲?。。?/span>


          瀏覽 29
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产成人无码A片免费看玄火 | 成年人精品视频 | 亚洲第一综合 | 免费成人高清 | 色色爽|