📄 (ldd) ch11-kerneld和高级模块化(转载).txt
字号:
(LDD) Ch11-kerneld和高级模块化(转载)
第11章 kerneld和高级模块化
在本书的第二部分,我们要讨论的话题比到目前为止我们所接触过的话题都更为
高级。我们将再次从模块化讲起,第二章“编写和运行模块”中对模块化的介绍只是其
中的一部分;modules包(它们的最新版本被称作modutils)支持一些更高级的特性,它们
比前面讨论的安装和运行一个基本的驱动程序所需的特性要更为复杂。
本章将讨论kerneld程序,模块中的版本支持(一种便利性,它使你在升级内核时
不必重新编译你的各个模块)以及在卸载和重新装载一个模块时对数据持久性的支持。最
后这项功能只有2.0.0版或更新版本的modules包才提供。
按需加载模块
按需加载模块
为了方便用户加载 托对啬 块,并且避免把不再使用的驱动程序继续保留在核心
中浪费内核存储空间,Linux提供了对模块的自动加载和卸载的支持。(在1.2版以前不提
供这种支持)要利用这个特性,在编译内核前进行的配置中你必须打开对kerneld的支持
。需要时可以请求附加模块的能力对于使用堆叠式模块的驱动程序尤其有用。
隐藏在kerneld之后的思想很简单,但却很有效。当内核试图访问不可用资源时,它会通
知用户程序而不仅仅是返回一个错误。如果守护进程成功地获得该资源,内核将继续工
作;否则它将返回错误。实际上申请任何一种资源的时候都可以使用这种办法:诸如字
符设备和块设备驱动程序,行律和网络协议等等。
用于获得按需装载能力的机制是使用一个修改过的消息队列,利用它在内核空间和用户
空间之间相互传递文本信息。要让按需装载能正确工作,必须正确地配置用户级守护进
程,并且内核代码必须做好准备,等待所需的模块。
可以从按需装载中受益的驱动程序的一个典型例子是通用帧捕获者(frame-grabber)驱动
程序。它能支持几种不同的外设,但却表现出相同的外部行为。发布中将包括它所支持
程序。它能支持几种不同的外设,但却表现出相同的外部行为。发布中将包括它所支持
的所有设备卡的代码,但是在运行时只有正在被使用的那个特定设备卡的代码才真正需
要。这样开发者就能够把具体实现划分为一个定义软件接口的通用模块和一系列用于低
层操作的与硬件相关的模块。在通用模块检测到系统中安装的捕获者的类型后,它就能
够为该捕获者申请正确的模块。
用户级方面
kerneld程序生存在用户空间,负责处理来自内核的对新模块的请求。它通过创建自己的
消息队列和内核相连,然后进入睡眠,等待请求。
请求一个模块时,守护进程从内核中接收一个字符串并试图解析它。这个字符串可能是
下面两种形式之一:
l 目标文件的名字,就象insmod命令的典型参数一样。floppy是这种名字的一个
例子;在这种情况下,守护进程将查找文件floppy.o并装载它。
l 更一般的标志符,比如block-major-2,它用来指明主设备号为2的块设备-也
l 更一般的标志符,比如block-major-2,它用来指明主设备号为2的块设备-也
就是软盘驱动程序。这种类型的字符串是最常见的,因为内核通常只知道资源的数字标
志符。例如,当你试图使用一个块设备时,内核只知道它的主设备号;仅仅就为了能通
过名字来请求每个块设备而为它们实现各自不同的钩子函数很浪费。
显然,后一种情况时,必须有某种方法把模块的"id"映射成它的实际名字。这种关联并
不由kerneld本身完成而是由kerneld调用modprobe来完成。在depmod命令的帮助下,由m
odprobe来处理模块装载的细节;kerneld本身只负责与内核的通讯并生成外部任务。所
有这些程序都在modules包中一起发布。depmod是一个能产生类似Makefile那样的模块依
赖信息的工具,而modprobe是能替代insmod用来正确装载模块堆栈的程序。例如ppp模块
堆叠在slhc模块(Serial Line Header Compression)之上(换句话说,可以使用slhc模块
中的符号)。除非已经装载了slhc,否则命令insmod ppp就会失败;另一方面,假如在安
装好模块之后会调用命令depmod -a来创建依赖规则,命令modprobe ppp就能成功。
insmod和modprobe间的另一个差别是后者不会在当前目录中查找模块,它只在/lib/modu
les下的缺省目录中查找。这是因为该程序是一个系统实用例程,而不是一个交互工具;
你可以通过在/etc/modules.conf中指定你自己的目录,来把它们加入缺省目录集。
/etc/modules.conf是一个用于定制modules包的文本文件。它负责把象block-major-2这
样的名字关联到floppy。注意,2.0前的版本的modules包查找的是另一个文件/etc/conf
..modules;出于兼容的考虑,仍支持这种文件名,但提倡更为标准的名字modules.conf
。
modules.conf的语法在depmod和modprobe命令的man页中有很好的描述;然而,我觉得有
必要在这里提及一些重要命令的意思。我用下面几行作为例子:
#sample line for /etc/modules.conf
keep
path[misc]=~rubini/driverBook/src/*
option short irq=1
alias eth0 ne
alias eth0 ne
上面显示的第一行是注释;path[misc]指出在哪查找各种模块-而keep指出应把用户路
径加到缺省路径中,而不是替换缺省路径。Option制导(directive)指出在装载short模
块时总是设定irq=1,alias行则指出当需要装载eth0时,相关的文件是ne.o(ne2000接口
的驱动程序)。象alias block-major-2 floppy这样的行并不真正需要,因为modprobe已
经知道的所有设备的官方主设备号,并且这些“可预见”的alias命令在程序中预定义过
了。
那么,按需装载模块的正确安装,就是在文件/etc/modules.conf中加入这么几行,因为
kerneld是依靠modprobe来进行实际的装载操作。
内核级方面
请求加载模块和卸载模块,内核代码可以使用<linux/kerneld.h>中定义的函数。这些函
数都定义成内联函数,实际上又将参数传递给了kerneld_send。kerneld_send函数是用
来与kerneld通讯的一个灵活的引擎,它存在于文件ipc/msg.c中,如果你感兴趣的话,
可以到那里浏览它。
这里,我不准备探讨kerneld_send的细节,因为在头文件<linux/kerneld.h>中定义的下
列一些调用,足够你用来实现按需装载:
int request_module(const char *name)
需要加载模块的时候可以调用该函数。参数name或者是模块的文件名,或者是在用户空
间解析的id类型字符串。在装载成功完成(或失败)后该函数返回。request_module只能
在进程上下文中被调用,因为当前进程将进入睡眠,等待模块被加载。任何一个按需加
载的模块,在使用计数降为0时,都将自动卸载。
int release_module(const char *name,int waitflag)
请求立即卸载一个模块。如果waitflag不为0,意味着函数在返回前必须等待卸载结束。
如果waitflag为0,函数可以在中断时间内调用-如果值得这么做的话。
int delayed_release_module(const char *name)
请求延迟的模块卸载。该函数总是立即返回。它的效果就是模块name在使用计数降为0就
卸载,即使该模块并不是由kerneld加载的。
int cancel_release_module(const char *name)
该函数取消delayed_release_module的作用,它不阻止按需装载模块的自动卸载,最少
当前的实现是这样的。一般不需要该函数,在这里提到它主要是出于完整性的考虑。
如果在内核空间检测到了错误,kerneld_send的返回值,包括所有列出的这些函数的返
回值都会是负的。如果内核中一切运行正常,返回值被置为执行这些操作的用户空间程
序的退出值。成功时的退出值为0,出错时为1到255间的一个数值。
有关kerneld_send的一个好消息就是即使在内核配置成不提供对kerneld的支持时,该函
数仍然存在(并向模块开放)。因而,模块编写者总是可以调用上面显示的这些函数,但
数仍然存在(并向模块开放)。因而,模块编写者总是可以调用上面显示的这些函数,但
此时只返回-ENOSYS。当然,不能运行在1.2版的内核上,因为所有这些机制都是到1.3.5
7版才引入的。
现在,让我们实际地试着使用这些按需加载函数。为此目的,我们使用两个模块,分别
叫作master和slave,O'Reilly的FTP站点上的misc-modules目录下以源文件的形式发布
。我们还将使用slaveD.o来测试延 傩 载,并且使用slaveH.o来测试手工加载以及自动
卸载模块。
为了不安装模块也可以运行测试代码,我在自己的/etc/modules.conf文件中加入了如下
一些行:
keep
path[misc]=~rubini/driverBook/src/misc-modules
slave模块只是一个空文件,而master模块看起来象下面这样:
#include <linux/kerneld.h>
int init_module(void)
{
int r[3]; /* 结果 */
r[0]=request_module("slave");
r[1]=request_module("slaveD");
r[2]=request_module("unexists");
r[2]=request_module("unexists");
printk("master: loading results are %i,%i,%i\n",r[0],r[1],r[2]);
return 0; /* 成功 */
}
void cleanup_module(void)
{
int r[4];/* results */
r[0]=release_module("slave",1/* wait */);
r[1]=release_module("slaveH",1 /* wait */);
r[2]=delayed_release_module("salveD");
r[2]=delayed_release_module("salveD");
r[3]=release_module("unexists",1 /* wait */);
printk("master: unloading results are %i,%i,%i,%i\n",r[0],r[1],r[2],r[3]);
}
在装载时,master模块试着载入两个模块和一个并不存在的模块。除非你改变了终端的
日志级别(loglevel),否则printk消息将出现在终端上。下面是系统被配置成支持kerne
ld而该守护进程又是活动的时候,装载mater模块时的结果:
morgana.root# depmod -a
morgana.root# insmod master
master: loading results are 0,0,255
morgana.root# cat /proc/modules
morgana.root# cat /proc/modules
slaveD 1 0 (autoclean)
slave 1 0 (autoclean)
master 1 0
isofs 5 1 (autoclean)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -