21:21-22:14 60m p21-p42
第二章构造和运行模块
设置测试系统
读者首先要准备好一个内核源代码树。
【Q:怎样准备?】
Helloworld模块
内核需要自己单独的打印输出函数,这是因为它在运行时不能依赖C库。
【Q:为什么?】
核心模块和应用程序相比
模块只是预先注册自己以便服务于将来的某个请求。
模块的退出函数必须仔细撤销初始化函数所做的一切。
一个内核错误即使不影响整个系统,也至少会杀死当前进程。【即出错的影响比应用程序严重】
用户空间和内核空间
将运行模式称为内核空间和用户空间,不仅说明两种模式具有不同的优先权等级,而且还说明每个模式都有自己的内存映射,也即自己的地址空间。
内核中的并发
即使编写最简单的内核程序,都需要在编写时铭记:同一时刻,可能会有许多事情发生。
可能有多个进程同时使用我们的驱动程序。
Linux内核代码(包括驱动程序)必须是可重入的。
其它一些细节
内核具有非常小的栈空间。
如果需要大的结构,则应该动态分配。
编译模块
makefile语法没读懂
装载和卸载模块
一个良好设计的模块可以在装载时进行配置,这比编译时配置为用户提供了更多的灵活性。
有且只有系统调用的名字前带有 sys_前缀
版本依赖
我们在构造过程中可以将自己的模块和当前内核树的 vermagic.o链接。用来检查模块和正在运行的内核的兼容性。
怎样做?
如果堵着打算编写能够和多个内核版本一些工作的模块,则必须使用 #ifdef来构造并编译自己的代码。可以使用 linux/version.h,它自动包含 linux/module.h。这个头文件包含
UTS_RELEASE, LINUX_VERSION_CODE, KERNEL_VERSION(major, minor, release)
不应该胡乱使用 #ifdef,最好的一个解决办法是将所有相关的预处理条件语句集中存放在一个特定的头文件里。
内核符号表
insmod使用公共内核符号表来解析模块中未定义的符号。公共内核符号表中包含了所有的全局内核项(即函数和变量)的地址。
当模块被装入内核后,他所导出的任何符号都会变成内核符号表的一部分。
模块层叠技术。
什么意思?
预备知识
所有模块都必须包含下面两行代码
#include <linux/module.h>
#include <linux/init.h>
初始化和关闭
模块可以注册许多不同类型的设施
设施是指什么?
清除函数
每个模块都需要一个清除函数。
初始化过程中的错误处理
当我们在内核中注册设施时,要时刻铭记注册可能失败。
因此模块代码必须始终检查返回值。
错误回复处理有时使用goto语句比较有效。
有时清除工作涉及很多设施,goto方法可能变得难以管理,则应该调用清除函数。这种方法将减少代码的重复并且使代码更清晰更有条理。
模块装在竞争
在初始化函数还在运行的时候,内核就完全有可能会调用我们的模块。
在用来支持某个设施的所有内部初始化完成之前不要注册任何设施。
我们还必须考虑,当初始化失败而内核的某些部分已经使用了模块所注册的某个设施应该如何处理。
模块参数
所有的模块参数都应该给定一个默认值。
模块参数通常应该设置为只读 S_IRUGO。
如果一个参数通过 sysfs而被修改,内核不会以任何形式通知模块。所以不应该让模块参数是可写的,除非我们打算检查这种修改。
在用户空间做
优点
- 方便调试
- 驱动挂起了,不会影响整个系统
缺点
- 没有中断
- 最重要的设备不能在用户空间处理,包括但不限于网络设备,块设备
【建议开发过程中在用户空间开发驱动,发布时在内核空间】
LDD3 D02 2018.01.02 星二
最新推荐文章于 2023-10-20 17:48:27 发布