Build and Run Modules [LDD3 02]

本文介绍了Linux内核模块的基本概念,包括模块与应用程序的区别、用户空间与内核空间的差异、并发性以及编译加载过程。重点讨论了模块的加载(insmod、modprobe、rmmod)及其依赖处理、版本兼容性和平台适应性。此外,还阐述了内核符号表、初始化与清理函数、错误处理以及模块参数的使用。
摘要由CSDN通过智能技术生成

Table of Contents

Module Driver Sample

Kernel Modules Versus Applications

User Space and Kernel Space

Concurrency in the Kernel

Compile and Loading

Compiling Modules

Module Driver Load and Unload

Load

Unload

Version Depedency

Platform Dependency

The Kernel Symbol Table

Preliminaries

Initialization and Shutdown

The Cleanup Function

Error Handling During Initialization

Module-Loading Races

Module Parameters

Doing It in User Space


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结构体&#

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值