一、中断
前文已经提到过中断,这里再回忆一下。中断服务程序一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长,CPU 就不能及时响应其他的中断请求,从而造成中断的丢失。
因此,Linux内核的目标就是尽可能快的处理完中断请求,尽其所能把更多的处理向后推迟。
例如,假设一个数据块已经达到了网线,当中断控制器接受到这个中断请求信号时,Linux内核只是简单地标志数据到来了,然后让处理器恢复到它以前运行的状态,其余的处理稍后再进行(如把数据移入一个缓冲区,接受数据的进程就可以在缓冲区找到数据)。
因此,内核把中断处理分为两部分:上半部(top half)和下半部(bottom half),上半部(就是中断服务程序)内核立即执行,而下半部(就是一些内核函数)留着稍后处理。
首先,一个快速的“上半部”来处理硬件发出的请求,它必须在一个新的中断产生之前终止。通常,除了在设备和一些内存缓冲区(如果你的设备用到了 DMA,就不止这些)之间移动或传送数据,确定硬件是否处于健全的状态之外,这一部分做的工作很少。
下半部运行时是允许中断请求的,而上半部运行时是关中断的,这是二者之间的主要区别。
但是,内核到底什时候执行下半部,以何种方式组织下半部?这就是我们要讨论的下半部实现机制,这种机制在内核的演变过程中不断得到改进,在以前的内核中,这个机制叫做 Bottom Half(简称 bh),在 2.4 以后的版本中有了新的发展和改进,改进的目标使下半部可以在多处理机上并行执行,并有助于驱动程序的开发者进行驱动程序的开发。
对于在上半部和下半部之间划分工作,尽管不存在某种严格的规则,但还是有一些提示可供借鉴:
- 如果一个任务对时问非常敏感,将其放在中断处理程序中执行。
- 如果一个任务和硬件相关,将其放在中断处理程序中执行。
- 如果一个任务要保证不被其他中断(特别是相同的中断)打断,将其放在中断处理程执行。
- 其他所有任务,考虑放置在下半部执行。
本文介绍常用的小任务(Tasklet)机制。
二、tasklet 的运行上下文
一次中断可以拆成:
1 2 3 4 5 6
| hardirq 上半部 -> 记录事件 -> 调度 tasklet
softirq 下半部 -> 执行 tasklet 函数
|
tasklet 的本质是建立在 softirq 之上的延后执行接口。
它的执行上下文仍然不是普通进程:
1 2 3 4
| tasklet 运行在 softirq 上下文 仍然不能睡眠 仍然不能拿 mutex 仍然不能调用 copy_to_user
|
它和 hardirq 的区别主要是:
| 对比项 |
hardirq |
tasklet |
| 执行时机 |
中断到来后立即执行 |
上半部返回后,在 softirq 阶段执行 |
| 能否睡眠 |
不能 |
不能 |
| 适合 |
最小必要处理 |
很短、不能睡眠的延后处理 |
| 典型 API |
request_irq() |
tasklet_schedule() |
所以 tasklet 解决的是“延后”,不是“可以睡眠”。
并且,tasklet运行在软中断上下文中,所以其代码必须是原子操作。
三、tasklet 的简单使用
使用步骤:
- 1、定义一个 tasklet 的执行任务;
- 2、初始化 taskelet,将处理任务的函数和 takslet 任务通过
DECLARE_TASKLET 捆绑;
- 3、调度 tasklet :
tasklet_schedule(&tasklet);
特点:
- tasklet 不能引起休眠,同一个 tasklet 不能在两个 CPU 上同时运行。
- 但是不同 tasklet 可能在不同 CPU 上同时运行,则需要注意共享数据的保护。
声明、初始化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <linux/interrupt.h>
DECLARE_TASKLET(tasklet_name, tasklet_func, func_data);
struct t asklet_struct tasklet_name; tasklet_init(&tasklet_name, tasklet_func, func_data);
struct tasklet_struct { struct tasklet_struct *next; unsigned long state; atomic_t count; void (*func)(unsigned long); unsigned long data; };
#define DECLARE_TASKLET(name, func, data) \ struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);
|
函数功能:将函数和一个tasklet绑定在一起。
参数解析:
tasklet_name : tasklet 的名字
tasklet_func : 执行的 tasklet 的函数名
func_data : 执行 tasklet 所附带的参数,unsigned long,可以像 ioctl 一样传递指针值进去。
执行:
1 2 3 4
| #include <linux/interrupt.h>
tasklet_schedule(struct tasklet_struct* t);
|
描述:调用后,让系统在适当的时候进行调度之前绑定好的 tasklet。
参数解析:t: 某一个 tasklet 。
接口:
tasklet 以一个数据结构形式存在,使用前必须被初始化。初始化能够通过调用一个特定函数或者通过使用某些宏定义声明结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data); #define DECLARE_TASKLET(name, func, data) \ struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data } #define DECLARE_TASKLET_DISABLED(name, func, data) \ struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data } void tasklet_disable(struct tasklet_struct *t); void tasklet_disable_nosync(struct tasklet_struct *t); void tasklet_enable(struct tasklet_struct *t); void tasklet_schedule(struct tasklet_struct *t); void tasklet_hi_schedule(struct tasklet_struct *t); void tasklet_kill(struct tasklet_struct *t);
|
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| void xxx_do_tasklet(unsigned long); irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs);
DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);
int _init xxx_init(void) { result = request_irq(xxx_irq,xxx_interrupt,SA_INTERRUPT,”xxx”,NULL); }
void _exit xxx_exit(void) { free_irq(xxx_irq,xxx_irq_interrupt); tasklet_kill(&xxx_tasklet); }
irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs) { tasklet_schedule(&xxx_tasklet); }
void xxx_do_tasklet(unsigned long) { }
|