《LINUX设备驱动程序》学习笔记 ——02

发布时间 2023-09-16 09:35:04作者: 成信吴彦祖(略胜亿筹)

1. 编译模块

  构造内核模块之前,需要注意以下条件:正确版本的编译器、模块工具和其他必要的工具。太新的或太老的工具都会对使得模块构造后产生许多复杂的问题,因为内核源代码对编译器做了大量假定,因此新的(或旧的)编译器版本可能导致问题出现。

  另外,尽量运行和模块对应的内核版本。

2. 模块的装载和卸载

——装载

  一个良好设计的模块可以在装载时进行配置,这比编译时进行配置给用户提供了更多的灵活性。但有些情况下仍然需要使用编译时的配置,涉及到模块参数,后续介绍。

  介绍一下insmod命令(它依赖于被定义在 kernel/module.c 中的一个系统调用,函数 sys_init_module 为其分配内存)。

  注意:只有系统调用的名字前带有 sys_ 前缀,而其他任何函数都没有这个前缀。这种命名上的区别是我们在 grep 系统调用时非常方便。

    

   modprobe 的优点是如果在欲加载的模块中检索到不存在的符号,会在该路径下查找其他定义了该符号的模块并加载(即检索并加载依赖模块)。同样的情况如果使用 insmod 则会在系统日志文件中记录 “unresolved symbols”(未解析的符号)消息。

——卸载

  使用 rmmod 工具从内核中移除模块,可能会失败,需要考虑以下两种场景:【1】. 内核认为模块仍然在使用状态(如某个程序打开由该模块导出的设备文件)【2】.内核被配置为禁止移除模块。

  配置内核模块使得内核在模块忙的时候仍然能 "强制" 移除模块也是可能的。

3. 版本依赖

  模块和特定内核定义的数据结构和函数原型紧密相关,即两个版本的内核对名称相同的一个接口定义可能完全不同。内核并不能知道这一点,因此构造时需要将模块和当前内核树中的一个文件 (即vermagic.o)链接(该文件包含大量有关内核的信息),在试图装载模块时,这些信息可以检查模块和正在运行的内核兼容性。(查看系统日志 /var/log/messages 将看到导致加载失败的具体原因)

  为了写一个能够兼容多个内核版本一起工作的内核模块(尤其是跨主发行号工作),则必须使用宏和 #ifdef 来构造自己的代码。有三个相关的宏:

UTS_RELEASE

LINUX_VERSION_CODE

KERNEL_VERSION(major, minor, release)

  最好的做法是将所有的相关的 #ifdef 预处理放在一个特定的头文件里。一般来说依赖于特定版本(或平台)的代码应该隐藏在低层宏或者函数后,之后高层宏可以直接调用这些函数而无需关注低层细节,这样的代码阅读性和健壮性更强。

4. 平台依赖

  考虑到不同进的目标平台的性能,内核需要发布源码,然后针对目标平台定制编译后才能达到对某个特定计算机集合的优化。

  如果我们希望编写一个驱动程序用于一般性的发布,则必须考虑不同处理器变种的兼容性。通常有两种方法:【1】用GPL许可证来发布自己的程序,并将其贡献给内核主分支。【2】以源代码 + 一组用于编译的脚本发布自己的驱动程序。