Linux那些事儿 之 我是PCI(4)初始化(一)

解析完了PCI的那些内核参数,再翻过多少座山跨过多少条河,内核就会遇到init/main.c里一个名叫do_initcalls的函数。do_initcalls对内核来说只不过是漫长冒险旅程中的一个驿站,对PCI这个故事来说却是命运转轮的开始,内核在它里边完成了对.initcall.init节里各种xxx_initcall函数的执行,PCI的那些自然也包括在内。你不用像新东方老罗“我走来走去,为中国的命运苦苦思索。”那样走来走去为PCI的命运思索,因为决定PCI命运的那些xxx_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
咱们从讲USB Core时就已经知道对这些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会首先被调用,那咱们就先窥视一下它的庐山真面目
class_register是设备模型中一个很基础的函数,在这里它的目的就是注册一个名叫“pci_bus”的class,关于class,你应该不会感到陌生了,usb那里就已经注册过一个usb_host,不过不同的是那时使用的是class_create,而现在使用的是class_register,咱不陪写代码的哥们儿玩这些文字游戏了,不管它是create还是register,咱只看它们能够带来啥后果,当然所谓的后果要体现在sysfs上,所以去look一下/sys/class目录

atm dma graphics hwmon i<chmetcnv w:st="on" unitname="C" sourcevalue="2" hasspace="False" negative="False" numbertype="1" tcsc="0">2c</chmetcnv>-adapter input

mem misc net pci_bus scsi_device scsi_disk

scsi_hostsound spi_host spi_master spi_transport tty

usb_device usb_endpoint usb_host vc vtconsole

想当初,usb子系统初始化的时候,调用了一次class_create(THIS_MODULE, "usb_host"),然后上边儿就多了一个usb_host目录,那么现在调用这个class_register,上边儿又会多出什么?这个大家一眼就能看出来,即使一眼看不出来两眼也能看出来了,赚钱买猪肉的本事没有,寻找这种敏感地带的本事还都是有的,凭空多出来的就是那个pci_bus。从这点儿看,create还是register对咱们来说都差不多,都是在/sys/class下边儿创建了一个类,usb_host类的目录里是各个具体的主机控制器,pci_bus类的目录里对应的就是各个pci总线了。本来难得糊涂一下明白这些就成了,不过如果真想稍微不那么糊涂一点儿,可以去扫两眼class_create的定义,你就会发现它里面最终也会调用一个class_register,这两个的差别就是class_create要更傻瓜一些,你指定个类的名称就可以调用它了,它里面会帮你创建一个struct class结构体,而class_register则更费事一些,你需要自己亲自动手创建一个struct class结构体。如果你觉得自己挺特殊,需要指定自己的release函数等,那就必须得使用class_register了,PCI就属于这种情况,至于它怎么个特殊,就是后话了。

pcibus_class_init之后,接着就应该是pci_driver_init

还记得linux设备模型里存在于总线、设备、驱动之间的那个著名的三角关系么?如果不记得,那就先听俺讲个小故事:

话说多少年以前有个人非常的健忘,他老婆很无奈,就对他说:“听说南村的谁谁谁专治女性不孕男性健忘,你还是去找他医一下吧!” 好男人准则第一条就是要听老婆的话,于是这个人就背上弓箭,骑上马出发了。人不是都有三急么,半道儿上他想大便,就把马拴在一棵大树上,躲在树后,顺手把箭插在地上。方便过后,正在顺爽,顺爽,一顺再顺,顺出新自我,忽然看见了地上的箭,惊出了一身冷汗,“好险,不知谁射来的箭,差点要了我的小命!”紧赶的往外跑,一脚踩在大便上,不禁连皱眉头,大骂不已:“谁这么缺德,不讲公共卫生,在这里随地大小便……”。等到看到拴在树上的马,又高兴起来,心想:虽然吃了点儿苦头,捡到一匹马着实得美,就像金帝美滋滋巧克力,全新的色彩,全新的味道。于是他骑上马晕晕乎乎不知所之,沿着原路折了回去。一边想着我这是在哪儿呢,一边瞧见了一座房子。“咦,这房子好生面熟?……”这个时候,他老婆正从屋里看见他糊里糊涂的样子,气不打一处来,出门儿来责备他。只见他不卑不亢的作了一个揖,说:“这位大嫂,你我素昧平生,何苦出言不逊?”

这个故事的教育意义就在于告诉我们健忘是一种病态,善忘是一种境界,做人不能健忘到如此地步。那个三角关系中的总线落实在USB就是usb_bus_type,落实在PCI就是上面的pci_bus_type,pci_driver_init函数的目的就是注册PCI总线,只有总线存在了,才会有设备的那条链表和驱动的那条链表,才会有设备和驱动之间的match。

你可能感兴趣的:(linux)