📄 (ldd) ch02-编写和运行模块(转载).txt
字号:
er_symtab都会替换相应模块的符号表。
如果你的模块不需要开放任何符号,而且你也不想把所有的东西都声明成static的,在i
nit_module里加上下面一行语句就可以了。这次对register_symtab的调用通过注册一个
空表覆盖了模块默认的符号表:
(代码)
如果源文件不想给堆叠在其上的模块提供什么接口,用上面那行语句隐藏所有的符号总
是不错的。
当模块从内 诵对厥 ,它所声明的所有公共符号也就自动?主符号表中注销了。不过是
全局符号还是显式符号表,这一点都适用。
初始化和终止
正如前面已述,init_module向内核注册模块所能提供的所有设施。这里我使用了“设施
”,我的意思是指新功能,是一整个设备驱动程序或新软件抽象,是一个可以由应用程
序使用的新功能。
通过调用内核函数完成新设施的注册。传递的参数通常为一个指向描述这个新设施的数
据结构和要注册的设施名称。这个数据结构通常会包含一些指向模块函数的指针,这就
是模块体内的函数是被调用的机制。
除了用来标别模块类别(如字符和块设备驱动程序)的“主”设施之外,模块还可以注
册如下项目:
其他设备
由于这类设施仅仅用于总线型鼠标,这些设备曾一度称为鼠标设备。它们都是些
不完整的设备,通常要比那些功能健全的设备简单。
串行端口
可以在运行时向系统里加入串口设备驱动程序;这也是支持PCMCIA调治解调器的
机制。
行律
行律是处理终端数据流的软件层。模块可以注册新行律,以非标准方式处理终端
事务。例如,模块kmouse就使用行律从串口鼠标中偷取数据。
终端设备驱动程序
终端设备驱动程序一组实现终端底层数据处理的函数。控制台和串口设备驱动程
序为了创建终端设备,它们都要注册自己的驱动程序。而多端口串口则有自己的驱动程
序为了创建终端设备,它们都要注册自己的驱动程序。而多端口串口则有自己的驱动程
序。
/proc文件
/proc包含了用来访问内核信息的文件。由于它们也可以用来调试,第4章的“使
用/proc文件系统”将讲解/proc文件。
二进制文件格式
对于每个可执行文件,内核扫描“二进制文件格式”列表并按相应的格式执行它
。模块可以实现新的格式,Java模块就是这样做的。
Exec域
为了提供与其他流行Unix系统的兼容,必须修改内核的某些内部表格。一个“执
行域”就是一组从其他操作系统约定到Linux系统的映射。例如,模块可以定义执行SCO
二进制文件的执行域。
符号表
这个已在前面的“注册符号表”小节中介绍了。
上面这些项目都不是前一章所考虑的设备类型,而且都支持那些通常集成到驱动程序功
能中的设施,如/proc文件和行律。之所以鼠标和其他设备驱动程序都没有象“完整”字
符设备那样管理,这主要是为了方便。过一会儿,当你读到第3章“字符设备”的“主从
设备号”小节时,原因就明了了。
还可以将模块注册为某些驱动程序的附件,但这样做就太特殊了,这里就不作讨论了;
它们都使用了“注册符号表”中讲到的堆叠技术。如果你想做更深一步的探究,你可以
在内核源码中查查register_symtab,并且找找不同驱动程序的入口点。大部分注册函数
都是以register_开始的,这样你就可以用“register_”在/proc/ksyms找找它们了。
init_module中的错误处理
如果你注册时发生什么错误,你必须取消失败前所有已完成的注册。例如,如果系统没
有足够内存分配新数据结构时,可能会发生错误。尽管这不太可能,但确实会发生,好
的程序代码必须为处理这类事件做好准备。
Linux不为每个模块保留它都注册了那些设施,因此当init_module在某处失败时,模块
必须统统收回。如果你在注销你已经注册的设施时失败了,内核就进入一种不稳定状态
:卸载模块后,由于它们看起来仍然是“忙”的,你再也不能注册那些设施了,而且你
也无法注销它们了,因为你必须使用你注册时的那个指针,而你不太可能得到那个指针
了。恢复这种情况非常复杂,通常,重新启动是最好的解决方法。
我建议你用goto语句处理错误恢复。我讨厌使用goto,但以我个人来看,这是一个它有
我建议你用goto语句处理错误恢复。我讨厌使用goto,但以我个人来看,这是一个它有
所做为的地方(而且,是唯一的地方)。在内核里,通常都会象这里处理错误那样使用g
oto。
下面这段样例在成功和失败时都能正确执行:
(代码)
返回值(err)是一个错误编码。在Linux内核里,错误编码是一个负值,在<linux/errn
o.h>中定义。如果你不使用其他函数返回的错误编码而要生成自己的,你应该包含<linu
x/errno.h>,这样就可以使用诸如-ENODEV,-ENOMEM之类的符号值。总是返回相应的错
误编码是种非常好的习惯,因为这样一来用户程序就利用perror或相似的方法把它们转
换成有意义的字符串了。
很明显,cleanup_module要取消所有init_module中完成的注册。
(代码)
使用计数
为了确定模块是否可以安全地卸载,系统为每个模块保留了一个使用计数。由于模块忙
的时候是不能卸载模块的,系统需要这些信息:当文件系统还被安装在系统上时就不能
删除这个文件系统类型,而且你也不能在还有程序使用某个字符设备时就去掉它。
如果忘了更新使用计数,你就不能再卸载模块了。在开发期间这种情况很可能发生,所
以你一定要牢记。例如,如果进程因你的驱动程序引用了NULL指针而终止,驱动程序就
不可能区关闭设备,使用计数也就无法回复到0。一种可能的解决方法就是在调试期间完
全不使用使用计数,将MOD_INC_USE_COUNT和MOD_DEC_USE_COUNT重新定义为空操作。另
一个解决方法就是利用其他方法将计数强制复位为0(在第5章的“使用ioctl参数”小节
中介绍)。在编写成品模块时,决不能投机取巧。然而在调试时期,有时候忽略一些问
题可以节省时间,是可以接受的。
使用计数的当前值可以在/proc/modules中每一项的第3个域中找到。这个文件显式系统
中当前共加载了那些模块,每一项对应一个模块。其中的域包括,模块名,模块使用的
页面数和当前使用计数。这是一个/proc/modules样例:
(代码)
(autoclean)标志表明模块由kerneld管理(见第11章)。较新的内核中又加入了一些新
的标志,除了一件事外,/proc/modules的基本结构完全相同:在内核2.1.18和更新的版
本中,长度用字节计而不是页面计。
卸载
要卸载一个模块就要使用rmmod命令。由于无需连编,它的任务远比加载简单。这个命令
调用系统调用delete_module,如果使用计数为0它又调用模块的cleanup_module。
cleanup_module实现负责注销所有由模块已经注册了的项目。只有符号表是自动删除的
cleanup_module实现负责注销所有由模块已经注册了的项目。只有符号表是自动删除的
。
使用资源
模块不使用资源是无法完成自己的任务的,这些资源包括内存,I/O端口和中断,如果你
要用DMA控制器的话,还得有DMA通道。
做为一个程序员,你一定已经习惯了内存分配管理,在这方面编写内核代码没什么区别
。你的程序使用kmalloc分配内存,使用kfree释放内存。除了kmalloc多一个参数,优先
级,外,它们和malloc,free很相似。很多情况下,用优先级GFP_KERNEL就可以了。缩
写GFP代表“Get Free Page(获取空闲页面)。”
与此不同,获取I/O端口和中断乍听起来怪怪的,因为程序员一般同用显式的指令访问它
们,不必让操作系统了解这些。“分配”端口和中断与分配内存不同,因为内存是从一
个资源池中分配,并且每个地址的行为是一样的;I/O端口都各有自己的作用,而且驱动
程序需要在特定的端口上工作,而不能随便使用某个端口。
端口
对于大多数驱动程序而言,它们的典型工作就是读写端口。不管是初始化还是正常工作
的时候,它们都是这样的。为了避免其他驱动程序的干扰,必须保证设备驱动程序以独
占方式访问端口――如果一个模块探测因自己的硬件而写某个端口,而恰巧这个端口又
是属于另一个设备的,这之后一定会发生点怪事。
为了防止不同设备间的干扰,Linux的开发者决定实现端口的请求/释放机制。然而,未
授权的对端口的访问并不会产生类似于“段失效”那样的错误――硬件无法支持端口注
册。
从文件/proc/ioports可以以文本方式获得已注册的端口信息,就象下面的样子:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -