Linux中断体系 下半部
一.Softirq
二.Tasklet
三.Tasklet_action
一.softirq
为了提高中断的响应速度,很多操作系统都把中断分成两个部分,上半部分和下半部分(bottom half).上半部分通常是响应中断,并把中断所得到的数据保存到下半部,耗时的操作一般都会留到下半部去处理。
在softirq_init()中会注册两个常用类型的软中断,看具体代码:
void __init softirq_init(void)
{
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}
在cat /proc/softirq时能看到此两种软中断
//参数含义:nr:软中断类型 action:软中断处理函数 data:软中断处理函数参数
void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{
softirq_vec[nr].data = data;
softirq_vec[nr].action = action;
}
static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp;
struct softirq_action
{
void (*action)(struct softirq_action *);
void *data;
};
在上面的代码中,我们可以看到:open_softirq()中.其实就是对softirq_vec数组的nr项赋值.softirq_vec是一个32元素的数组,实际上linux内核只使用了六项. 如下示:
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
SCSI_SOFTIRQ,
TASKLET_SOFTIRQ
}
另外.如果使软中断能被CPU调度,还得要让它激活才可以.激活所使用的函数为__raise_softirq_irqoff()
代码如下:
#define __raise_softirq_irqoff(nr) do { local_softirq_pending() |= 1UL << (nr); } while (0)
这个宏使local_softirq_pending的nr位置1
好了,经过open_softirq()à local_softirq_pending()后,我们来看下软中断怎么被CPU调度.
继续上面中断处理的代码.在处理完硬件中断后,会调用irq_exit().这就是软中断的入口点了,我们来看下
#define irq_exit() /
do { /
preempt_count() -= IRQ_EXIT_OFFSET; /
//注意了,软中断不可以在硬件中断上下文或者是在软中断环境中使用哦
//softirq_pending()的判断,注意我们上面分析过的_raise_softirqoff().它判断当前cpu有没有激活软中断
if (!in_interrupt() && softirq_pending(smp_processor_id())) /
do_softirq(); /
preempt_enable_no_resched(); /
} while (0)
跟踪进do_softirq()
asmlinkage void do_softirq(void)
{
__u32 pending;
unsigned long flags;
//在硬件中断环境中,退出
if (in_interrupt())
return;
//禁止本地中断,不要让其受中断的影响
local_irq_save(flags);
pending = local_softirq_pending();
//是否有软中断要处理?
if (pending)
__do_softirq();
//恢复CPU中断
local_irq_restore(flags);
}
转入__do_softirq()
asmlinkage void __do_softirq(void)
{
struct softirq_action *h;
__u32 pending;
int max_restart = MAX_SOFTIRQ_RESTART;
int cpu;
pending = local_softirq_pending();
//禁止软中断,不允许软中断嵌套
local_bh_disable();
cpu = smp_processor_id();
restart:
/* Reset the pending bitmask before enabling irqs */
//把挂上去的软中断清除掉,因为我们在这里会全部处理完
local_softirq_pending() = 0;
//开CPU中断
local_irq_enable();
//softirq_vec:32元素数组
h = softirq_vec;
//依次处理挂上去的软中断
do {
if (pending & 1) {
//调用软中断函数
h->action(h);
rcu_bh_qsctr_inc(cpu);
}
h++;
pending >>= 1;
} while (pending);
//关CPU 中断
local_irq_disable();
pending = local_softirq_pending();
//在规定次数内,如果有新的软中断了,可以继续在这里处理完
if (pending && --max_restart)
goto restart;
//依然有没有处理完的软中断,为了提高系统响应效率,唤醒softirqd进行处理
if (pending)
wakeup_softirqd();
//恢复软中断
__local_bh_enable();
}
从上面的处理流程可以看到,软中断处理就是调用open_ softirq()的action参数.这个函数对应的参数是软中断本身(h->action(h)),采用这样的形式,可以在改变softirq_action结构的时候,不会重写软中断处理函数
在进入了软中断的时候,使用了in_interrupt()来防止软中断嵌套,和抢占硬中断环境。然后软中断以开中断的形式运行,软中断的处理随时都会被硬件中断抢占,由于在软中断运行之前调用了local_bh_disable(),所以in_interrupt()为真,不会执行软中断.
来看下in_interrupt() local_bh_disable() __local_bh_enable()的具体代码:
#define in_interrupt() (irq_count())
#define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK))
#define local_bh_disable() /
do { preempt_count() += SOFTIRQ_OFFSET; barrier(); } while (0)
#define __local_bh_enable() /
do { barrier(); preempt_count() -= SOFTIRQ_OFFSET; } while (0)
相当于local_bh_disable设置了preempt_count的SOFTIRQ_OFFSET。In_interrupt判断就会返回一个真值
相应的__local_bh_enable()清除了SOFTIRQ_OFFSET标志
还有几个常用的判断,列举如下:
in_softirq():判断是否在一个软中断环境
hardirq_count():判断是否在一个硬中断环境
local_bh_enable()与__local_bh_enable()作用是不相同的:前者不仅会清除SOFTIRQ_OFFSET,还会调用do_softirq(),进行软中断的处理
上述几个判断的代码都很简单,可自行对照分析
二.Tasklet
注册了两类软中断之后,在什么时候被触发呢?看tasklet_schedule
static inline void tasklet_schedule(struct tasklet_struct *t)
{
//如果tasklet没有置调度标置,也就是说该tasklet没有被调度
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
void fastcall __tasklet_schedule(struct tasklet_struct *t)
{
unsigned long flags;
//把tasklet加到__get_cpu_var(tasklet_vec).list链表头
local_irq_save(flags);
t->next = __get_cpu_var(tasklet_vec).list;
__get_cpu_var(tasklet_vec).list = t;
//激活相应的软中断
raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_restore(flags);
}
这个函数比较简单,不详细分析了
void tasklet_kill(struct tasklet_struct *t)
{
//不允许在中断环境中进行此操作
if (in_interrupt())
printk("Attempt to kill tasklet from interrupt/n");
//一直等待tasklet被调度完
while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
do
yield();
while (test_bit(TASKLET_STATE_SCHED, &t->state));
}
//一直等待tasklet被运行完
tasklet_unlock_wait(t);
//清除调度标志
clear_bit(TASKLET_STATE_SCHED, &t->state);
}
该函数会一直等待该tasklet调度并运行完,可能会睡眠,所以不能在中断环境中使用它
三.Tasklet_action
软中断处理函数其实是tasklet_action与tasklet_hi_action.
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
//禁止本地中断
local_irq_disable();
//per_cpu变量
list = __get_cpu_var(tasklet_vec).list;
//链表置空
__get_cpu_var(tasklet_vec).list = NULL;
//恢复本地中断
local_irq_enable();
//接下来要遍历链表了
while (list) {
struct tasklet_struct *t = list;
list = list->next;
//为了避免竞争,下列操作都是在加锁情况下进行的
if (tasklet_trylock(t)) {
//t->count为零才会调用task_struct里的函数
if (!atomic_read(&t->count)) {
//t->count 为1。但又没有置调度标志。系统BUG
if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
BUG();
//调用tasklet函数
t->func(t->data);
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
//注意 :所有运行过的tasklet全被continue过去了,只有没有运行的tasklet才会重新加入到链表里面
//禁本地中断
local_irq_disable();
//把t放入队列头,准备下一次接收调度
t->next = __get_cpu_var(tasklet_vec).list;
__get_cpu_var(tasklet_vec).list = t;
//置软中断调用标志。下次运行到do_softirq的时候,可以继续被调用
__raise_softirq_irqoff(TASKLET_SOFTIRQ);
//启用本地中断
local_irq_enable();
}
}
高优先级tasklet的处理其实与上面分析的函数是一样的,只是per_cpu变量不同而已。
另外,有几个问题值得考虑:
1) cpu怎么计算软中断优先级的
在do_softirq()à__do_softirq()有:
{
pending = local_softirq_pending();
......
do {
if (pending & 1) {
h->action(h);
rcu_bh_qsctr_inc(cpu);
}
h++;
pending >>= 1;
} while (pending);
......
}
从上面看到,从softirq_vec[]中取项是由pending右移位计算的。
另外,在激活软中断的操作中:
#define __raise_softirq_irqoff(nr) do { local_softirq_pending() |= 1UL << (nr); } while (0)
可以看到 nr越小的就会越早被do_softirq遍历到
2) 在什么条件下才会运行tasklet 链表上的任务
我们在上面的代码里看到只有在t->count为零,且设置了TASKLET_STATE_SCHED标志才会被遍历到链表上对应的函数