歡迎您光臨本站 註冊首頁

那年,我學習《Linux內核修鍊之道》——子系統的初始化:那些入口函數 .

那年,我學習《Linux內核修鍊之道》——子系統的初始化:那些入口函數 .



這些文章是閱讀《linux內核修鍊之道》的筆記和一些自己補充的知識和感悟,寫的不清楚的地方請查看《linux內核修鍊之道》等資料。同時文章若有不妥的地方請大家指出,謝謝。



內核選項的解析完成之後,各個子系統的初始化即進入第二部分—入口函數的調用。通常USB、PCI這樣的子系統都會有一個名為subsys_initcall的入口,如果你選擇它們作為研究內核的切入點,那麼就請首先找到它。

朱德庸在《關於上班這件事》里說,要花前半生找入口,花後半生找出口。可見尋找入口對於咱們這一生,對於看內核代碼這件事兒都是無比重要的。

但是很多時候,入口並不僅僅只有subsys_initcall一個,比如PCI。117 #define pure_initcall(fn)               __define_initcall("0",fn,1)
118
119 #define core_initcall(fn)               __define_initcall("1",fn,1)
120 #define core_initcall_sync(fn)          __define_initcall("1s",fn,1s)
121 #define postcore_initcall(fn)           __define_initcall("2",fn,2)
122 #define postcore_initcall_sync(fn)      __define_initcall("2s",fn,2s)
123 #define arch_initcall(fn)               __define_initcall("3",fn,3)
124 #define arch_initcall_sync(fn)          __define_initcall("3s",fn,3s)
125 #define subsys_initcall(fn)             __define_initcall("4",fn,4)
126 #define subsys_initcall_sync(fn)        __define_initcall("4s",fn,4s)
127 #define fs_initcall(fn)                 __define_initcall("5",fn,5)
128 #define fs_initcall_sync(fn)            __define_initcall("5s",fn,5s)
129 #define rootfs_initcall(fn)     __define_initcall("rootfs",fn,rootfs)
130 #define device_initcall(fn)             __define_initcall("6",fn,6)
131 #define device_initcall_sync(fn)        __define_initcall("6s",fn,6s)
132 #define late_initcall(fn)               __define_initcall("7",fn,7)
133 #define late_initcall_sync(fn)          __define_initcall("7s",fn,7s)
134
135 #define __initcall(fn) device_initcall(fn)這些入口有個共同的特徵,它們都是使用__define_initcall宏定義的。它們的調用也不是隨便的,而是按照一定順序的,這個順序就取決於__define_initcall宏。__define_initcall宏用來將指定的函數指針放到.initcall.init節里。

.initcall.init節

內核可執行文件由許多鏈接在一起的對象文件組成。對象文件有許多節,如文本、數據、init數據、bass等等。這些對象文件都是由一個稱為鏈接器腳本的文件鏈接並裝入的。這個鏈接器腳本的功能是將輸入對象文件的各節映射到輸出文件中;換句話說,它將所有輸入對象文件都鏈接到單一的可執行文件中,將該可執行文件的各節裝入到指定地址處。 vmlinux.lds是存在於arch//目錄中的內核鏈接器腳本,它負責鏈接內核的各個節並將它們裝入內存中特定偏移量處。在vmlinux.lds文件里查找initcall.init就可以看到下面的內容__inicall_start = .;
.initcall.init : AT(ADDR(.initcall.init) – 0xC0000000) {
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
}
__initcall_end = .;這就告訴我們.initcall.init節又分成了7個子節,而xxx_initcall入口函數指針具體放在哪一個子節裡邊兒是由xxx_initcall的定義中,__define_initcall宏的參數決定的,比如core_initcall將函數指針放在.initcall1.init子節,device_initcall將函數指針放在了.initcall6.init子節等等。各個子節的順序是確定的,即先調用.initcall1.init中的函數指針再調用.initcall2.init中的函數指針,等等。不同的入口函數被放在不同的子節中,因此也就決定了它們的調用順序。

do_initcalls()函數

那些入口函數的調用由do_initcalls函數來完成。

do_initcall函數通過for循環,由__initcall_start開始,直到__initcall_end結束,依次調用識別到的初始化函數。而位於__initcall_start和__initcall_end之間的區域組成了.initcall.init節,其中保存了由xxx_initcall形式的宏標記的函數地址,do_initcall函數可以很輕鬆的取得函數地址並執行其指向的函數。

.initcall.init節所保存的函數地址有一定的優先順序,越前面的函數優先順序越高,也會比位於後面的函數先被調用。

由do_initcalls函數調用的函數不應該改變其優先順序狀態和禁止中斷。因此,每個函數執行后,do_initcalls會檢查該函數是否做了任何變化,如果有必要,它會校正優先順序和中斷狀態。

另外,這些被執行的函數有可以完成一些需要非同步執行的任務,flush_scheduled_work函數則用於確保do_initcalls函數在返回前等待這些非同步任務結束。
666 static void __init do_initcalls(void)
667 {
668   initcall_t *call;
669   int count = preempt_count();
670
671   for (call = __initcall_start; call < __initcall_end; call++) {
672    ktime_t t0, t1, delta;
673    char *msg = NULL;
674    char msgbuf;
675    int result;
676
677    if (initcall_debug) {
678     printk("Calling initcall 0x%p", *call);
679     print_fn_descriptor_symbol(": %s()",
680       (unsigned long) *call);
681     printk("/n");
682     t0 = ktime_get();
683    }
684
685    result = (*call)();
686
687    if (initcall_debug) {
688     t1 = ktime_get();
689     delta = ktime_sub(t1, t0);
690
691     printk("initcall 0x%p", *call);
692     print_fn_descriptor_symbol(": %s()",
693       (unsigned long) *call);
694     printk(" returned %d./n", result);
695
696     printk("initcall 0x%p ran for %Ld msecs: ",
697      *call, (unsigned long long)delta.tv64 >> 20);
698     print_fn_descriptor_symbol("%s()/n",
699      (unsigned long) *call);
700    }
701
702    if (result && result != -ENODEV && initcall_debug) {
703     sprintf(msgbuf, "error code %d", result);
704     msg = msgbuf;
705    }
706    if (preempt_count() != count) {
707     msg = "preemption imbalance";
708     preempt_count() = count;
709    }
710    if (irqs_disabled()) {
711     msg = "disabled interrupts";
712     local_irq_enable();
713    }
714    if (msg) {
715     printk(KERN_WARNING "initcall at 0x%p", *call);
716     print_fn_descriptor_symbol(": %s()",
717       (unsigned long) *call);
718     printk(": returned with %s/n", msg);
719    }
720   }
721
722   /* Make sure there is no pending stuff from the initcall sequence */
723   flush_scheduled_work();
724 }
《解決方案》

謝謝分享

[火星人 ] 那年,我學習《Linux內核修鍊之道》——子系統的初始化:那些入口函數 .已經有400次圍觀

http://coctec.com/docs/service/show-post-1003.html