中断下半部分
中断分为上半部和下半部分,操作系统把一些紧急的任务,需要硬件参与的任务放在上半部去完成。而把一些不是很急切需要完成的任务放在下半部分来执行。
中断下半部的三种机制
Softirq(软中断)
软中断是一组静态定义的下半部接口,可以在所有处理器上同时执行(即使两个类型相同也可以)。
软中断的结构体
struct softirq_action {
void (*action)(struct softirq_action*);
};
softirq_action这个数组有32项,每个被注册的软中断都会占据其中的一项。
static struct softirq_action softirq_vec[NR_SOFTIRQS]
软中断流程
将软中断注册进softirq_vec数组中
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
对TASKLET_SOFTIRQ和HI_SOFTIRQ两种软中断进行初始化
void __init softirq_init(void)
{
int cpu;
for_each_possible_cpu(cpu) {
per_cpu(tasklet_vec, cpu).tail =
&per_cpu(tasklet_vec, cpu).head;
per_cpu(tasklet_hi_vec, cpu).tail =
&per_cpu(tasklet_hi_vec, cpu).head;
}
/* 开启常规tasklet */
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
/* 开启高优先级tasklet */
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
触发本地cpu上的软中断
void raise_softirq(unsigned int nr)
{
unsigned long flags;
local_irq_save(flags);//关中断
raise_softirq_irqoff(nr);
local_irq_restore(flags);//恢复
}
关中断之后调用raise_softirq_irqoff,通过in_interrupt判断现在是否在中断上下文中,或者软中断是否被禁止,如果都不成立,我们必须要调用wakeup_softirqd函数用来唤醒本CPU上的softirqd这个内核线程。
inline void raise_softirq_irqoff(unsigned int nr)
{
__raise_softirq_irqoff(nr);
if (!in_interrupt())
wakeup_softirqd();
}
当触发软中断之后,我们就需要对软中断进行处理。
asmlinkage __visible void do_softirq(void)
{
__u32 pending;
unsigned long flags;
if (in_interrupt())//在中断处理中,就返回
return;
local_irq_save(flags);//关中断
pending = local_softirq_pending();//取得当前已注册软中断的位图,当第n位为1,则表示需要处理
if (pending && !ksoftirqd_running())//如果ksoftirqd这个内核线程启动了,软中断就不需要现在就处理了,这里显然需要未启动
do_softirq_own_stack();
local_irq_restore(flags);//恢复
}
do_softirq_own_stack调用__do_softirq
static inline void do_softirq_own_stack(void)
{
__do_softirq();
}
__do_softirq
asmlinkage __visible void __softirq_entry __do_softirq(void)
{
unsigned long end = jiffies + MAX_SOFTIRQ_TIME;//为了防止软中断执行时间太长,设置了一个软中断结束时间
unsigned long old_flags = current->flags;//保存当前进程的标志
int max_restart = MAX_SOFTIRQ_RESTART;//软中断循环执行次数
struct softirq_action *h;
bool in_hardirq;
__u32 pending;
int softirq_bit;
/*
* Mask out PF_MEMALLOC s current task context is borrowed for the
* softirq. A softirq handled such as network RX might set PF_MEMALLOC
* again if the socket is related to swap
*/
current->flags &= ~PF_MEMALLOC;//PF_MEMALLOC内存分配
pending = local_softirq_pending();//获取此CPU的__softirq_pending变量值
account_irq_enter_time(current);//用于统计进程被软中断使用时间
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);//增加preempt_count软中断计数器
in_hardirq = lockdep_softirq_start();//避免死锁
//循环的入口
restart:
/* Reset the pending bitmask before enabling irqs */
set_softirq_pending(0);//把当前cpu上的__softirq_pending清零,保证了新的软中断会在下次循环中执行
local_irq_enable();//中断开启
h = softirq_vec;//h指向软中断数组头
while ((softirq_bit = ffs(pending))) {
//每次获取pending第一个1的位置的软中断
unsigned int vec_nr;
int prev_count;
h += softirq_bit - 1;//获取此软中断描述符地址
vec_nr = h - softirq_vec;//减去软中断描述符数组首地址,获得软中断号
prev_count = preempt_count();//获取preempt_count的值
kstat_incr_softirqs_this_cpu(vec_nr);//增加统计该软中断发生次数
trace_softirq_entry(vec_nr);
h->action(h);//执行该软中断的action操作
trace_softirq_exit(vec_nr);
if (unlikely(prev_count != preempt_count())) {
/*
之前保存的preempt_count并不等于当前的preempt_count的情况处理,也是简单的把之前的复制到当
前的preempt_count上,这样做是防止最后软中断计数不为0导致系统不能够执行调度
*/
pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
vec_nr, softirq_to_name[vec_nr], h