*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
__initcall_end = .;
http://book.51cto.com/art/201007/213623.htm
4.5.3 分析示例
这里以PCI子系统为例,分析一下它的初始化都使用了上述的哪些宏标记。
与很多子系统不同,PCI子系统的实现代码分布在内核代码树的两个地方,除去drivers/pci存放了体系结构无关部分的代码之外,还有arch/i386/pci存放了体系结构相关部分的代码,因此我们需要在这两个地方分别查找它的初始化代码。
如表4.1所示为PCI子系统的初始化代码分布情况。
PCI子系统的初始化几乎使用了本节所描述的所有xxx_initcall宏,它的初始化也严格按照表4.1所描述的内存存放顺序执行。
表4.1 PCI子系统初始化代码分布情况
文 件 | 初始化函数 | 宏 标 记 | 内 存 位 置 |
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/proc.c | pci_proc_init | __initcall | .initcall6.init |
arch/i386/pci/init.c | pci_access_init | arch_initcall | .initcall3.init |
但是,我们可以从表4.1中发现,PCI子系统的一些初始化函数位于同一子节,前面只是讲述了不同子节之间的函数按照子节的优先级顺序执行,并没有讲述同一子节函数之间的调用顺序。
当然,我们可以指出一个事实,同一子节之间,地址位于最前面的函数会首先被调用。但是我们并不知道哪个函数位于前边、哪个函数位于后边,比如同样位于.initcall2.init子节的pcibus_class_init函数和pci_driver_init函数,有没有一个简单的方法来进行判断?
下面是GCC手册中的一段话。
- 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'.
即是说,连接器按照库文件和目标文件被指定的顺序进行处理,打开pcibus_class_init函数和pci_driver_init函数所在目录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函数之前被调用。
对于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文件,它们位于不同的目录,此时问题即转化为arch/i386/pci下的Makefile和drivers/pci下的Makefile谁先谁后的问题,这就涉及kbuild构建内核的运行机制。
内核中的Makefile主要有如下3种。
内核源码树根目录里的Makefile。虽说只有一个,但地位远远高于其他Makefile,其中定义了所有与体系结构无关的变量和目标。
arch/*/Makefile。与特定体系结构相关,它会被根目录下的Makefile包含,为kbuild提供体系结构的特定信息。而它又包含了arch/*/目录下面各级子目录下的那些Makefile。
drivers/等各个子目录下的那些Makefile。
kbuild构建内核时,首先从根目录Makefile开始执行,从中获得与体系结构无关的变量和依赖关系,并同时从arch/*/Makefile中获得体系结构特定的变量等信息,用来扩展根目录Makefile所提供的变量。
此时,kbuild已经拥有了构建内核需要的所有变量和目标。然后,kbuild进入各个子目录,把部分变量传递给子目录里的Makefile,子目录Makefile根据配置信息决定编译哪些源文件,从而构建出一个需要编译的文件列表。
之后,即是内核编译的漫长过程。现在,很明显,arch/i386/pci下的Makefile是在drivers/pci下的Makefile之前被执行,即是说,pci_access_init函数在acpi_pci_init函数之前被执行。
掌握这些规则,我们在研究某个子系统时,即可获得初始化函数的执行顺序,并按照该顺序进行深入的分析。