一、为什么需要中断下半部?

硬件中断处理是操作系统中最关键的执行路径之一。当一个硬件设备(网卡、磁盘、键盘等)需要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的执行模型

软中断的执行发生在三个时机:

  1. 硬件中断返回时:irq_exit() → invoke_softirq()
  2. ksoftirqd内核线程:当软中断被挂起次数过多时,唤醒ksoftirqd/N进程在进程上下文中执行
  3. 显式调度点:网络子系统(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                        
                    
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部