【嵌入式Linux驱动程序-基础篇】- 前和后半部

前和后半部

实际开发中,我们常常会面对一个问题:中断处理应当如何去处理一个较长时间的任务。我们都知道,中断处理不能被阻塞过长时间,不然就影响中断的效率。既要保证工作量大,又要保证中断速度快,确实是很难解决的难题。因此,内核开发者就通过前和后半部的方法来解决这难题。

内核开发者将中断分为2部分处理,前半部分实际响应中断的入口,即我们request_irq注册时所传递的中断处理函数。后半部则是前半部调度来延后执行的函数,后半部就可以执行大量的工作。在典型的场景中,前半部保存设备数据到一个设备特定的缓存,调度它的后半部,并且退出:这个操作非常快速,后半部则接着进行任何其他需要的工作,例如唤醒经常,启动另一个I/O操作,等等。这种设置允许前半部来服务一个新中断而同时后半部仍然在工作。

几乎每个认真的中断处理都这样划分。例如,当一个网络接口报告有新报文到达,处理者只是获取数据并且上推给协议层,报文的实际处理在后半部进行。

Linux内核有2个不同的机制可用来实现后半部处理:

  • tasklet

tasklet常常是后半部处理的首选机制,它们非常快,但是所有的tasklet代码必须是原子的。

  • 处理队列

1 Tasklet实现

记住tasklet是一个特殊的函数,可能被调度来运行,在软中断上下文,在一个系统决定的安全时间中,它们可能被调度运行多次,但是tasklet调度不积累;tasklet只运行一次,但是tasklet可以与SMP系统上的其他tasklet并行运行。因此,如果你的驱动有多个tasklet,它们必须采取某类加锁机制避免冲突。

tasklet也保证作为函数运行在第一个调度它们的同一个CPU上。因此,一个中断处理可以确保一个tasklet在处理者解除前不会开始执行。但是,另一个中断当然可能在tasklet运行时被递交。

tasklet必须使用DECLARE_TASKLET宏来声明:

DECLARE_TASKLET(name, function, data);

name是给tasklet的名字,function是调用来执行tasklet(它带一个unsigned long参数并且返回void)的函数,以及data是一个unsigned long值来传递给tasklet函数。

例子:

void test_do_tasklet(unsigned long);
DECLARE_TASKLET(test_tasklet, test_do_tasklet, 0);

函数tasklet_schedule用来调度一个tasklet运行,例如调度test_tasklet,如下:

tasklet_schedult(&test_tasklet);

调度之后,test_do_tasklet将在系统fang方便时被执前面提过,这个函数进行处理中断大量工作。一个设备能够在短时间内产生大量中断,因此在后半部执行前有几个中断到达就不是不寻常的。驱动必须一直准备这种可能并且必须能够从前半部留下的信息中决定有多少工作要做。

2 工作队列

工作队列在将来某个时候调用一个函数,在一个特殊工作者进程的上下文中,因为这个工作队列函数在进程上下文运行,它需要时能够睡眠。但是,你不能从一个工作队列拷贝数据到用户空间, 工作者进行不存取任何其他进程的地址空间。

声明和初始化工作队列如下所示:

static struct work_struct test_wq;

INI_WORK(&test_wq, (void * (void ()) test_do_tasklet, NULL);

 工作队列调用如下:

schedule_work(&test_wq);

猜你喜欢

转载自blog.csdn.net/santapasserby/article/details/81697915
今日推荐