《Linux內核修煉之道》精華分享與討論(17)——子系統的初始化:以PCI子系統爲例

由Kconfig這張地圖的分佈來看,PCI這塊兒的代碼應該分佈在兩個地方,drivers/pci和arch/i386/pci,兩岸三地都屬 於一箇中國,不管是drivers/pci那兒的,還是arch/i386/pci那兒的,也都只屬於一個PCI子系統,本着一箇中國的原則,咱們要統籌 的全面的考察分析位於兩個地方的代碼,於是,這些遠遠突破了五位數的代碼左看右看橫看豎看都顯得那麼的陰森恐怖,不過人家咋說也是整個一PCI子系統,就 像走在T臺上的芙蓉姐姐和楊二車那姆一樣,看起來恐怖但也是很有內涵的,豈能夠讓人三眼兩眼三言兩語就給看透了說透了?

那現在咱們就高瞻遠矚統籌全面的掃視一下這兩個地方的代碼,根據前面的內容可以推測對於USB、PCI這樣的子系統都應該有一個 subsys_initcall這樣的入口,咱們得先找到它。朱德庸在《關於上班這件事》裏說了,要花前半生找入口,花後半生找出口。可見尋找入口對於咱 們這一生,對於看內核代碼這件事兒都是無比重要的,當然尋找subsys_initcall這個入口是不用花前半生那麼久的。下邊兒俺就把找到的給列出 來,爲什麼說“列”出來?難道還會有很多麼?你猜對了,PCI這邊兒入口格外多,而且是有預謀有組織成系列的,不單單有subsys_initcall, 還有arch_initcall、postcore_initcall等等等等。

文件                                     函數                                         入口                        內存位置
arch/i386/pci/acpi.c             pci_acpi_init                            subsys_initcall        .initcall4.init
arch/i386/pci/common.c      pcibios_init                              subsys_initcall        .initcall4.init
arch/i386/pci/i386.c            pcibios_assign_resources       fs_initcall                .initcall5.init
arch/i386/pci/legacy.c         pci_legacy_init                        subsys_initcall        .initcall4.init
drivers/pci/pci-acpi.c           acpi_pci_init                            arch_initcall            .initcall3.init
drivers/pci/pci-driver.c        pci_driver_init                         postcore_initcall      .initcall2.init
drivers/pci/pci-sysfs.c         pci_sysfs_init                          late_initcall              .initcall7.init
drivers/pci/pci.c                  pci_init                                    device_initcall           .initcall6.init
drivers/pci/probe.c             pcibus_class_init                     postcore_initcall       .initcall2.init
drivers/pci/proc.c               pci_proc_init                            __initcall                   .initcall6.init
arch/i386/pci/init.c             pci_access_init                        arch_initcall              .initcall3.init

看看那一列入口,形盡而意不同的種種xxx_initcall讓人眼花繚亂的,真不知道該從哪兒下手,應了keso那句話:所有的痛苦都來自選擇, 所謂幸福,就是沒有選擇。像USB子系統那樣子簡簡單單一個subsys_initcall,沒得選擇,傻強都知道怎麼走。不過你迷惘一陣兒就可以了,可 別真的被繞進去了。要知道“多少事,從來急;天地轉,光陰迫。一萬年太久,只爭朝夕。四海翻騰雲水怒,五洲震盪風雷激。要看清一切入口,全無敵。”咱們要 只爭朝夕看清一切入口的。

咱們已經知道對這些xxx_initcall函數的調用是必須按照一定順序的,先調用.initcall1.init中的再調 用.initcall2.init中的,很明顯,表裏列出來的應該最先被調用的是.initcall2.init子節中的兩個函數 pcibus_class_init和pci_driver_init。現在問題出現了,對於處於同一子節中的那些函數,比如 pcibus_class_init和pci_driver_init這兩個函數來說又是哪個會最先被調用?當然,你可以說處在前邊兒地址的會最先被調 用,這是大實話,因爲do_initcalls函數的實現就是在.initcall.init所處的地址上來回的for循環。可你怎麼知道同一子節的函數 哪個在前邊兒哪個在後邊兒?

別的不多說,先看看gcc的Using the GNU Compiler Collection中的一段話:

the linker searches and processes libraries and object files in the order they are specified. Thus, ‘foo.o -lz bar.o’ searches library ‘z’ after file ‘foo.o’ but before ‘bar.o’.

看完這段話,希望會聽到你說:我悟道了!更希望會看到你翻出來drivers/pci/Makefile文件,瞅到下邊兒這兩行

5 obj-y           += access.o bus.o probe.o remove.o pci.o quirks.o /
6                         pci-driver.o search.o pci-sysfs.o rom.o setup-res.o

probe.o在pci- driver.o的前面,那麼probe.c裏的pcibus_class_init函數也會在pci- driver.c裏的pci_driver_init函數之前被調用。再

給你看一句話,Documents/kbuild/makefile.txt的3.2中的:

The order of files in $(obj-y) is significant.

對於pcibus_class_init函數和pci_driver_init函數這樣位於同一目錄位置的可以通過該目錄Makefile文件指定 的鏈接順序來判斷,而對於.initcall3.init子節中的acpi_pci_init函數和pci_access_init函數則不能使用這個方 法。

acpi_pci_init在drivers/pci/pci-acpi.c文件裏,而pci_access_init在arch/i386 /pci/init.c文件裏,它倆根本就不在同一個目錄下面,所以前邊兒判斷pcibus_class_init和pci_driver_init的順 序的技巧並不適用,那有什麼方法可以讓咱們找出它們的順序?看看王冉怎麼說:“昨天是五一勞動節,可是全國都在放大假絕大多數人不勞動。可見,慶祝一件事 的最好的方法就是不去做這件事。譬如,慶祝世界盃的最好的方式就是不去參加世界盃——中國隊幾乎一直都是這麼做的。再譬如,慶祝情人節的最好的方式就是不 去找情人——於是,很多中國的男人把情人節的前一天(2月13日)過成了情人節。”按他這說法,認清這倆函數之間順序的最好方法就是不去管它們的順序,俺 可以點兵點將的隨便點一個出來先說,不過作爲一個很清楚自己責任和使命的80後,俺還是決定去發掘一下它們的順序。

其實這個問題可以轉化爲arch/i386/pci下面的Makefile和drivers/pci下面的Makefile誰先誰後的問題,往大的方面說,就是內核是怎麼構建的,也就是kbuild的問題。

內核裏的Makefile主要有三種:第一種是根目錄裏的Makefile,它雖然只有一個,但地位遠遠凌駕於其它Makefile之上,裏面定義 了所有與體系結構無關的變量和目標;第二種是arch/*/Makefile,看到arch就知道它是與特定體系結構相關的,它包含在根目錄下的 Makefile中,爲kbuild提供體系結構的特定信息,而它裏面又包含了arch/*/下面各級子目錄的那些Makefile;第三種就是密密麻麻 躲在drivers/等各個子目錄下邊兒的那些Makefile了。

而kbuild構建內核的過程中,是首先從根目錄Makefile開始執行,從中獲得與體系結構無關的變量和依賴關係,並同時從arch/* /Makefile中獲得體系結構特定的變量等信息,用來擴展根目錄Makefile所提供的變量。此時kbuild已經擁有了構建內核需要的所有變量和 目標,然後,Make進入各個子目錄,把部分變量傳遞給子目錄裏的Makefile,子目錄Makefile根據配置信息決定編譯哪些源文件,從而構建出 一個需要編譯的文件列表。

然後,然後還有很漫長的路,你編譯內核要耗多久,它就有多漫長,不過說到這兒前面問題的答案就已經浮出水面了,很明顯,arch/i386/pci 下面的Makefile是處在drivers/pci下面的Makefile前面的,也就是說,pci_access_init處在 acpi_pci_init前面。

掌握了這些潛規則,我們在研究某個子系統時,就可以獲得初始化函數的執行順序,並按照該順序使用韓峯同志對待日記的態度進行深入的分析。

發佈了7 篇原創文章 · 獲贊 0 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章