free website counter

Linux Device Driver——模块相关

1. 驱动编写策略:

  • 编写内核代码存取硬件,但是不能强加不同策略给用户;驱动应该做到使硬件可用,将所有关于如何使用硬件的事情交给应用程序;
  • 单个设备可能由不同程序并发使用,驱动有完全自由决定如何处理并发性;

2. 内核角色划分:

  • 进程管理:内核负责创建,销毁进程;处理其与外部世界的联系;处理不同进程之间通信(信号,管道,进程间通讯原语);控制进程如何共享CPU;
  • 内存管理:内核为每个进程都在有限的可用资源上建立一个虚拟地址空间;
  • 文件系统:内核在非结构化硬件上建立结构化的文件系统,Unix中所有东西都看作文件;
  • 设备控制:每个系统操作都会映射到物理设备,除了处理器,内存等,任何设备控制操作都由设备驱动代码进行;
  • 网络:操作系统管理网络,系统负责在程序和网络接口之间传递数据报文,根据程序的网络活动控制程序执行;路由和地址解析也在内核实现;

3. 模块:

  • 可以在运行时添加到内核的代码,设备驱动是模块;
  • 模块由目标代码组成(没有链接成完整可执行文件),通过insmod,rmmod去连接,卸载;

4. 设备和模块分类:

  • 字符设备(char):可以当作字节流来存取的设备,如同文件;字符驱动至少实现open, close, read, write等系统调用;文本控制台和串口是字符设备的例子;
  • 块设备,通过位于/dev目录的文件系统结点存取,一个块设备可以驻有文件系统;Linux允许应用程序像字符设备一样写块设备,一次传送任意数目字节;块设备和字符设备与内核接口完全不同,但对用户透明;
  • 网络接口:任何网络事务都通过接口进行,网络接口负责收发数据报文;

5. 驱动编写注意:

  • 如今很多安全问题由缓冲区覆盖引起,忘记检查多少数据写入缓冲区,数据在缓冲区之外结束,覆盖无关数据;
  • 任何从用户进程接收的输入,都要谨慎;
  • 小心对待未初始化内存,从内核获得的任何内存都应该清零或者在其对用户进程/设备可用之前初始化,否则容易内存泄漏;

6. 内核版本:

  • 偶数内核版本(2.6.x)是稳定的,通用发布;
  • 奇数内核版本(2.7.x)是开发快照,代表开发的当前状态,几天过时;

7. Hello World模块:

  • module_init(hello_init), module_exit(hello_exit), 使用了特别的内核宏指出hello_init, hello_exit的角色;前者在模块加载到内核时调用,后者在模块被去除时调用;MODULE_LICENSE(“Dual BSD/GPL”)告知内核,该模块带有一个自由的许可证,否在模块加载时内核抱怨;
  • printk为内核自己的打印函数,没有C库帮助;insmod模块以后,被连接到内核并可存取内核的函数和变量,故可以调用printk;
  • 在窗口系统中运行insmod、rmmod,不会在屏幕有任何输出,消息进入系统日志文件中,如/var/log/messages;

8. 内核模块相比应用程序:

  • 应用程序执行单个任务,而内核模块只注册自己,以便服务将来应用,内核模块类似于事件驱动编程;
  • 模块的退出函数必须小心恢复每个由初始化函数建立的东西;
  • 应用程序可以调用其没有定义的函数,连接阶段使用合适库函数解决外部引用;而模块只连接到内核,能够调用的函数仅限于内核输出的那些;
  • 处理错误方式不同,应用程序开发阶段错误通常无害,可以使用调试器追踪错误;而内核错误会杀死当前进程,甚至kernel panic;
  • 模块在内核空间运行,应用程序在用户空间运行;
  • 模块拓展内核功能,模块中的一些函数作为系统调用的一部分,另一些负责中断处理;
  • 大多数应用程序(多线程除外),从头到尾不必担心其他事情发发生而改变其运行环境;而内核代码,并发经常发生;
  • 内核编程的并发来源:系统运行多个进程,同一时间不止一个进程试图使用驱动;大部分设备可以中断处理器,中断处理异步,可能在驱动试图做其他事情时被调用;对于多处理器系统,驱动可能在多个CPU上并发执行;
  • 内核代码的数据结构必须小心设计以保持多个执行线程分开,正确的并发管理在编写正确内核代码时必须;
  • 应用程序存在于虚拟内存中,有一个非常大的堆栈区保存函数调用历史以及所有由当前活跃函数创建的自动变量;内核只有一个小的堆栈可能只有4096字节的页,内核模块函数与内核空间调用链共享堆栈;不能声明大的自动变量,如果需要大的结构,应当在调用时动态分配;
  • 内核代码不做浮点运算,使能浮点将要求内核每次进出内核空间时保存和恢复浮点处理器状态,额外负担不值得;

9. 初始化和卸载:

  • 实际初始化函数:
static int __init initialization_function(void) {
              /*code here.*/
         }
         module_init(initialization_function);
  • 初始化函数应该声明为静态,不会在特定文件之外可见;__init 标志暗示内核,给定函数只在初始化使用,模块加载后会丢掉该函数,使其内存可作他用;module_init增加特别的段到目标代码,表明在哪找到模块的初始化函数;
  • 实际卸载函数,注销接口,模块被去除之前返回所有资源给系统:
static void __exit cleanup_function(void) {
              /*code here.*/
         }
         module_exit(cleanup_function);
  • __exit标识代码只用于模块卸载,模块直接建立在内核或者内核配置成不允许模块卸载时,__exit将被丢弃;module_exit标明清理函数位置;

10. 错误处理:

  • 注册时大量内存分配操作,分配的内存可能不可用,必须一直检查返回值;一旦失败不能加载,必须取消任何失败之前注册的动作;
  • 错误恢复通常使用goto语句;
Published 18 November 2014
分享按钮