1. 程式人生 > >Linux內核(13) - 子系統的初始化之以PCI子系統為例

Linux內核(13) - 子系統的初始化之以PCI子系統為例

鏈接 技巧 中國 log 內核代碼 rem 位置 gac 三種

由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前面。

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

Linux內核(13) - 子系統的初始化之以PCI子系統為例