2.2.3 内核模块
整个内核并不需要同时装入内存。应该确认,为保证系统能够正常运行,一些特定的内核
必须总是驻留在内存中,例如,进程调度代码就必须常驻内存。但是内核其他部分,例如
大部分的设备驱动就应该仅在内核需要的时候才装载,而在其他情况下则无需占用内存。

举例来说,只有在内核真正和CD-ROM通讯时才需要使用完成内核与CD-ROM通讯的设备驱动
程序,因此内核可以被设置为在和设备通讯之前才装载相应代码。内核完成和设备的通讯
之后可以将这部分代码丢弃。也就是说,一旦代码不再需要,就可以从内存中移走。系统
运行过程中可以增减的这部分内核称为内核模块。
内核模块的优点是可以简化内核自身的开发。假设你购买了一个新的高速CD-ROM驱动器,
但是现有的CD-ROM驱动程序并不支持该设备。你自然就希望增加对这种高速模式的支持以
提高系统光驱设备的性能。如果作为内核模块来编译驱动程序,你的工作将会方便得多:
编译驱动程序、加载到内核、测试、卸载驱动程序、修改驱动程序、再次加载驱动程序到
内核、测试,如此周而复始。如果你的驱动程序是直接编辑在内核中的,那么你就必须重
新编译整个内核并且在每次修改驱动程序之后重新启动机器。这样慢得很多。
自然,你也必须留意内核模块。对于指明其他内核模块在磁盘上的驻留位置的那些模块,
一定不能从内存中卸载,否则,内核将只能通过访问磁盘来装载处理磁盘访问的内核模块
,这是不可能实现的。这也是我们要选择把部分内核作为模块编译还是直接编译进内核使
其常驻内存的又一个原因。知道自己系统的设置方式,因而也就可以选择正确使用的方式
(如果为了确保安全,可以简单的忽略内核模块系统的优点,而把所有的内容都编译到内
核里面)。
内核模块会带来一些速度上的损失,这是因为一些必需的代码现在并不在RAM中,必需要
从磁盘读入。但是整个系统的性能通常会有所提高,这主要是因为通过丢弃暂时不使用的
模块可以释放出额外的RAM供应用程序使用。如果这部分内存被内核所占用,应用程序将
只能更加频繁地进行磁盘交换,而这种磁盘交换会显著地降低应用程序的性能(磁盘交换
将在第8章中讨论)。
内核模块还会带来因复杂度的增加所造成的开销,这是因为在系统运行的过程中,移进移
出部分内核需要额外的代码。然而,复杂度的开销是可以管理的。通过使用外部程序来代
理一些必需的工作还可以更进一步降低复杂度的开销(更为确切的说法是,这样做不是减
少了复杂度的开销,而是把复杂度的开销重新分配了一下)。这是对内核模块原理的一个
小小的扩展:即使是内核的支持模块,对于内核来说也只是外部的、部分可用的,只有在
需要的时候才被装入内存。
通常用于这种目的程序称为modprobe。有关的modprobe代码超出了本书的范围,但是在
Linux的每个发行版本中都包含有它。本节的剩余部分将讨论同modprobe协同工作,以装
载内核模块的内核代码。
1. request_module
24432:作为函数说明之前的注释,request_module是一个函数。内核的其他模块在需要
装载其他内核模块的时候,都必须调用这个函数。就像内核处理其他工作一样,这种调用
也是为当前运行的进程进行的。从进程的角度来看,这种调用的请求通常是隐含的—正在
执行进程其他请求的内核可能会发现,必须调入一个模块才能够完成该请求。例如,请参
见10070行,这里是一些将在第7章中讨论的代码。
24446:以内核中的一个独立进程的形式执行exec_modprobe函数(24384行)。这并不能
只通过函数的简单调用实现,因为exec_modprobe要继续调用exec来执行一个程序。因此
,对函数exec_modprobe的简单调用将永远不会有返回。
这和使用fork以准备exec调用十分类似,你可以认为kernel_thread对内核来说就是较低
版本的fork,虽然两者有很大不同。fork是从指定函数开始执行新的进程,而不是从调用
者的当前位置开始运行。正如fork一样,kernel_thread返回的值是新进程的进程号。
24448:和fork一样,从kernel_thread返回的负值表示内部错误。
24455:正如函数中论述的一样,大部分的信号将因当前进程而被暂时阻塞。
24462:等待exec_modprobe执行完毕,同时指出所需要的模块是已经成功装入内存,还是
装载失败了。
24465:结束运行,恢复信号。如果exec_modprobe返回错误代码,则打印错误消息。
2. exec_modprobe
24384:exec_modprobe运行为内核增加内核模块的程序。这里的模块名是一个void*的指
针,而不是char*的指针。原因简单说来就是kernel_thread 产生的函数通常都使用void*
指针参数。
24386:设置modprobe的参数列表和环境。modprobe_path(24363行)用来定位modprobe
程序的位置。它可以通过内核的sysctl特性来修改,这一点将在第11章中介绍(参见
30388行)。这意味着root可以动态选择不同于/sbin/modprobe的程序来运行,以适应当
modprobe被安装到其他地方或者使用修改过的modprobe替换掉了原有的modprobe之类的情
况。
24400:(正如代码中描述的一样)出于安全性考虑,丢弃所有挂起的信号和信号句柄(
handl-ers)。这里最重要的部分是对flush_signal_handlers的调用(28041行),它使
用内核默认的信号句柄代替所有用户定义的信号句柄。如果在此时有信号被传送到内核,
它将获得默认响应—通常是忽略信号或杀死进程。但是不管怎样都不会引起安全风险。由
于该函数从触发它的进程中分离出来(如前所述),所以,不管原始进程在此处是否改变
其原来分配的信号,句柄都不会产生任何影响。
24405:关闭调用进程打开的所有文件。最重要的是,这意味着modprobe程序不再从调用
进程中继承标准输入输出和标准错误。这很有可能会引起安全漏洞(这可能是在替代
modprobe的程序中引起的问题,但是modprobe本身实际上并不关心这个差异)。
24413:modprobe程序作为root运行,它拥有root所拥有的所有权限。和整个内核中其他
地方一样,请注意root使用用户ID号0的假定在这里已经被写入程序。用户ID号和权能系
统(capability system,在接下来的几行中会用到)将在第7章中介绍。
24421:试图执行modprobe程序。如果尝试失败,内核将使用printk打印错误消息并返回
错误代码。这里是可能产生printk的缓冲器过载的地点之一。module_name的长度并没有
明确限制,就我们对该调用的看法而言,它可能长达一百万个字符。为防止printk缓冲器
过载,你必需遍历所有对于该函数的调用(实际上是对request_module的调用),以保证
每个调用者使用足够短的、不会为printk造成麻烦的模块名。
24427:当execve成功执行时,它不会返回任何结果,因此本处是不可能执行到的。但是
编译器却并不知道这一点,因此,此处使用了return语句以保证gcc不出错。
对于内核的进一步讨论将超出本章的既定范围,因此在这个问题上我们到此为止。然而本
书中也包括了其他必需的内核代码。在读完第4章和第5章之后,也许你会希望再次仔细研
读一下这部分内容。有关这个问题的两个文件是include/linux/module.h(从15529行开
始)和/kernel/module.c(从24476行开始)。和sys_create_module(24586行)、
sys_init_module(24637行)、sys_delete_module(24860行)和sys_query_module(
25148行)四个函数需要特别注意一样,struct module(15581行)也要特别引起注意。
这些函数实现了modprobe及insmod、lsmod和rmmod所使用的系统调用,以完成模块的装载
、定位和卸载。
内核触发直接回调内核程序的现象看起来很令人奇怪。但是,实际上进行的工作不止于此
。例如,modprobe必须实际访问磁盘以搜寻要装载的模块。而且更为重要的一点是,这种
方法赋予root对内核模块系统更多的控制能力。这主要是因为root也可以运行modprobe及
相关程序。因此,root既可以手工装载、查询、卸载模块,也可以由内核自动完成。