Linux那些事儿 之 我是PCI(2)PCI全接触

中新浙江网11月6日电 近日,备受关注的浙江湖州市南浔区三人围追堵截偷车贼,致使小偷跳河溺水身亡的不作为间接故意杀人案,终于尘埃落定。

昨天,湖州市南浔区法院对三人一审判决颜克于犯故意杀人罪判处有期徒刑3年9个月;廖红军犯故意杀人罪判处有期徒刑3年3个月;韩应龙犯故意杀人罪判处有期徒刑3年,缓刑4年。
知道这个南浔区,也就是南浔镇么?俺大学里同一宿舍的兄弟就是那旮沓的,他嘴里边儿号称的中国四大名镇之一,真的假的俺没有考证过,从他嘴里说相声般崩出的 刘镛、张颂贤、徐迟、庞云曾、顾福昌等等等这一长串名字来看,所言应该非虚。不过上边儿的这则新闻说的不是南浔的旖旎风光和人文历史,而是告诉咱们,要用一颗平常心去看待偷车贼,要用一颗平常心去看代码。道儿上混的人都知道,变态的代码和偷车贼都是我们生活中不得不应对的很平常的一部分。说到这里你应该能够猜得出,接下来咱们就要看具体的代码了。
由Kconfig这张地图的分布来看,pci这块儿的代码应该分布在两个地方,drivers/pci和arch/i386/pci,使用wc –l命令分别看一下多少行,具体数字俺就不说了,反正吓死人了,所以还是紧赶的看看另一张地图Makefile,看能不能缩小一下目标,先看drivers/pci下边儿的
1 #
2 # Makefile for the PCI bus specific drivers.
3 #
4
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
7 obj-$(CONFIG_PROC_FS) += proc.o
8
9 # Build PCI Express stuff if needed
10 obj-$(CONFIG_PCIEPORTBUS) += pcie/
11
12 obj-$(CONFIG_HOTPLUG) += hotplug.o
13
14 # Build the PCI Hotplug drivers if we were asked to
15 obj-$(CONFIG_HOTPLUG_PCI) += hotplug/
16
17 # Build the PCI MSI interrupt support
18 obj-$(CONFIG_PCI_MSI) += msi.o
19
20 # Build the Hypertransport interrupt support
21 obj-$(CONFIG_HT_IRQ) += htirq.o
22
23 #
24 # Some architectures use the generic PCI setup functions
25 #
26 obj-$(CONFIG_X86) += setup-bus.o
27 obj-$(CONFIG_ALPHA) += setup-bus.o setup-irq.o
28 obj-$(CONFIG_ARM) += setup-bus.o setup-irq.o
29 obj-$(CONFIG_PARISC) += setup-bus.o
30 obj-$(CONFIG_SUPERH) += setup-bus.o setup-irq.o
31 obj-$(CONFIG_PPC32) += setup-irq.o
32 obj-$(CONFIG_PPC64) += setup-bus.o
33 obj-$(CONFIG_MIPS) += setup-bus.o setup-irq.o
34 obj-$(CONFIG_X86_VISWS) += setup-irq.o
35
36 #
37 # ACPI Related PCI FW Functions
38 #
39 obj-$(CONFIG_ACPI) += pci-acpi.o
40
41 # Cardbus & CompactPCI use setup-bus
42 obj-$(CONFIG_HOTPLUG) += setup-bus.o
43
44 ifndef CONFIG_X86
45 obj-y += syscall.o
46 endif
47
48 ifeq ($(CONFIG_PCI_DEBUG),y)
49 EXTRA_CFLAGS += -DDEBUG
50 endif
5行,一看到obj-y,后面儿那一堆文件就是必须得关注的,不容置疑。
7行,关于proc文件系统的,咱们都得忙着发展自己口袋里的经济,精力有限,proc.c文件就不用理睬了。
10行,CONFIG_PCIEPORTBUS有没有选要看你都对Kconfig做了些什么,不过不管你做了什么,这里都不打算过多的关注PCI Express,所以整个pcie/目录就可以飘过了。
12~15行,热插拔有关的,关于PCI的热插拔,走到目前这一步时,俺还不清楚要对它讲到什么程度,hotplug目录还是暂且飘过吧。
18行,如果Kconfig那里你选上了CONFIG_PCI_MSI,文件msi.c就必须得关注了。
21行,没想过要聊到Hypertransport有关的东东,htirq.c仍然飘过。
23~34行,与体系架构相关,前面已经说过,与架构有关的代码都只会看X86上的,所以只用关注setup-bus.c文件,setup-irq.c就不用理睬了。
39行,现在电源管理一般都是用的ACPI了,为了响应十一五建设节约型社会的号召,还是应该关注一下。
44行,既然要看的X86上的,syscall.c就可以忽略不计了。
现在可以来统计一下咱们接下来需要关注的的文件,有access.c,bus.c,probe.c,remove.c, pci.c,quirks.c,pci-driver.c,search.c,pci-sysfs.c,rom.c,setup-res.c,hotplug.c,msi.c,setup-bus.c,pci-acpi.c,就这些了,说多不多说少不少,使用wc –l命令统计一下,7382 total,还处于一个勉强可以接受的范围。不过不要忘了,drivers/pci下面的代码只是其中一部分,还有arch/i386/pci,再看看它里面的Makefile
1 obj-y := i386.o init.o
2
3 obj-$(CONFIG_PCI_BIOS) += pcbios.o
4 obj-$(CONFIG_PCI_MMCONFIG) += mmconfig.o direct.o mmconfig-shared.o
5 obj-$(CONFIG_PCI_DIRECT) += direct.o
6
7 pci-y := fixup.o
8 pci-$(CONFIG_ACPI) += acpi.o
9 pci-y += legacy.o irq.o
10
11 pci-$(CONFIG_X86_VISWS) := visws.o fixup.o
12 pci-$(CONFIG_X86_NUMAQ) := numa.o irq.o
13
14 obj-y += $(pci-y) common.o early.o
这个Makefile地图所描述的代码版图大小取决于pci-y是怎么定义的,这里玩了个花活儿,先在7~9这几行预先定义pci-y的值,然后在11行和12行判断CONFIG_X86_VISWS或者CONFIG_X86_NUMAQ有没有被定义,如果定义了这两个中的任何一个,就会重定义pci-y的值。一定要注意到11~12这两行里的是“ :=”,而不是“ +=”。至于CONFIG_X86_VISWS和CONFIG_X86_NUMAQ,你使用make menuconfig的时候可以在“Processor type and features”下的子菜单“Subarchitecture Type”里看到它们,仍然是道单项选择题,我这里选的是第一项“PC-compatible”,所以它们两个就与俺没啥关系了,所以visws.c、numa.c、irq.c这几个文件就可以华丽丽的飘过了。现在统计一下剩下的那些需要咱们去关注的的文件,2753 total。
两岸三地都属于一个中国,不管是drivers/pci那儿的,还是arch/i386/pci那儿的,也都只属于一个PCI子系统,本着一个中国的原则,咱们要统筹的全面的考察分析位于两个地方的代码,于是,7382和2753这两个数字加起来就是咱们接下来需要关心的部分了,这个突破了五位数的数字左看右看横看竖看都显得那么的阴森恐怖,不过实话告诉你,已经比看到Makefile之前少了很多了,人家咋说也是整个一PCI子系统,就像走在T台上的芙蓉姐姐和杨二车那姆一样,看起来恐怖但也是很有内涵的,岂能够让人三眼两眼三言两语就给看透了说透了?
那现在咱们就高瞻远瞩统筹全面的扫视一下这两个地方的代码,根据在USB Core那里得来的经验,可以推测对于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,没得选择,傻强都知道怎么走。不过你迷惘一阵儿就可以了,可别真的被绕进去了。要知道“多少事,从来急;天地转,光阴迫。一万年太久,只争朝夕。四海翻腾云水怒,五洲震荡风雷激。要看清一切入口,全无敌。”咱们要只争朝夕看清一切入口的,因此到include/linux/init.h文件里碰碰运气
111 /*
112 * A "pure" initcall has no dependencies on anything else, and purely
113 * initializes variables that couldn't be statically initialized.
114 *
115 * This only exists for built-in code, not for modules.
116 */
117 #define pure_initcall(fn) __define_initcall("0",fn,1)
118
119 #define core_initcall(fn) __define_initcall("1",fn,1)
120 #define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
121 #define postcore_initcall(fn) __define_initcall("2",fn,2)
122 #define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
123 #define arch_initcall(fn) __define_initcall("3",fn,3)
124 #define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
125 #define subsys_initcall(fn) __define_initcall("4",fn,4)
126 #define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
127 #define fs_initcall(fn) __define_initcall("5",fn,5)
128 #define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
129 #define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
130 #define device_initcall(fn) __define_initcall("6",fn,6)
131 #define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
132 #define late_initcall(fn) __define_initcall("7",fn,7)
133 #define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
134
135 #define __initcall(fn) device_initcall(fn)
这些入口有个共同的特征,它们都是用 __define_initcall宏定义的,其实,如果你是从USB Core那儿一路走过来的话,看清楚它们并不难,如果你实在忘记了或者说没见到过也没关系,不用你去蓦然回首了,俺会在这里再说一下,因为菩萨说俺今年要多做善事。
内核对子系统或者模块的初始化其实包括了两个方面,一是对各种参数的解析,一是调用上面的各种入口函数。先来说说参数的解析,这里所谓的参数包括了内核参数和模块参数,什么是内核参数?打开你的grub文件找找kernel打头儿的那些行,比如
kernel /boot/vmlinuz-2.6.18-kdb root=/dev/sda1 ro splash=silent vga=0x314
这行里边儿的root,splash,vga等等就都是内核参数,USB那边儿咱们已经见过一个类似的东东,就是nousb,PCI这边儿那?当然也有,具体的可以到Documentation/kernel-parameters.txt文件里找找
1302 pci=option[,option...][PCI] various PCI subsystem options:
1303 off [IA-32] don't probe for the PCI bus
1304 bios [IA-32] force use of PCI BIOS, don't access
1305 the hardware directly. Use this if your machine
1306 has a non-standard PCI host bridge.
1307 nobios [IA-32] disallow use of PCI BIOS, only direct
1308 hardware access methods are allowed. Use this
1309 if you experience crashes upon bootup and you
1310 suspect they are caused by the BIOS.
1311 conf1 [IA-32] Force use of PCI Configuration
1312 Mechanism 1.
1313 conf2 [IA-32] Force use of PCI Configuration
1314 Mechanism 2.
1315 nommconf [IA-32,X86_64] Disable use of MMCONFIG for PCI
1316 Configuration
1317 nomsi [MSI] If the PCI_MSI kernel config parameter is
1318 enabled, this kernel boot option can be used to
1319 disable the use of MSI interrupts system-wide.
1320 nosort [IA-32] Don't sort PCI devices according to
1321 order given by the PCI BIOS. This sorting is
1322 done to get a device order compatible with
1323 older kernels.
1324 biosirq [IA-32] Use PCI BIOS calls to get the interrupt
1325 routing table. These calls are known to be buggy
1326 on several machines and they hang the machine
1327 when used, but on other computers it's the only
1328 way to get the interrupt routing table. Try
1329 this option if the kernel is unable to allocate
1330 IRQs or discover secondary PCI buses on your
1331 motherboard.
1332 rom [IA-32] Assign address space to expansion ROMs.
1333 Use with caution as certain devices share
1334 address decoders between ROMs and other
1335 resources.
1336 irqmask=0xMMMM[IA-32] Set a bit mask of IRQs allowed to be
1337 assigned automatically to PCI devices. You can
1338 make the kernel exclude IRQs of your ISA cards
1339 this way.
1340 pirqaddr=0xAAAAA [IA-32] Specify the physical address
1341 of the PIRQ table (normally generated
1342 by the BIOS) if it is outside the
1343 F0000h-100000h range.
1344 lastbus=N [IA-32] Scan all buses thru bus #N. Can be
1345 useful if the kernel is unable to find your
1346 secondary buses and you want to tell it
1347 explicitly which ones they are.
1348 assign-busses [IA-32] Always assign all PCI bus
1349 numbers ourselves, overriding
1350 whatever the firmware may have done.
1351 usepirqmask [IA-32] Honor the possible IRQ mask stored
1352 in the BIOS $PIR table. This is needed on
1353 some systems with broken BIOSes, notably
1354 some HP Pavilion N5400 and Omnibook XE3
1355 notebooks. This will have no effect if ACPI
1356 IRQ routing is enabled.
1357 noacpi [IA-32] Do not use ACPI for IRQ routing
1358 or for PCI scanning.
1359 routeirq Do IRQ routing for all PCI devices.
1360 This is normally done in pci_enable_device(),
1361 so this option is a temporary workaround
1362 for broken drivers that don't call it.
1363 firmware [ARM] Do not re-enumerate the bus but instead
1364 just use the configuration from the
1365 bootloader. This is currently used on
1366 IXP2000 systems where the bus has to be
1367 configured a certain way for adjunct CPUs.
1368 noearly [X86] Don't do any early type 1 scanning.
1369 This might help on some broken boards which
1370 machine check when some devices' config space
1371 is read. But various workarounds are disabled
1372 and some IOMMU drivers will not work.
1373 bfsort Sort PCI devices into breadth-first order.
1374 This sorting is done to get a device
1375 order compatible with older (<= 2.4) kernels.
1376 nobfsort Don't sort PCI devices into breadth-first order.
1377 cbiosize=nn[KMG] The fixed amount of bus space which is
1378 reserved for the CardBus bridge's IO window.
1379 The default value is 256 bytes.
1380 cbmemsize=nn[KMG] The fixed amount of bus space which is
1381 reserved for the CardBus bridge's memory
1382 window. The default value is 64 megabytes.
这么将近100行的东东都是PCI这边儿可以设置的参数选项,与之相比,USB那里也忒单纯了。咱们地球上都在搞军备竞争,内核也去跟风搞什么代码竞争,看谁的代码长谁的代码复杂,现在连内核参数也搞竞争了,看谁的多。当然这种竞争与keso说的中国互联网行业的“抄袭盛行,低水平竞争,恶性竞争。”不一样。
这么多选项有的前边儿已经提到了,没有提到的俺也不打算在这里讲,日后碰到再说了。明白了内核参数,再看看模块参数。其实俺所说的模块参数就是你使用insmod或者modprobe加载模块时在命令行里指定的参数,它们在驱动里使用module_param这样的宏来声明。不过千万要明白一点的是,俺意思并不是说内核参数就不能使用module_param这样的宏去定义了,USB那边儿的nousb就是使用__module_param_call宏定义的。
另外还要明白一点的是,当模块被编译进内核的时候,它的那些模块参数就需要在grub文件的kernel行里来指定了,比如,
modprobe usbcore autosuspend=2
放到kernel行里就是
usbcore.autosuspend=2
当然在drivers/usb/usb.c里autosuspend的默认值就是2,所以这里再去指定为2纯粹就是春节申遗多此一举。
你如果很好奇想知道一个模块都有哪些参数可以使用,敲命令“ modinfo -p ${modulename} ”就可以了。对于已经加载到内核里的模块,它们的参数都会陈列在 /sys/module/${modulename}/parameters/ 目录下面儿等着你去检阅,当然你也可以使用 "echo -n ${value} > /sys/module/${modulename}/parameters/${parm}" 这样的命令去修改它们。
现在看看内核是怎么进行参数解析的,这主要依靠一个名叫parse_args的函数,它的细节不用关心,只需要明白在内核启动时,或者加载模块结协模块命令行参数时它都会被调用就可以了。如果你是一个有心人,去扫了眼init/main.c文件里的start_kernel函数,你就会发现,内核启动时调用了一次parse_args还觉得不过瘾,又调用了第二次
parse_args的第一次调用就在parse_early_param函数里面,为什么会出现两次调用parse_args的情况?怪只怪内核参数又分成了两种,一种是普普通通的,一种是有特权,需要在普通选项之前处理的,就像银行的VIP客户,有专门开辟的窗口接待,不管咱们排队排的多长,他们一到就会优先服务,即使没有VIP,那个窗口儿就是空着也不会给你用,就让你在那儿等。既然这个世界上人有两种,有特权的和没特权的,那内核参数同样也就有两种,有特权的和没特权的。
特权谁都想有,但是有了特权又都怕人说自己有特权,就像哈医大二院的纪委书记在接受央视的采访“老人住院费550万元”时评价自己单位“我们就是一所人民医院……就是一所贫下中农的医院,从来不用特权去索取自己身外的任何利益……我们不但没有多收钱还少收了。”那样,人就是这么的复杂和奇怪。内核参数就不一样了,有特权也是光明正大的用,从不藏着掖着,直接使用early_param宏去声明,让你一眼就看出它是有特权的,而普通的内核参数则是使用module_param系列的宏声明的。使用early_param声明的那些参数就会首先由parse_early_param去解析,然后才轮得着其它的参数。这里引用一下《Understanding Linux Network Internals》里的图7-2
Linux那些事儿 之 我是PCI(2)PCI全接触

关于这张图,你只要注意到__setup_start__setup_end__start___param__stop___param这几个东东就可以了,它们很明确的告诉我们特权参数和普通参数在内存里存放的位置是不一样的,特权参数放在__setup_start__setup_end之间的节里,普通参数放在__start___param__stop___param之间的节里。

PCI里边儿也出现了一个early_param,在drivers/pci/pci.c里面

early_param怎么定义的你可以去include/linux/init.h里面看,俺这里就不说了,反正这行的意思就是发现你grub文件的kernel行里有“pci=”这样的东东的时候,就会调用函数pci_setup进行处理,怎么处理?这都是以后的事儿了。

参数解析完之后,再经过一个漫长而曲折的过程,就会调用到各种各样的xxx_initcall入口函数,这些函数的调用不是随便的,而是按照一定顺序的,这个顺序就取决于__define_initcall宏。__define_initcall宏用来将指定的函数指针放到.initcall.init节里,这都是讲USB Core的时候说过的,再扼要重述一遍。

内核可执行文件由许多链接在一起的对象文件组成。对象文件有许多节,如文本、数据、init 数据、bass 等等。这些对象文件都是由一个称为链接器脚本的文件链接并装入的。这个链接器脚本的功能是将输入对象文件的各节映射到输出文件中;换句话说,它将所有输入对象文件都链接到单一的可执行文件中,将该可执行文件的各节装入到指定地址处。 vmlinux.lds是存在于 arch/<target>/ 目录中的内核链接器脚本,它负责链接内核的各个节并将它们装入内存中特定偏移量处。在vmlinux.lds文件里查找initcall.init就可以看到下面的内容

__inicall_start = .;

.initcall.init : AT(ADDR(.initcall.init) – 0xC0000000) {

*(.initcall1.init)

*(.initcall2.init)

*(.initcall3.init)

*(.initcall4.init)

*(.initcall5.init)

*(.initcall6.init)

*(.initcall7.init)

}

__initcall_end = .;

这就告诉我们.initcall.init节又分成了7个字节,而xxx_initcall入口函数指针具体放在哪一个子节里边儿是由xxx_initcall的定义中,__define_initcall宏的参数决定的,比如core_initcall将函数指针放在.initcall1.init子节,device_initcall将函数指针放在了.initcall6.init子节等等。各个子节的顺序是确定的,即先调用.initcall1.init中的函数指针再调用.initcall2.init中的函数指针,等等。不同的入口函数被放在不同的子节中,因此也就决定了它们的调用顺序。

关于内核里初始化有关的一些内存节,可以再参考下《Understanding Linux Network Internals》的图7-3

Linux那些事儿 之 我是PCI(2)PCI全接触

现在你再回头去看看前面那张描述PCI里边儿各种xxx_initcall入口的表,应该不会再觉得迷惘了,对整个PCI子系统的运作流程也大致有个谱了。

你可能感兴趣的:(linux)