为搞清楚如何在系统启动时自动加载模块,搜索了好久,网上有很多人提出这个问题,但都没有正确的答案,无论是中文社区还是英文社区,大家的回答都没有讲到点子上,无非是围绕 modprobe.conf、modprobe讲来讲去的,要不就是针对特定问题尝试不同的方法。有的还建议把modprobe modulename写入rc.local,却不曾想,rc.local的执行被放在整个启动顺序的很后面,而启动init.d下面定义的服务却在 rc.local前面,那么如果某个服务要用这个模块,就不行了。
在测试LVS时,因为我的Fedora7的Kernel(2.6.21-1)缺省没有加载ip_vs模块,而内核中已经包含编译好的IPVS相关的模块了,放在:/lib/modules/2.6.21- 1.3194.fc7/kernel/net/ipv4/ipvs/下面,有:
/lib/modules/2.6.21-1.3194.fc7/kernel/net/ipv4/ipvs/ip_vs.ko
/lib/modules/2.6.21-1.3194.fc7/kernel/net/ipv4/ipvs/ip_vs_dh.ko
/lib/modules/2.6.21-1.3194.fc7/kernel/net/ipv4/ipvs/ip_vs_ftp.ko
/lib/modules/2.6.21-1.3194.fc7/kernel/net/ipv4/ipvs/ip_vs_lblc.ko
/lib/modules/2.6.21-1.3194.fc7/kernel/net/ipv4/ipvs/ip_vs_lblcr.ko
/lib/modules/2.6.21-1.3194.fc7/kernel/net/ipv4/ipvs/ip_vs_lc.ko
/lib/modules/2.6.21-1.3194.fc7/kernel/net/ipv4/ipvs/ip_vs_nq.ko
/lib/modules/2.6.21-1.3194.fc7/kernel/net/ipv4/ipvs/ip_vs_rr.ko
/lib/modules/2.6.21-1.3194.fc7/kernel/net/ipv4/ipvs/ip_vs_sed.ko
/lib/modules/2.6.21-1.3194.fc7/kernel/net/ipv4/ipvs/ip_vs_sh.ko
/lib/modules/2.6.21-1.3194.fc7/kernel/net/ipv4/ipvs/ip_vs_wlc.ko
/lib/modules/2.6.21-1.3194.fc7/kernel/net/ipv4/ipvs/ip_vs_wrr.ko
其中ip_vs.ko是IPVS的基本模块,不加载IPVS就不能工作(运行ipvsadm会报错的),而其他的都是IPVS的调度算法或特定协议的辅助模块,需要时则须加载。
如果系统运行时手动加载则需:modprobeip_vs 和modprobe ip_vs_sh等。
要了解如何在系统启动时自动加载模块(Automaticallyload kernel modules),就得先了解系统是如阿启动的,启动的过程中按什么顺序做了什么,怎么做的,这些启动操作都有那些文件和脚本控制。由于Google和 Baidu出来的东西都解决不了问题,而且man modprobe和man modprobe.conf发现并不是需要修改的文件。
于是温习鸟哥的[url]http://linux.vbird.org/[/url] “开机关机流程与Loader”:
1. 整个开机流程是
(1) 载入BIOS的硬件信息,并取得第一个开机装置的代号
(2)读取第一个开机装置的MBR的boot Loader(grub)的开机信息
(3)载入OS Kernel信息,解压Kernel,尝试驱动硬件
(4) Kernel执行init程序并获得run-lebel信息(如3或5)
(5) init执行 /etc/rc.d/rc.sysinit
(6)启动内核外挂模块 (/etc/modprobe.conf)
(7) init执行run-level的各种Scripts,启动服务
(8) init执行 /etc/rc.d/rc.local
(9)执行 /bin/login,等待用户Login
(10)Login后进入Shell
看来正确的方式是把需要加载的模块放在(5)或(6),但正如网络上很多人的尝试,修改modprobe.conf都没有成功(例如在modprobe.conf中增加installip_vs...)。于是我修改了/etc/rc.d/rc.sysinit就成功加载了。
初步尝试在rc.sysinit最后增加 modprobe.conf ip_vs,重启后lsmod | grep ip_vs,发现成功自动加载了。
于是仿效rc.sysinit中其他模块的加载方法,扩展改脚本文件,在最后增加下来一段:
# load LVS IPVS modules
if [ -d /lib/modules/$unamer/kernel/net/ipv4/ipvs ]; then
for module in /lib/modules/$unamer/kernel/net/ipv4/ipvs/* ; do
module=${module##*/}
module=${module%.ko}
modprobe $module >/dev/null 2>&1
done
fi
就把/lib/modules/2.6.21-1.3194.fc7/kernel/net/ipv4/ipvs/下的所有模块都自动加载了。其中:
if语句检查ipvs模块的目录是否存在
for循环遍历该目录下面的所有文件
module=${module##*/} :其中##表示从前面删除字符,*/表示删除到最后一个/,如果一个#就表示只删除到第一个/。如果变量后面接##,表示在##后面的字符串取最长的(一直到最后面),如果接#,表示取最小的一段。
module=${module%.ko}:表示从后面删除.ko。如果变量后面接%%,表示在%%后面的字符串取最长的(一直到最前面),如果接%,表示取最小的一段。
这样多module的两次修改就得到了模块名,就是文件名不带路径和.ko后缀。
modprobe $module >/dev/null 2>&1:加载模块,输出都指向空设备
这样重启后lsmod | grep ip_vs就会得到:
ip_vs_wrr 6977 0
ip_vs_wlc 6081 0
ip_vs_sh 6593 0
ip_vs_sed 6081 0
ip_vs_rr 6081 0
ip_vs_nq 5953 0
ip_vs_lc 5953 0
ip_vs_lblcr 10565 0
ip_vs_lblc 9797 0
ip_vs_ftp 10053 0
ip_vs_dh 6593 0
ip_vs 79425 22 ip_vs_wrr,ip_vs_wlc,ip_vs_sh,ip_vs_sed,ip_vs_rr,ip_vs_nq,ip_vs_lc,ip_vs_lblcr,ip_vs_lblc,ip_vs_ftp,ip_vs_dh
创�P6���� �� ang=EN-US>map文件,例如modules.dep,modules.isapnpmap,modules.pcimap,modules.alias这些文件将会被hotplug 用到。
OPTIONS:
-a --all Probe all modules. This option is enabled by default if no
file namesare given in the command-line.
检查所有的模块,这个命令是默认的如果你没有指定模块名字的话。
-A --quick This option scans to see if any modules are newerthan the
modules.dep file before any work is done%3
再来看看modprobe命令:
modprobe 命令是根据depmod -a的输出/lib/modules/version/modules.dep来加载全部的所需要模块。可以通过modprobe -l来显示可以当前可以加载的模块。modprobe 在挂载模块是不用指定模块文件的路径,也不用带文件的后缀.o 或.ko, 而insmod 需要的是模块的所在目录的绝对路径,并且一定要带有模块文件名后缀的(modulefile.o 或modulesfile.ko )。 insmod比较重要的用途是用来测试模块的正确性,加载一般都是依靠modprobe。
用法:modprobe xxx.ko #加载某个模块
modprobe -r xxx.ko #卸载某个模块
lsmod:
lsmod 显示当前加载的所有模块,相当于cat /proc/modules,假设你没有设定开机加载某个模块,比如ntfs,那么开机后执行lsmod,列表里不会有ntfs这个模块的,这时你再执行 mount -t ntfs xxx后,执行lsmod后列表里就会有ntfs这个模块了。
还要注意的是lsmod显示的是模块名,而不是别名(alias)。
二:安装完成以后,我们就可以加载模块了:
和linux中加载模块有关的几个命令分别如下:
depmod, modprobe, lsmod
先来看看depmod命令:
depmod是一个用来产生modules.dep和map文件的程序。在modules.dep文件中空白行和以'#'开头的行将被忽略.depmod通过读取/lib /modules/version目录下的每一个模块来创建一个记录模块相依性的列表。这个列表就是/lib/modules/version目录下的 modules.dep。depmod也会在/lib/modules/version目录下创建许多map文件,例如modules.dep,modules.isapnpmap,modules.pcimap,modules.alias这些文件将会被hotplug 用到。
OPTIONS:
-a --all Probe all modules. This option is enabled by default if no
file namesare given in the command-line.
检查所有的模块,这个命令是默认的如果你没有指定模块名字的话。
-A --quick This option scans to see if any modules are newerthan the
modules.dep file before any work is done%3
再来看看modprobe命令:
modprobe 命令是根据depmod -a的输出/lib/modules/version/modules.dep来加载全部的所需要模块。可以通过modprobe -l来显示可以当前可以加载的模块。modprobe 在挂载模块是不用指定模块文件的路径,也不用带文件的后缀.o 或.ko, 而insmod 需要的是模块的所在目录的绝对路径,并且一定要带有模块文件名后缀的(modulefile.o 或modulesfile.ko )。 insmod比较重要的用途是用来测试模块的正确性,加载一般都是依靠modprobe。
用法:modprobe xxx.ko #加载某个模块
modprobe -r xxx.ko #卸载某个模块
lsmod:
lsmod 显示当前加载的所有模块,相当于cat /proc/modules,假设你没有设定开机加载某个模块,比如ntfs,那么开机后执行lsmod,列表里不会有ntfs这个模块的,这时你再执行 mount -t ntfs xxx后,执行lsmod后列表里就会有ntfs这个模块了。
还要注意的是lsmod显示的是模块名,而不是别名(alias)。
三、在内核中有一个“Automatic kernelmodule loading"功能被编译到了内核中。当用户尝试打开某类型的文件时,内核会根据需要尝试加载相应的模块。我们来看看驱动程序自动加载是怎么实现的:
每一个设备都有Verdon ID, DeviceID, SubVendor ID等信息。而每一个设备驱动程序,必须说明自己能够为哪些Verdon ID,Deviece
ID, SubVendor ID的设备提供服务。以PCI设备为例,它是通过一个pci_device_id的数据结构来实现这个功能的。例如:RTL8139的pci_device_id定义为:
static struct pci_device_idrtl8139_pci_tbl[] = {
{0x10ec,0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x10ec,0x8138, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
......
}
在模块安装的时候,depmod会根据模块中的rtl8139_pci_tbl的信息,生成下面的信息,保存到/lib/modules/uname-r/modules.alias文件中,其内容如下:
alias pci:v000010ECd00008138sv*sd*bc*sc*i*8139too
aliaspci:v000010ECd00008139sv*sd*bc*sc*i* 8139too
......
另外在/lib/modules/uname-r/modules.dep文件中还保存这模块之间的依赖关系,其内容如下:
(这里省去了路径信息。)
8139too.ko:mii.ko
在内核启动过程中,总线驱动程序会会总线协议进行总线枚举(总线驱动程序总是集成在内核之中,不能够按模块方式加载,你可以通过make menuconfig进入Bus
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
# kill -9 25063 然后跟踪udevd,在shell中运行:
# strace -f /sbin/udevd --daemon
这时,我们看到udevd的输出如下:
......
close(8) = 0
munmap(0xb7f8c000, 4096) = 0
select(7, [3 4 5 6], NULL, NULL, NULL我们发现udevd在这里被阻塞在select()函数中。
select函数原型如下:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,struct timeval *timeout);
第一个参数:nfds表示最大的文件描述符号,这里为7(明明是6 ?)。
第二个参数:readfds为读文件描述符集合,这里为3,4,5,6.
第三个参数:writefds为写文件描述符集合,这里为NULL。
第四个参数:exceptfds为异常文件描述符集合,这里为NULL。
第五个参数:timeout指定超时时间,这里为NULL。select函数的作用是:如果readfds中的任何一个文件有数据可读,或者witefds中的任何一个文件可以写入,或者exceptfds中的任
何一个文件出现异常时,就返回。否则阻塞当前进程,直到上诉条件满足,或者因阻塞时间超过了timeout指定的时间,当前进程被唤醒,select返
回。 所以,在这里udevd等待3,4,5,6这几个文件有数据可读,才会被唤醒。现在,到shell中运行:
# ps aux | grep udevd
root 27615 ...... strace -o /tmp/udevd.debug -f/sbin/udevd --daemon
root 27617 ...... /sbin/udevd --daemon
udevd的进程id为27617,现在我们来看看select等待的几个文件:
# cd /proc/27615/fd
# ls -l
udevd的标准输入,标准输出,标准错误全部为/dev/null.
0 -> /dev/null
1 -> /dev/null
2 -> /dev/null
udevd在下面这几个文件上等待。
3 -> /inotify
4 -> socket:[331468]
5 -> socket:[331469]
6 -> pipe:[331470]
7 -> pipe:[331470]由于不方便在运行中插入一块8139的网卡,因此现在我们以一个U盘来做试验,当你插入一个U盘后,你将会看到strace的输出,从它的输出可以看到
udevd在select返回后,调用了modprobe加载驱动模块,并调用了sys_mknod,在dev目录下建立了相应的节点。
execve("/sbin/modprobe", ["/sbin/modprobe", "-Q","usb:v05ACp1301d0100dc00dsc00dp00"...]
......
mknod("/dev/sdb", S_IFBLK|0660, makedev(8, 16)) = 0
......这里modprobe的参数"usb:v05AC..."对应modules.alias中的某个模块。
可以通过udevmonitor来查看内核通过netlink发送给udevd的消息,在shell中运行:
# udevmonitor --env
然后再插入U盘,就会看到相关的发送给udevd的消息。
五、内核模块开机自动挂载:
对于开机自动挂载模块,在RedHat系统里,网上说在内核启动的过程中,init执行/etc/rc.d/rc.sysinit后,启动内核外挂模块 时会读取/etc/modprobe.conf这个文件。在2.4的内核中, 只要直接修改/etc/modprobe.conf加入install xxx即可。2.6内核则需修改/etc/rc.d/rc.sysinit文件。