Linux内核(13) - 子系统的初始化之以PCI子系统为例

时间:2023-01-29 19:44:24

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

掌握了这些潜规则,我们在研究某个子系统时,就可以获得初始化函数的执行顺序,并按照该顺序使用韩峰同志对待日记的态度进行深入的分析。