Linux内核-中断下半部

Posted by keys961 on March 25, 2019

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()调度:

  • taskletTASKLET_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个在中断上下文)。

实现机制:

wq

5. 总结

下半部 上下文 顺序执行保障
软中断 中断
tasklet 中断 同类型不能同时执行
工作队列 进程 无(同进程上下文一样调度)

选择:若需要休眠,选择工作队列;否则最好用tasklet;若极端性能要求,选择软中断。

6. 下半部之间加锁

对于共享数据:

  • tasklet:同类型的不需考虑,不同类型的要考虑
  • 其它的都要考虑
    • 进程上下文和一个下半部:禁用下半部的处理
    • 中断上下文和一个下半部:禁用中断的处理
    • 工作队列:和一般的内核代码没区别

7. 禁用下半部

一般先得到锁,然后禁止下半部的处理。API为:local_bh_disable/enable(),它是可重入的。