我们知道,中断处理程序被分为上半部函数和下半部函数。而软中断、微任务、工作队列都是是下半部函数的机制。(至于工作队列的方式,她是以另一个线程的方式实现的,初始化时创建,调用时唤醒,其实request_thread_irq是将中断线程化,包括利用定时器实现延时处理,这都不是在中断上下文中)
软中断和微任务都是通过
do_softirq执行的。
中断处理在do_IRQ中调用软中断 do_softirq
中断处理在do_IRQ中调用软中断 do_softirq
unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
{
......
irq_exit();
}
void irq_exit(void)
{
......
invoke_softirq(); //调用软irq处理
}
#ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
# define invoke_softirq() __do_softirq()
#else
# define invoke_softirq() do_softirq()
#endif
软中断:
获取软中断向量表
asmlinkage void do_softirq(void)
{
__u32 pending;
unsigned long flags;
// 这个函数判断,如果当前有硬件中断嵌套,或有软中断正在执行时候,则马上返回
//入口判断主要是为了和 ksoftirqd 互斥。
if (in_interrupt())
return;
local_irq_save(flags);// 关中断执行以下代码
pending = local_softirq_pending();// 判断是否有 pending 的软中断需要处理。
if (pending) // 如果有则调用 __do_softirq() 进行实际处理
__do_softirq();
local_irq_restore(flags); // 开中断继续执行
}
asmlinkage void __do_softirq(void)
{
struct softirq_action *h; // 软件中断处理结构,此结构中包括了 ISR 中 注册的回调函数。
__u32 pending;
unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
int cpu;
int max_restart = MAX_SOFTIRQ_RESTART;
pending = local_softirq_pending(); // 得到当前所有 pending 的软中断。
account_system_vtime(current);
/* 执行到这里要屏蔽其他软中断,每个 CPU 上同时运行的软中断只能有一个。*/
__local_bh_disable((unsigned long)__builtin_return_address(0),
SOFTIRQ_OFFSET);
lockdep_softirq_enter();
cpu = smp_processor_id(); // 针对 SMP 得到当前正在处理的 CPU
restart:
// 每次循环在允许硬件 ISR 强占前,首先重置软中断的标志位。
/* Reset the pending bitmask before enabling irqs */
set_softirq_pending(0);
// 开中断运行,(注意:以前运行状态一直是关中断运行)
//这里以后的代码才可能被硬件中断抢占。
local_irq_enable();
h = softirq_vec; //获取软中断向量表
do {
if (pending & 1) { // 如果对应的软中断设置 pending 标志则表明需要进一步处理他所注册的函数。
int prev_count = preempt_count();
kstat_incr_softirqs_this_cpu(h - softirq_vec);
trace_softirq_entry(h, softirq_vec);
h->action(h); // 在这里执行了这个软中断所注册的回调函数。
trace_softirq_exit(h, softirq_vec);
if (unlikely(prev_count != preempt_count())) {
printk(KERN_ERR "huh, entered softirq %td %s %p"
"with preempt_count %08x,"
" exited with %08x?\n", h - softirq_vec,
softirq_to_name[h - softirq_vec],
h->action, prev_count, preempt_count());
preempt_count() = prev_count;
}
rcu_bh_qs(cpu);
}
h++;// 继续找,直到把软中断向量表中所有 pending 的软中断处理完成。
pending >>= 1; // 从代码里能看出按位右移操作,表明一次循环只处理 32 个软中断的回调函数
} while (pending);
local_irq_disable(); // 再次关中断
pending = local_softirq_pending();
if (pending) {
if (time_before(jiffies, end) && !need_resched() &&
--max_restart) // 如果刚才触发的硬件中断注册了软中断,并且重复执行次数没有到 max_restart个 次的话
goto restart;
//超过max_restart个硬件中断注册了软中断,满负荷,需要另开线程处理软中断
// 此函数实际是调用 wake_up_process() 来唤醒 ksoftirqd
wakeup_softirqd();
}
lockdep_softirq_exit();
account_system_vtime(current);
// 到最后才开软中断执行环境,允许软中断执行。注意:这里
// 使用的不是 local_bh_enable(),不会再次触发 do_softirq()的调用。
__local_bh_enable(SOFTIRQ_OFFSET);
}
void wakeup_softirqd(void)
{
/* Interrupts are disabled: no need to stop preemption */
struct task_struct *tsk = __get_cpu_var(ksoftirqd); //ksoftirqd 线程
if (tsk && tsk->state != TASK_RUNNING)
wake_up_process(tsk); //唤醒ksoftirqd线程
}
static int ksoftirqd(void * __bind_cpu)
{
// 设置当前进程状态为可中断的状态,
set_current_state(TASK_INTERRUPTIBLE);
current->flags |= PF_KSOFTIRQD; // 设置当前进程不允许被挂起
while (!kthread_should_stop()) { //循环判断当前进程是否会停止
preempt_disable();// 禁止当前进程被抢占。
if (!local_softirq_pending()) { // 首先判断系统当前没有需要处理的 pending 状态的软中断
// 没有的话在主动放弃 CPU 前先要允许抢占,因为一直是在不允许抢占状态下执行的代码。
preempt_enable_no_resched();
// 主动放弃 CPU 将当前进程放入睡眠队列,并转换新的进程执行(调度器相关不记录在此),但此进程再次被唤醒时,直接执行下一语句。
schedule();
preempt_disable(); // 当进程再度被调度时,在以下处理期间内禁止当前进程被抢占。
}
__set_current_state(TASK_RUNNING); // 设置当前进程为运行状态。
/*
* 循环判断是否有 pending 的软中断,如果有则调用 do_softirq()来做具体处理。
* 注意:这里又是个 do_softirq() 的入口点,
* 那么在 __do_softirq() 当中循环处理 10 次软中断的回调函数后,
* 如果更有 pending 的话,会又调用到这里
* 那么在这里则又会有可能去调用 __do_softirq() 来处理软中断回调函数。
* 在__do_softirq()中,处理 10 次还处理不完的话说明系统正处于繁忙状态。
* 综上,在系统非常繁忙时,这个进程将会和 do_softirq() 相互交替执行,
* 这时此进程占用 CPU 应该会非常高,
*/
while (local_softirq_pending()) {
/* Preempt disable stops cpu going offline.
If already offline, we'll be on wrong CPU:
don't process */
if (cpu_is_offline((long)__bind_cpu))// 如果当前被关联的 CPU 无法继续处理则跳转
goto wait_to_die; // 到 wait_to_die 标记出,等待结束并退出。
// 执行 do_softirq() 来处理具体的软中断回调函数。
//如果此时有一个正在处理的软中断的话,则会马上返回 注意in_interrupt()
do_softirq();
preempt_enable_no_resched();// 允许当前进程被抢占。
// 这个函数有可能间接的调用 schedule() 来转换当前进程,而且上面已允许当前进程可被抢占。
//也就是说在处理完一轮软中断回调函数时,有可能会转换到其他进程。
cond_resched();
preempt_disable(); // 禁止当前进程被抢占。
rcu_sched_qs((long)__bind_cpu);
}// 处理完所有软中断了吗?没有的话继续循环以上步骤
preempt_enable();// 允许当前进程被抢占
set_current_state(TASK_INTERRUPTIBLE); // 设置当前进程状态为可中断的状态,
}
__set_current_state(TASK_RUNNING);// 设置当前进程为运行状态
return 0;
wait_to_die:// 一直等待到当前进程被停止
preempt_enable();
/* Wait for kthread_stop */
set_current_state(TASK_INTERRUPTIBLE);
// 判断当前进程是否会被停止,如果不是的话则设置进程状态为可中断状态并放弃当前 CPU 主动转换。
// 也就是说这里将一直等待当前进程将被停止时候才结束。
while (!kthread_should_stop()) {
schedule();
set_current_state(TASK_INTERRUPTIBLE);
}
// 如果将会停止则设置当前进程为运行状态后直接返回。调度器会根据优先级来使当前进程运行。
__set_current_state(TASK_RUNNING);
return 0;
}