Linux设备驱动-顶半部与底半部

来源:互联网 发布:matlab cell变成矩阵 编辑:程序博客网 时间:2024/05/24 23:12

尽管有些设备仅通过它们的I/O寄存器就可以得到控制,但现实中的大部分设备却比这复杂一些。设备需要与外部世界打交道,如旋转的磁盘,绕卷的磁带,远距离连接的电缆等。这些设备的许多工作通常是在与处理器完全不同的时间周期内完成的,并且总是要比处理器慢。这种让处理器等待外部事件的情况总是不能令人满意,所以必须有一种方法可以让设备在产生某个事件时通知处理器,这种方法就是中断。在大多数情况下,一个驱动程序只需要为它自己设备的中断注册一个处理例程,并在中断到达时进行正确处理。从本质上讲,中断处理例程和其它代码并发运行,对并发控制技术的透彻理解对处理中断来讲非常重要——引自linux device driver。

安装中断处理例程:

int request_irq(unsigned int irq, irq_handler_t handler,  unsigned long irqflags, const char *devname, void *dev_id)

释放中断号:

void free_irq(unsigned int irq, void *dev_id)

irq为中断号使用cat /proc/interrupts 可查看当前已经使用的中断号。 handler中断处理函数,irqflags设置中断方式,如上升沿,下降沿,低电平触发等,devname为中断的名字(随意)。dev_id 用于共享的中断信号线,必须唯一,可以用它来指向驱动程序的私有数据区(用来识别哪个设备产生中断),可以设置为NULL。

其中,irqflags定义于<linux/irq.h>,irq定义于 arch/arm/mach-s3c2410/include/mach/irqs.h(对于s3c2410来说)。 

中断处理例程可在驱动程序初始化时或者第一次打开设备时进行安装,但为断信号线的数量是非常有限的,如果一个模块在初始化时请求了IRQ,那么即使驱动程序只是占用它而从未使用,也将阻止其它驱动使用该中断,而在打开设备时申请中断,则可以其享这些有限的中断资源。

调用request_irq的正确位置应该是在设备第一次打开,硬件被告知产生中断之前。调用free_irq的位置是最后一次关闭设备,硬件被告知不用再中断处理器之后。

中断处理例程的实现:

static irqreturn_t key_interrupt(int irq, void *dev_id,sturct pt_regs *regs);通常使用下面的方式,只传入两个参数。

static irqreturn_t key_interrupt(int irq, void *dev_id);

中断处理例程应该返回一个值,用来指明是否真正处理了一个中断,如果处理例程确实发现其设备的确处理,应该返回IRQ_HANDLED;否则返回IRQ_NONE。可使用宏IRQ_RETVAL(handle)来产生这个返回值。

中断处理例程是在中断时间内运行的,它的行为会受到一些限制。处理例程不能向用户空间发送或者接收数据,因为它不是任何进程的上下文中执行的,处理例程不能做任何可能发生休眠的操作,例如调用wait_event,使用不带GFP_ATOMIC标志的内存分配操作,或者锁住一个信号量等等,不能调用schdule函数。

中断处理例程的一个典型任务是:如果中断通知进程所等待的事件已发生,就会唤醒在该设备上休眠的进程。

中断处理例程应该尽可能的短,执行一个长时间的任务时最好的方法是使用底半部机制。

Linux内核中断机制:为了在中断执行时间尽可能短和中断处理需要完成大量工作之间找到一个平衡点,Linux将中断处理程序分解为两个半部,顶半部和底半部。

顶半部完成尽可能少的比较紧急的任务,它往往只是简单地读取寄存器中的中断状态并清除中断标志位就进行“登记工作”,将底半部处理程序挂到该设备的底半部执行队列中去。

Linux实现下半部的机制主要有tasklet和工作队列。

一:tasklet

记住tasklet是一个可以在由系统决定的安全时刻在软件中断上下文被调度运行的特殊函数,它们可以被多次调度运行,但tasklet不会积累,也就是说,实际只会运行一次。不会有同一个tasklet的多个实例并行的运行,因为它们只运行一次,但是tasklet可以与其它的tasklet并行的运行在对称多处理器(SMP)系统上。如果驱动程序中有多个tasklet,它们必须使用某种机制锁来避免冲突。tasklet在中断处理例程结束前不会开始运行,tasklet运行时,可以有其它的中断发生。tasklet通常是底半部处理的优选机制,因为这种机制非常快,但是所有的tasklet都必须是原子的。

tasklet使用相当简单,我们只需要定义tasklet及其处理函数并将二者关联:
  
void my_tasklet_func(unsigned long); //定义一个处理函数:
DECLARE_TASKLET(my_tasklet,my_tasklet_func,data); //定义一个tasklet结构my_tasklet,与
my_tasklet_func(data)函数相关联 。data为传入处理函数的参数。

然后,在需要调度tasklet的时候引用一个简单的API就能使系统在适当的时候进行调度运行:  tasklet_schedule(&my_tasklet);

实例: 

01//定义与绑定tasklet函数
02 void test_tasklet_action(unsigned long t);
03 DECLARE_TASKLET(test_tasklet, test_tasklet_action, 0);
04 //中断处理底半部
05 void test_tasklet_action(unsigned long t)
06 {
07     printk("tasklet is executing\n");
08 }
09 
10/*中断处理顶半部*/
11 
12static irqreturn_t xxx_interrupt(int irq, void *dev_id)
13{
14    .....
15    tasklet_schedule(&my_tasklet);
16    .....
17}
18 
19/*设备驱动加载模块*/
20 
21int __init xxx_init(void)
22{
23    ......
24    request_irq(IRQ_EINT0,xxx_interrupt, IRQ_TYPE_LEVEL_LOW, "xxx", NULL);
25    ......
26}
27 
28/*设备驱动卸载模块*/
29 
30void __exit xxx_exit(void)
31{
32    ......
33    free(IRQ_EINT0,NULL);
34    ......
35 
36}

二:工作队列

工作队列会在将来某个时间,在某个特殊的工作者进程上下文中调用一个函数,因为工作队列函数运行在进程上下文中,因此可在必要时进行休眠,但是我们不能从工作队列向用户空间复制数据,要知道工作者进程无法访问其它任何进程的地址空间,除非使用进程间工享技术。

struct work_struct my_wq;/*定义一个工作队列*/

void my_wq_func(unsigned long);/*定义一个处理函数*/

INIT_WORK(&my_wq,(void (*)(void *))my_wq_func,NULL);/*初始化工作队列并将其与处理函数绑定*/

示例:

01struct work_struct my_wq;/*定义一个工作队列*/
02 
03void my_wq_func(unsigned long);/*定义一个处理函数*/
04 
05//中断处理底半部
06 void my_wq_func(unsigned long t)
07{
08    .......
09}
10 
11/*中断处理顶半部*/
12 
13static irqreturn_t xxx_interrupt(int irq, void *dev_id)
14{
15    .....
16    schedule_work(& my_wq);
17    .....
18 
19}
20 
21/*设备驱动加载模块*/
22 
23int __init xxx_init(void)
24{
25    ......
26    request_irq(IRQ_EINT0,xxx_interrupt, IRQ_TYPE_LEVEL_LOW, "xxx", NULL);
27    INIT_WORK(&my_wq,(void (*)(void*))my_wq_func,NULL);/*初始化工作队列并将其与处理函数绑定*/
28    ......
29}
30 
31/*设备驱动卸载模块*/
32 
33void __exit xxx_exit(void)
34{
35    ......
36    free(IRQ_EINT0,NULL);
37    ......
38}

来源:http://blog.csdn.net/haomcu/article/details/8156006
原创粉丝点击