Linux那些事兒 之 戲說USB(12)從這裡開始
The `section' attribute specifies that a function lives in a
particular section. For example, the declaration: extern void foobar (void) __attribute__ ((section ("bar"))); puts the function ‘foobar' in the ‘bar' section. Some file formats do not support arbitrary sections so the
‘section' attribute is not available on all platforms. If you
need to map the entire contents of a module to a particular
section, consider using the facilities of the linker instead. 通常編譯器將函式放在.text節,變數放在.data 或 .bss 節,使用section屬性,可以讓編譯器將函式或變數放在指定的節中。那麼前面對__init的定義便表示將它修飾的程式碼放在.init.text節。聯結器可以把相同節的程式碼或資料安排在一起,比如__init修飾的所有程式碼都會被放在.init.text節裡,初始化結束後就可以釋放這部分記憶體。 那核心又是如何呼叫到這些__init修飾的初始化函式那?好奇心是科學的原動力,茶葉蛋就是這麼煮出來的,原子彈也是這麼造出來的,__init背後的哲學總不會比它們還難,芙蓉姐姐說了,我挑戰,我喜歡。好像越聊越遠了,不過想想多少年前當自己還是青澀少年的時候就對它極度的好奇過,這裡還是儘量將它說一下,也順便積攢下rp,稍後的冠軍盃裡俺的米蘭也好有個開門紅。 要回答這個問題,還需要回顧一下上面938行的程式碼,那裡已經提到subsys_initcall也是一個巨集,它也在include/linux/init.h裡定義
這裡又出現了一個巨集__define_initcall,它是用來將指定的函式指標fn放到initcall.init節裡,也在include/linux/init.h檔案裡定義,這裡就不多說了,有那點意思就可以了。而對於具體的subsys_initcall巨集,則是把fn放到.initcall.init的子節.initcall4.init裡。要弄清楚.initcall.init、.init.text和.initcall4.init這樣的東東,我們還需要了解一點核心可執行檔案相關的概念。
核心可執行檔案由許多連結在一起的物件檔案組成。物件檔案有許多節,如文字、資料、init 資料、bass 等等。這些物件檔案都是由一個稱為連結器指令碼的檔案連結並裝入的。這個連結器指令碼的功能是將輸入物件檔案的各節對映到輸出檔案中;換句話說,它將所有輸入物件檔案都連結到單一的可執行檔案中,將該可執行檔案的各節裝入到指定地址處。 vmlinux.lds是存在於 arch/<target>/ 目錄中的核心連結器指令碼,它負責連結核心的各個節並將它們裝入記憶體中特定偏移量處。
涉及到的東西越來越多了是吧,先深呼吸,平靜一下,堅定而又勇敢的開啟arch/i386/kernel/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_start指向.initcall.init節的開始,__initcall_end指向它的結尾。而.initcall.init節又被分為了7個子節,分別是
.initcall1.init .initcall2.init .initcall3.init .initcall4.init .initcall5.init .initcall6.init .initcall7.init我們的subsys_initcall巨集便是將指定的函式指標放在了.initcall4.init子節。其它的比如core_initcall將函式指標放在.initcall1.init子節,device_initcall將函式指標放在了.initcall6.init子節等等,都可以從include/linux/init.h檔案找到它們的定義。各個位元組的順序是確定的,即先呼叫.initcall1.init中的函式指標再呼叫.initcall2.init中的函式指標,等等。__init修飾的初始化函式在核心初始化過程中呼叫的順序和.initcall.init節裡函式指標的順序有關,不同的初始化函式被放在不同的子節中,因此也就決定了它們的呼叫順序。
至於實際執行函式呼叫的地方,就在/init/main.c檔案裡,核心的初始化麼,不在那裡還能在哪裡,裡面的do_initcalls函式會直接用到這裡的__initcall_start、__initcall_end來進行判斷,不多說了。我們的思念已經入滔滔江水氾濫成災了,還是回到久違的usb_init函式吧。