如果想让内核启动过程中自动加载某个模块该怎么做呢?最容易想到的方法就是到/etc/init.d/中添加一个启动脚本,然后在/etc/rcN.d/目录下创建一个符号链接,这个链接的名字以S开头,这内核启动时,就会自动运行这个脚本了,这样就可以在脚本中使用modprobe来实现自动加载。但是我们发现,内核中加载了许多硬件设备的驱动,而搜索/etc目录,却没有发现任何脚本负责加载这些硬件设备驱动程序的模块。那么这些模块又是如何被加载的呢?
ID, SubVendor ID的设备提供服务。以PCI设备为例,它是通过一个pci_device_id的数据结构来实现这个功能的。例如:RTL8139的pci_device_id定义为: static struct pci_device_id rtl8139_pci_tbl[] = { 上面的信息说明,凡是Verdon ID为0x10EC, Device ID为0x8139, 0x8138的PCI设备(SubVendor ID和SubDeviceID为PCI_ANY_ID,表示不限制。),都可以使用这个驱动程序(8139too)。
alias pci:v000010ECd00008138sv*sd*bc*sc*i* 8139toov后面的000010EC说明其Vendor ID为10EC,d后面的00008138说明Device ID为8139,而sv,和sd为SubVendor ID和SubDevice ID,后面的星号表示任意匹配。 另外在/lib/modules/uname-r/modules.dep文件中还保存这模块之间的依赖关系,其内容如下: (这里省去了路径信息。)
options,这里面的各种总线,你只能够选择Y或N,而不能选择M.),并且为每一个设备建立一个设备对象。每一个总线对象有一个kset对象,每一个设备对象嵌入了一个kobject对象,kobject连接在kset对象上,这样总线和总线之间,总线和设备设备之间就组织成一颗树状结构。当总线驱动程序为扫描到的设备建立设备对象时,会初始化kobject对象,并把它连接到设备树中,同时会调用kobject_uevent()把这个(添加新设备的)事件,以及相关信息(包括设备的VendorID,DeviceID等信息。)通过netlink发送到用户态中。在用户态的udevd检测到这个事件,就可以根据这些信息,打开/lib/modules/uname-r/modules.alias文件,根据 alias pci:v000010ECd00008138sv*sd*bc*sc*i* 8139too得知这个新扫描到的设备驱动模块为8139too。于是modprobe就知道要加载8139too这个模块了,同时modprobe根据 modules.dep文件发现,8139too依赖于mii.ko,如果mii.ko没有加载,modprobe就先加载mii.ko,接着再加载 8139too.ko。 试验在你的shell中,运行: # ps aux | grep udevd 我们得到udevd的进程ID为25063,现在结束这个进程: # kill -9 25063然后跟踪udevd,在shell中运行: # strace -f /sbin/udevd --daemon这时,我们看到udevd的输出如下: ......我们发现udevd在这里被阻塞在select()函数中。 select函数原型如下: int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); select函数的作用是:如果readfds中的任何一个文件有数据可读,或者witefds中的任何一个文件可以写入,或者exceptfds中的任何一个文件出现异常时,就返回。否则阻塞当前进程,直到上诉条件满足,或者因阻塞时间超过了timeout指定的时间,当前进程被唤醒,select返回。 所以,在这里udevd等待3,4,5,6这几个文件有数据可读,才会被唤醒。现在,到shell中运行: # ps aux | grep udevd udevd的进程id为27617,现在我们来看看select等待的几个文件: # cd /proc/27615/fd 由于不方便在运行中插入一块8139的网卡,因此现在我们以一个U盘来做试验,当你插入一个U盘后,你将会看到strace的输出,从它的输出可以看到 udevd在select返回后,调用了modprobe加载驱动模块,并调用了sys_mknod,在dev目录下建立了相应的节点。 execve("/sbin/modprobe", ["/sbin/modprobe", "-Q", "usb:v05ACp1301d0100dc00dsc00dp00"...] 这里modprobe的参数"usb:v05AC..."对应modules.alias中的某个模块。 可以通过udevmonitor来查看内核通过netlink发送给udevd的消息,在shell中运行: # udevmonitor --env然后再插入U盘,就会看到相关的发送给udevd的消息。 == 内核处理过程 ==:
int pci_bus_add_device(struct pci_dev *dev) device_add()代码如下: int device_add(struct device *dev) device_add()在准备好相关数据结构后,会调用kobject_uevent(),把这个消息发送到用户空间的udevd。 int kobject_uevent(struct kobject *kobj, enum kobject_action action) 思考现在我们知道/dev目录下的设备文件是由 udevd负责建立的,但是在内核启动过程中,需要mount一个根目录,通常我们的根目录是在硬盘上,比如:/dev/sda1,但是硬盘对应的驱动程序没有加载前,/dev/sda1是不存在的, 如果没有/dev/sda1,就不能通过mount /dev/sda1 /来挂载根目录。另一方面udevd是一个可执行文件,如果连硬盘驱动程序到没有加载,根目录都不存在,udevd就不能运行。如果udevd不能运行,那么就不会自动加载磁盘驱动程序,也就不能自动创建/dev/sda1。这不是死锁了吗?那么你的Linux是怎么启动的呢? 参考资料: Essential Linux Device Drivers |
|
来自: clover_xian > 《我的图书馆》