1. 下半部
下半部任务:执行与中断处理密切相关、中断处理程序本身不执行的时间宽松的工作
推荐放置任务规则:
- 上半部(中断处理程序):时间敏感,硬件相关,不被其它中断打断的任务
- 下半部:其它所有的任务
执行时间:不确切,通常在中断处理程序返回后马上运行,它允许响应其它中断
系统实现:软中断、tasklet
、工作队列、内核定时器
2. 软中断
代码位于
kernel/softirq.c
中
2.1. 定义
静态定义的下半部接口,定义在<linux/interrupt.h>
中:
1
2
3
struct softirq_action {
void (*action)(struct softirq_action *);
}
kernel/softirq.c
中,通常允许注册32个该接口,用一个数组保存:
1
static struct softirq_action softirq_vec[NR_SOFTIRQS];
2.2. 执行
执行时,只需执行:
1
my_softirq->action(my_softirq_args)
它不会抢占另一个软中断,只会被中断处理程序抢占。它可并行执行。
它只会在标记后才会执行/触发(中断处理程序返回前会标记),时期是:
- 硬件中断代码返回时
ksoftirqd
内核线程中- 显式检查和调用软中断时
都会在do_softirq()
执行,它会循环扫描待执行的软中断。
3. tasklet
利用软中断实现(HI_SOFTIRQ
, TASKLET_SOFTIRQ
),更加简单。
3.1. 定义
定义在<linux/interrupt.h>
中的struct tasklet_struct
:
struct tasklet_struct {
struct tasklet_struct *next; // next tasklet in the linked list
unsigned long state; // tasklet state, only = 0(not sched), TASKLET_STATE_SCHED(sched),TASKLET_STATE_RUN(running)
atomic_t count; // ref count, only activated when = 0
void (*func)(unsigned long); // tasklet procedure
unsigned long data; // tasklet arg
}
3.2. 调度
在tasklet_(hi_)schedule()
调度:
- 若
tasklet
为TASKLET_STATE_SCHED
,直接返回 - 调用
_tasklet_schedule()
- 保存中断状态,并禁止本地中断
- 把需要调度的
tasklet
添加到每个处理器的task_(hi_)vec
链表上 - 唤起
TASKLET/HI_SOFTIRQ
软中断,下一次调用do_softirq()
时会执行tasklet
- 恢复到原状态(包括允许中断),返回
3.3. 执行
核心是tasklet_(hi_)action()
:
- 禁止中断,检索
tasklet_(hi_)vec
链表 - 置当前处理器的该链表为空
- 允许中断
- 遍历链表的每个待处理
tasklet
,若不在执行或没被禁止(count == 0
),设置状态TASKLET_STATE_RUN
,执行它 - 运行完毕,清除
state
域的状态,然后扫描下一个tasklet
项,直到没有待处理的tasklet
3.4. ksoftirqd
每个处理器配这样一个辅助处理软中断/tasklet
的内核线程。
它类似一个死循环,不断检查有无软中断,有就去调度它并执行do_softirq()
,若没有就调用schedule()
让自己睡眠(若do_softirq()
发现已经执行过的内核线程重新触发了它自己,软中断内核线程会被唤醒)。
4. 工作队列
另一种工作推后的机制。工作队列可以重新调度,也可以睡眠,是唯一在进程上下文中运行的下半部实现机制(前2个在中断上下文)。
实现机制:
5. 总结
下半部 | 上下文 | 顺序执行保障 |
---|---|---|
软中断 | 中断 | 无 |
tasklet |
中断 | 同类型不能同时执行 |
工作队列 | 进程 | 无(同进程上下文一样调度) |
选择:若需要休眠,选择工作队列;否则最好用tasklet
;若极端性能要求,选择软中断。
6. 下半部之间加锁
对于共享数据:
tasklet
:同类型的不需考虑,不同类型的要考虑- 其它的都要考虑
- 进程上下文和一个下半部:禁用下半部的处理
- 中断上下文和一个下半部:禁用中断的处理
- 工作队列:和一般的内核代码没区别
7. 禁用下半部
一般先得到锁,然后禁止下半部的处理。API为:local_bh_disable/enable()
,它是可重入的。