local_softirq_pending位何时置位
硬中断执行结束在退出之前,都会在irq_exit中判断如果有pending的软中断就触发软中断:内核代码通过调用raise_softirq_irqoff来触发相应的软中断,将相应的bit位置位,这样在irq_exit中判断就有pending的软中断,进而触发执行相应的软中断(直接执行软中断或触发软中断线程):raise_softirq_irqoff函数调用__raise_softi...
硬中断执行结束在退出之前,都会在irq_exit中判断如果有pending的软中断就触发软中断:

内核代码通过调用raise_softirq_irqoff来触发相应的软中断,将相应的bit位置位,这样在irq_exit中判断就有pending的软中断,进而触发执行相应的软中断(直接执行软中断或触发软中断线程):

raise_softirq_irqoff函数调用__raise_softirq_irqoff设置了pending位后,调用in_interrupt判断当前上下文是不是处于中断中(软中断或硬中断),如果处于中断上下文就什么也不做(因为若是处于硬中断上下文,irq_exit就会执行软中断;如果处于软中断上下文,在软中断退出时也会执行到)。否则唤醒软中断线程执行,避免软中断执行有延迟。


在linux内核中通过 raise_softirq 触发一个软中断,但这个软中断什么时候执行呢?
调用 raise_softirq() 并不会立即执行软中断处理函数。它只是做了一个“标记”动作(在每 CPU 变量的位图上置位)。
软中断真正执行的时机,取决于当前上下文是否允许执行以及系统负载情况。主要有以下三个执行时机:
1. 硬中断返回时(最常见、延迟最低)
这是最典型的场景。当你是在硬中断处理函数(Top Half)中调用 raise_softirq() 时:
- 流程:
- 硬中断函数执行
raise_softirq(),标记 pending 位。 - 硬中断函数返回。
- 内核在退出中断栈之前(汇编代码
ret_from_intr或 C 代码irq_exit_rcu中),会检查是否有 pending 的软中断。 - 如果有,且当前不在其他软中断上下文中,立即调用
do_softirq()执行。
- 硬中断函数执行
- 特点:延迟极低,几乎紧跟着硬中断执行。此时中断已开启,但处于中断上下文。
2. 显式启用底半部时(local_bh_enable())
如果你是在内核线程、进程上下文或某些禁止底半部的临界区之后调用 raise_softirq()(或者在临界区内标记,出来后检查):
- 流程:
- 代码执行
local_bh_enable()。 - 该宏会检查:当前是否没有屏蔽底半部?是否有 pending 的软中断?
- 如果满足条件,立即调用
do_softirq()执行。
- 代码执行
- 典型场景:网络协议栈中,某些处理逻辑在禁用底半部后触发了新的软中断,一旦解除屏蔽就会立刻处理。
3. 由 ksoftirqd 内核线程异步执行(延迟稍高,防饿死)
如果出现以下两种情况之一,软中断不会在当前上下文立即执行,而是被推迟到专门的内核线程 ksoftirqd/x 中执行:
- 情况 A:当前上下文不允许
- 例如在持有自旋锁(Spinlock)且禁止调度的情况下触发了软中断,为了防止死锁或长时间占用 CPU,可能推迟执行。
- 或者在某些特定的原子上下文中。
- 情况 B:软中断负载过高(保护机制)
- 如果在第 1 或第 2 种场景中,软中断执行时间过长,或者不断有新的软中断产生导致无法退出循环。
- 内核检测到软中断处理耗时超过阈值(通常是
SOFTIRQ_TIME_LIMIT,约 2ms)或重试次数过多。 - 动作:当前上下文停止执行软中断,唤醒对应的
ksoftirqd/x线程,由该线程在进程上下文中继续处理剩余的软中断。
- 特点:避免了单个 CPU 因处理软中断过久而导致调度器无法运行(Starvation),保证了系统的响应性。
🕵️♂️ 深度源码逻辑解析
让我们看看内核是如何决定“现在执行”还是“稍后执行”的。核心逻辑在 irq_exit_rcu() 和 __do_softirq() 中。
步骤一:标记 (Raise)
// include/linux/interrupt.h
static inline void raise_softirq(unsigned int nr) {
// 只是设置当前 CPU 的 pending 位图
__raise_softirq_irqoff(nr);
}
步骤二:检查与决策 (Check & Dispatch)
当硬中断返回或调用 local_bh_enable() 时,会进入类似以下的逻辑(简化版):
// kernel/softirq.c (irq_exit_rcu 的一部分)
void irq_exit_rcu(void) {
// ... 其他清理工作 ...
// 关键判断:
// 1. !in_interrupt(): 确保我们不是在嵌套的中断里(虽然软中断可重入,但这里指顶层退出)
// 2. local_softirq_pending(): 检查有没有待处理的软中断
if (!in_interrupt() && local_softirq_pending()) {
do_softirq(); // 尝试立即执行
}
}
步骤三:执行引擎 (do_softirq)
// kernel/softirq.c
void do_softirq(void) {
__u32 pending = local_softirq_pending();
if (pending) {
// 如果当前已经是软中断上下文,或者某些特殊标志,可能直接跳去 __do_softirq
// 否则,为了安全,可能会通过 stub 进入
// 核心执行函数
__do_softirq();
}
}
步骤四:核心循环与降级 (__do_softirq)
// kernel/softirq.c
static void __do_softirq(void) {
unsigned long end = jiffies + MAX_SOFTIRQ_TIME; // 时间限制 (约 2ms)
int max_restart = MAX_SOFTIRQ_RESTART; // 重启次数限制
struct softirq_action *h;
__u32 pending;
pending = local_softirq_pending();
set_softirq_pending(0); // 【重要】先清零,防止死循环
open_softirq(); // 开启中断 (此时 CPU 中断是开的)
while (pending) {
for (h = softirq_vec; h < softirq_vec + NR_SOFTIRQS; h++) {
if (pending & (1 << h->index)) {
h->action(h); // 执行具体的软中断函数
}
}
// 【保护机制检查】
// 如果时间到了,或者重启次数太多了,或者需要调度了
if (time_after_eq(jiffies, end) ||
--max_restart <= 0 ||
need_resched()) {
// 停止当前执行,唤醒 ksoftirqd 线程来处理剩下的
wakeup_ksoftirqd();
break;
}
pending = local_softirq_pending(); // 检查是否有新产生的软中断
if (pending) {
set_softirq_pending(0); // 再次清零,准备下一轮
}
}
}
📊 总结:到底什么时候执行?
全屏复制
| 触发场景 | 执行时机 | 执行上下文 | 备注 |
|---|---|---|---|
| 硬中断中触发 | 硬中断函数返回后立即执行 | 中断上下文 (Interrupt Context) | 最快,绝大多数情况。 |
| 底半部启用时触发 | local_bh_enable() 调用时立即执行 |
进程上下文 / 内核线程 | 常见于协议栈内部逻辑。 |
| 负载过高/时间片耗尽 | 当前上下文放弃,稍后由 ksoftirqd 执行 |
内核线程 (ksoftirqd/x) |
防止系统卡死,保证调度。 |
| 显式唤醒 | 用户手动 wake_up_process(ksoftirqd) |
内核线程 (ksoftirqd/x) |
极少见,通常由内核自动处理。 |
一句话总结:raise_softirq() 只是举手示意“我有活要干”,内核会优先在当前中断返回或解锁时立刻干;但如果活太多干不完,就会叫专门的后台线程 ksoftirqd 来接着干。
更多推荐

所有评论(0)