標籤:
核心選項的解析完成之後,各個子系統的初始化即進入第二部分—入口函數的調用。通常USB、PCI這樣的子系統都會有一個名為subsys_initcall的入口,如果你選擇它們作為研究核心的切入點,那麼就請首先找到它。
朱德庸在《關於上班這件事》裡說,要花前半生找入口,花後半生找出口。可見尋找入口對於咱們這一生,對於看核心代碼這件事兒都是無比重要的。
但是很多時候,入口並不僅僅只有subsys_initcall一個,比如PCI。
以下代碼來自 linux核心源碼中 include/linux/init.h 檔案
- 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/<target>/目錄中的核心連結器指令碼,它負責連結核心的各個節並將它們裝入記憶體中特定位移量處。在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中的函數指標,等等。不同的入口函數被放在不同的子節中,因此也就決定了它們的調用順序。
注意:裝置驅動程式中常見的module_init(x)函數,查看init.h檔案發現,
#define module_init(x)__initcall(x);
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) __define_initcall("6",fn,6)
這樣推斷 module_init 調用優先順序為6低於subsys_initcall調用優先順序4.
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[40];
- 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驅動程式的啟動流程,這篇文章對Linux子系統調用順序進行了詳細的講解,同時也說明了裝置驅動程式的調用順序,很值得收藏。
本文來自:http://blog.csdn.net/yimiyangguang1314/article/details/7312209
linux子系統的初始化_subsys_initcall():那些入口函數【轉】