Table of Contents
Kernel Modules Versus Applications
Error Handling During Initialization
Module Driver Sample
模块化是Linux kernel driver的立身之本,几乎所有的device driver都是module driver,在kernel启动的时候动态加载。直接拿书里的例子看:
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
printk(KERN_ALERT "Hello, world\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye, cruel world\n");
}
module_init(hello_init);
module_exit(hello_exit);
上面的这个sample code只是简单展示module driver在安装卸载时会调用的函数。
无论多么复杂的device driver,都是这个骨架,module driver的init和exit,就是driver和kernel接触的第一次和最后一次。module driver在init做完以后,其实就不干活了,躺在那里睡大觉。那么什么时候干活呢?driver的运行是基于event-driven模型的,就是说要有event过来,driver才会有事情做。
这很合理,我写了一个USB 设备的驱动,比如键盘,如果没有人使用键盘,那键盘驱动干啥活。虽然如此,在init的时候,driver也有很多事情可以做,主要是初始化相关的,比如注册设备,设置中断ISR,初始化设备等等。等初始化做完以后,device和driver的信息kernel就会记录下来,一旦有人使用了你的设备,驱动就会被调用。
如果driver被卸载,那么driver自己就要把init中申请的资源按序释放,这个非常重要,因为module driver的一大好处就是动态的加载和卸载,不用重启系统。如果init/exit没有做好,那driver很有可能出问题,甚至把kernel搞死。
来一张module driver被insmod以后在kernel中的描述图:
Kernel Modules Versus Applications
kernel mode driver和应用程序有很大的不同:
1. kernel mode driver无一例外都是事件驱动的,依赖于user mode或者kernel mode事件来触发操作,应用程序有的是,有的不是。
2. 应用程序的资源可以晚一点释放,甚至在退出时也不主动释放,因为OS会在进程结束的时候自动回收进程占用过得资源。而kenrel mode driver则必须在退出时谨慎的释放所有占用的资源。
3. 应用程序可以调用别的库中的函数,比如调用libc库中实现的函数;而module driver只能调用kernel中export出来的函数。(有些module driver可以主动export一些function出来给别的module用)
4. kernel 中没有float运算的支持,只能通过软件的方式来做。
5. 错误处理不同。应用程序出错,最多应用程序被kill,对系统和别的应用程序没有影响;如果kernel driver出了问题,至少当前的process挂了,严重点就是kernel panic,整个系统死掉,只能重启。
说到第五点,就要讨论user space和kernel space了。
User Space and Kernel Space
学过操作系统的人应该都了解,应用程序都运行在user space,kernel(包括所有的kernel driver)都运行在kernel space。操作系统的职责之一就是对计算机进行抽象,并提供统一的资源访问方式给用户。在用户程序访问资源的同时,OS要对各个用户进行隔离,也要对资源进行保护。user space和kernel space这两种space会对应CPU不同的运行等级,现代CPU的运行等级有好几级,kernel code运行在最高等级,可以访问一切资源;user code运行在最低等级,只能访问有限的资源,而且对hardware的访问,都需要经过kernel。这种等级的限制,可以把user space的不可靠对系统造成的影响降到最低。另外,两种space也对应了两种不同的地址空间,user space对应了比较大的虚拟地址空间,比如32位上,每个应用程序在linux系统中有4G的地址空间,user space占了3G,kernel space只有高地址的1G,所有应用程序的高地址的这1G是共享的,意思就是每个人虚拟地址空间的1G内容都是一样的,都是当前正在运行的kernel,只有剩下的3G虚拟地址空间是每个应用程序私有的。
既然应用程序需要通过kernel访问硬件资源,那程序执行过程中必然有从user space转到kernel space的过程,怎么发生的呢?答案是两种情况:系统调用和硬件中断。
系统调用发生时,用户空间的代码通过系统调用进到了kernel space,同一个process的执行特权等级被提高到了最高等级,可以访问一切资源;硬件中断发生时,kernel会暂停当前CPU的process,并把CPU的使用权交给对应的ISR,中断处理完以后恢复现场。其实系统调用也是中断的一种,是一种软中断,和各种各样的调试器,如gdb等设置断点的原理类似,这里就不展开了。
Concurrency in the Kernel
下面很重要的一个概念,是kernel中的并发。
最开始做device driver的时候,其实根本意识不到并发这个事情,因为device driver是kernel按需调用的,我只要按照kernel的要求实现好接口,driver里面实现好业务的逻辑就可以了,脑子里并没有并发或者竞争条件这种事情。
kernel是一个非常庞大的系统,每个driver在把自己交给kernel的时候就已经分散到kernel的各个子系统之中,随时会被任何程序调用,因此写driver的时候除了要实现好业务逻辑以外,还要清楚的认识到driver运行环境的复杂性。以char device为例,一般支持open/close/read/write/mmap等操作,这些操作是由user mode应用程序发起的,程序调用了open,driver对应的probe会被调用;程序调用了close,driver的close会被调用等,driver本身不再是一个整体,而是由user mode发起的事件所驱动,并且由kernel适时的调用,因此driver什么时候被调用,调用的那个函数,都是user mode和kerne决定的,driver本身无法控制。想明白这点,就比较容易理解并发了,既然是事件驱动,那事件有可能不止一个,因为同一个时刻可能有多个程序使用你的设备,每个程序都在执行你的代码。
并发在很多情况下都会发生,比如:
1. linux本身支持多个process同时运行,这就是上文讲的,同时有多个程序访问你的设备,也就有多个进程调用你的驱动。
2. ISR,中断随时可能发生,也许你的driver正在处理用户态程序的请求,而这时设备中断发生了,那么driver的中断处理程序就会被调用,CPU转去执行driver的中断处理函数。
3. kernel里的timer等,定时器触发,也会调用你的driver,这种情况发生在driver设置了timer function,并且timer被触发的情况下。
正是因为存在这么多并发的可能性,所以driver要可重入(reentrant)。意思就是,driver被多个process同时调用,但是没有副作用。这就要求driver developer在coding的时候,脑子里要有这个意识,function有可能被多个程序同时调用,那么这些function里就要做好全局数据的保护,kernel的锁机制可以用来保证自己的数据不会因为同时访问而损坏,要知道kernel是允许抢占的,CPU可能在任何时候被人抢占,等回来时,说不定数据已经被人修改,所以一定要用锁做好数据保护。
kernel中提供了一种直接访问当前CPU上正在执行的task的方法——current指针,current指针指向struct task_struct结构体&#