一、为什么需要中断下半部?
硬件中断处理是操作系统中最关键的执行路径之一。当一个硬件设备(网卡、磁盘、键盘等)需要CPU关注时,它会触发硬件中断,CPU会暂停当前工作去执行中断处理程序。但问题在于:中断处理程序执行时间越长,系统的响应性和吞吐量就越差。
1.1 中断的代价
当CPU响应硬件中断时,当前正在执行的进程会被打断。如果中断处理程序执行时间过长(比如网卡收到一个巨大的TCP数据包后在中断中完成整个协议栈处理),会导致:
- 中断屏蔽时间过长:在x86架构上,
local_irq_disable()会屏蔽当前CPU的所有中断,时间过长可能导致丢失后续中断 - 实时性下降:被抢占的进程长时间得不到CPU时间片,用户体验卡顿
- 吞吐量降低:高负载下中断堆积,系统进入"活锁(livelock)"状态——CPU时间全花在处理中断上,没有时间处理中断产生的数据
1.2 上下半部分离的设计哲学
Linus Torvalds在早期Linux内核开发中就引入了"上下半部分离"(top half/bottom half)的设计思想:
- 上半部(Top Half):中断上下文中的紧急处理——ACK中断、读取硬件寄存器状态、将数据从FIFO复制到内存
- 下半部(Bottom Half):非紧急的延迟处理——协议栈处理、数据分发、用户态通知
这种分离使得上半部执行时间极短,最小化中断关闭窗口,同时将耗时工作推迟到更安全、更灵活的上下文中执行。
二、软中断(softirq)机制详解
softirq是Linux内核中最底层的下半部机制,它在2.3版本中取代了传统的BH(Bottom Half)机制。softirq是静态编译时注册的,不支持动态创建,但拥有最高的执行优先级。
2.1 软irq向量表
内核通过一个枚举softirq_nr定义了所有预注册的软中断向量:
enum {
HI_SOFTIRQ = 0, // tasklet_hi_action() — 最高优先级
TIMER_SOFTIRQ, // run_timer_list() — 定时器
NET_TX_SOFTIRQ, // net_tx_action() — 网络发送
NET_RX_SOFTIRQ, // net_rx_action() — 网络接收
BLOCK_SOFTIRQ, // blk_done_softirq() — IO完成
IRQ_POLL_SOFTIRQ, // irq_poll_cpu() — IO轮询
TASKLET_SOFTIRQ, // tasklet_action() — 普通tasklet
SCHED_SOFTIRQ, // run_rebalance_domains() — 调度均衡
HRTIMER_SOFTIRQ, // run_hrtimer_callbacks() — 高精度定时器
RCU_SOFTIRQ, // rcu_core() — RCU回调
NR_SOFTIRQS // 总数(当前为10)
};
软中断向量编号从0到NR_SOFTIRQS-1,编号越小优先级越高。HI_SOFTIRQ被保留给高优先级tasklet使用(如音频缓冲区填充),而NET_RX_SOFTIRQ则是网络数据包接收的核心路径。
2.2 软irq的数据结构
每个CPU维护两个关键数据结构:
// 每个CPU的软irq待处理位图(32位,每位对应一个软irq向量)
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
// 每CPU变量:记录哪些软irq被挂起
irq_cpustat_t irq_stat[NR_CPUS];
// 其中 irq_stat[cpu].__softirq_pending 是位图
这种设计保证了同类型的软中断可以在所有CPU上同时执行(不同向量),而同一个向量在同一个CPU上不会重入。
2.3 软irq的执行模型
软中断的执行发生在三个时机:
- 硬件中断返回时:
irq_exit()→invoke_softirq() - ksoftirqd内核线程:当软中断被挂起次数过多时,唤醒
ksoftirqd/N进程在进程上下文中执行 - 显式调度点:网络子系统(NAPI)等代码在合适时机主动调用
raise_softirq()
核心的执行循环位于do_softirq()函数中:
asmlinkage __visible void __softirq_entry __do_softirq(void)
{
unsigned long old_irqs = local_softirq_pending();
int max_restart = MAX_SOFTIRQ_RESTART; // 默认10次
// 循环处理挂起的软中断
do {
// 取出挂起的位图
unsigned long pending = local_softirq_pending();
local_irq_enable(); // 开中断,允许新中断
if (pending) {
// 遍历所有置位的软irq向量
h = softirq_vec;
// ...执行每个注册的handler
while ((softirq_bit = ffs(pending))) {
h = softirq_bit - 1;
h->action(h); // 执行软中断处理函数
h ;
pending >>= softirq_bit;
}
}
local_irq_disable(); // 关中断,检查是否又收到了新软中断
max_restart--;
} while (pending

发表评论 取消回复