中断下文之 tasklet介绍
中断的上下文与进程上下文并没有什么瓜葛, 当执行一个中断处理函数时, 内核处于中断上下文。 由于中断相当于打断了当前执行的程序, 而且中断也没有后备的进程, 所以中断上下文不可以睡眠( 注意某些函数会睡眠) , 中断处理也必须做到迅捷, 有一定的时限要求。 中断处理程序存在希望中断程序运行的尽量快以及希望中断处理程序完成的工作量多这一对矛盾。 因此我们一般将中断分为上下两个部分, 分为上半部, 下半部。 上半部完成有严格时限的工作(必须) , 例如回复硬件等, 这些工作都是在禁止其他中断情况下进行的。 能够延后执行的都放在下半部进行。 上半部只能通过中断处理程序实现, 下半部的实现目前有 3 种实现方式, 分别为: 1、 软中断、 2、 tasklet 3、 工作队列(work queues) 我们主要讲 tasklet。 调用 tasklet 以后, tasklet 绑定的函数并不会立马执行, 而是有中断以后, 经过一个很短的不确定时间在来执行, 如下图所示:
tasklet 的概念
tasklet 是通过软中断实现的, 所以它本身也是软中断。 软中断用轮询的方式处理, 假如正好是最后一种中断, 则必须循环完所有的中断类型, 才能最终执行对应的处理函数。 为了提高中断处理数量, 顺道改进处理效率, 于是产生了 tasklet 机制。 tasklet 采用无差别的队列机制, 有中断时才执行, 免去了循环查表之苦, tasklet 机制的优点: 无类型数量限制, 效率高, 无需循环查表, 支持 SMP 机制, 一种特定类型的 tasklet 只能运行在一个 CPU 上, 不能并行, 只能串行执行。 多个不同类型的 tasklet 可以并行在多个CPU 上。 软中断是静态分配的, 在内核编译好之后, 就不能改变。 但 tasklet 就灵活许多, 可以在运行时改变(比如添加模块时) 。
Linux 内核中的 tasklet 结构体:
struct tasklet_struct
{
struct tasklet_struct *next; /* 下一个 tasklet */
unsigned long state; /* tasklet 状态 */
atomic_t count; /* 计数器, 记录对 tasklet 的引用数 */
void (*func)(unsigned long); /* tasklet 执行的函数 */
unsigned long data; /* 函数 func 的参数 */
};
next: 链表中的下一个 tasklet, 方便管理和设置 tasklet;
state: tasklet 的状态。
count: 表示 tasklet 是否出在激活状态, 如果是 0, 就处在激活状态, 如果非 0, 就处在非激活状态
void (*func)(unsigned long): 结构体中的 func 成员是 tasklet 的绑定函数, data 是它唯一的参数。
date: 函数执行的时候传递的参数。
如果要使用 tasklet, 必须先定义一个 tasklet, 然后使用 tasklet_init 函数初始化 tasklet, taskled_init 函数原型如下:
函数 | void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data); |
t | 要初始化的 tasklet |
func | tasklet 的处理函数 |
data | 要传递给 func 函数的参数 |
返回值 | 没有返回值。 |
功能 | 动态初始化 tasklet |
也可以使用宏 DECLARE_TASKLET 一次性完成 tasklet 的定义和初始化, DECLARE_TASKLET 定义在include/linux/interrupt.h 文件中, 定义如下:
DECLARE_TASKLET(name, func, data)
其中 name 为要定义的 tasklet 名字, 这个名字就是一个 tasklet_struct 类型的时候变量, func 就是tasklet 的处理函数, data 是传递给 func 函数的参数。
在需要调度 tasklet 的时候引用一个 tasklet_schedule() 函数就能使系统在适当的时候进行调度运行,该函数原型为如下所示:
函数 | void tasklet_schedule(struct tasklet_struct *t) |
t | 要调度的 tasklet, 也就是 DECLARE_TASKLET 宏里面的 name。 |
返回值 | 没有返回值 |
功能 | 调度 tasklet |
杀死 tasklet 使用 tasklet_kill 函数,函数原型如下表所示:
函数 | tasklet_kill(struct tasklet_struct *t) |
t | 要删除的 tasklet |
功能 | 删除一个 tasklet |
注意 | 这个函数会等待 tasklet 执行完毕, 然后再将它移除。 该函数可能会引起休眠, 所以要禁止在 中断上下文中使用。 |
tasklet 参考步骤
关于 tasklet 的参考使用示例如下所示:
/* 定义 taselet */
struct tasklet_struct testtasklet;
/* tasklet 处理函数 */
void testtasklet_func(unsigned long data)
{
/* tasklet 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
......
/* 调度 tasklet */
tasklet_schedule(&testtasklet);
......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
......
/* 初始化 tasklet */
tasklet_init(&testtasklet, testtasklet_func, data);
/* 注册中断处理函数 */
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
......
}
总结一下基本步骤为:
步骤一: 定义一个 tasklet 结构体
步骤二: 动态初始化 tasklet
步骤三: 编写 tasklet 绑定的函数
步骤四: 在中断上文调用 tasklet
步骤五: 卸载模块的时候删除 tasklet