硬中断 软中断

一、为什么需要软中断?

要理解软中断,首先要明确硬中断的局限性

  1. 硬中断执行时会关闭本地 CPU 的中断(至少关闭对应中断线),如果硬中断处理函数耗时过长,会导致其他硬件中断无法及时响应,甚至丢失中断(比如网卡丢包)。
  2. 硬中断是硬件触发的,执行上下文是中断上下文,不能睡眠、不能调度,只能处理最紧急的硬件操作。
  3. 硬中断无法并行:同一个中断线的硬中断不能嵌套,即使在多核 CPU 上,同一个硬中断处理函数也只能串行执行。

因此,内核将中断处理拆分为两个阶段:

  • 上半部(Top Half) :由硬中断处理,只做最紧急、最快速的操作(比如读取网卡寄存器、把数据从硬件拷贝到内核 Ring Buffer、标记中断状态),然后触发软中断,立即返回。
  • 下半部(Bottom Half) :由软中断执行,处理耗时但不紧急 的操作(比如解析网络包、协议栈处理、数据拷贝到用户态),执行时开中断,可以被硬中断抢占,多核 CPU 上还能并行执行。

二、软中断的核心特性

1. 与硬中断的核心区别

特性 硬中断 软中断
触发方式 硬件设备主动触发(通过中断控制器) 内核代码主动触发(raise_softirq()
执行时机 硬件中断发生后立即执行 硬中断返回时、ksoftirqd 线程中、显式调度时
中断状态 执行时关闭本地对应中断 执行时开启所有中断
执行上下文 中断上下文,不能睡眠、不能调度 中断上下文,不能睡眠、不能调度
并发性 不可嵌套,同中断线串行执行 多核 CPU 上可并行执行(不同 CPU)
优先级 最高 低于硬中断,高于普通进程

2. 软中断的本质

软中断本质是内核预定义的一组全局函数指针数组 ,每个数组元素对应一个软中断类型,包含该软中断的处理函数。内核通过pending 位图标记哪些软中断需要执行,当触发软中断时,只需要将对应位图位置 1,内核会在合适的时机执行对应的处理函数。

三、软中断的内核实现机制

1. 软中断的定义与注册

Linux 内核中,软中断的最大数量固定为 32 个(目前只使用了前 10 个),通过softirq_vec数组管理,定义如下:

cpp 复制代码
struct softirq_action {
    void (*action)(struct softirq_action *); // 软中断处理函数
};
struct softirq_action softirq_vec[32];

内核启动时,通过open_softirq()函数注册不同类型软中断的处理函数,例如网络接收软中断的注册:

cpp 复制代码
open_softirq(NET_RX_SOFTIRQ, net_rx_action); // 注册网络接收软中断
open_softirq(NET_TX_SOFTIRQ, net_tx_action); // 注册网络发送软中断

2. 常见的软中断类型

内核预定义的软中断类型(优先级从高到低):

软中断类型 数值 用途
HI_SOFTIRQ 0 高优先级 tasklet(处理紧急的小任务)
TIMER_SOFTIRQ 1 内核定时器(处理超时事件)
NET_TX_SOFTIRQ 2 网络数据包发送
NET_RX_SOFTIRQ 3 网络数据包接收(最常用的软中断之一)
BLOCK_SOFTIRQ 4 块设备 I/O 处理(磁盘、SSD 等)
IRQ_POLL_SOFTIRQ 5 中断轮询(替代传统硬中断的轮询机制)
TASKLET_SOFTIRQ 6 普通 tasklet(驱动开发常用)
SCHED_SOFTIRQ 7 进程调度(负载均衡、唤醒进程)
HRTIMER_SOFTIRQ 8 高精度定时器
RCU_SOFTIRQ 9 RCU 锁的延迟回收(内核同步机制)

3. 软中断的触发

软中断通过raise_softirq()函数触发,核心操作是将对应软中断的 pending 位图位置 1。例如,网卡硬中断处理函数中触发网络接收软中断:

cpp 复制代码
raise_softirq(NET_RX_SOFTIRQ);

注意:如果触发软中断时,当前 CPU 正在处理硬中断,软中断不会立即执行,而是等待硬中断返回后再执行。

4. 软中断的执行时机

内核会在以下 3 个关键时机检查并执行 pending 的软中断:

  1. 硬中断处理完成后 :这是最常见的执行时机,硬中断返回用户态 / 内核态前,会调用do_softirq()处理所有 pending 的软中断。
  2. 内核线程 ksoftirqd 中 :如果软中断执行时间过长(比如网络突发大量数据包),内核会唤醒每个 CPU 对应的ksoftirqd/n内核线程,专门处理软中断,避免软中断占满 CPU 导致用户进程饿死。
  3. 显式调用local_bh_enable():当内核代码主动开启下半部时,会检查并执行 pending 的软中断。

5. 软中断的处理流程

网络包接收为例,完整的软中断处理流程如下:

  1. 网卡收到数据包,通过 DMA 将数据拷贝到内核的 Ring Buffer,触发硬中断。
  2. 网卡硬中断处理函数执行:
    • 读取网卡状态,确认是接收中断。
    • 关闭网卡接收中断(NAPI 机制),避免持续触发硬中断。
    • 调用raise_softirq(NET_RX_SOFTIRQ),标记网络接收软中断为 pending。
    • 硬中断处理完成,返回。
  3. 硬中断返回前,内核调用do_softirq()
    • 遍历 pending 位图,找到需要执行的软中断(NET_RX_SOFTIRQ)。
    • 调用对应的处理函数net_rx_action()
    • net_rx_action()从 Ring Buffer 中批量读取数据包,封装为sk_buff结构体,交给 TCP/IP 协议栈逐层处理(IP 层→TCP 层→Socket 层)。
    • 处理完成后,清除 pending 位图,开启网卡接收中断(NAPI 机制)。
  4. 如果net_rx_action()处理超时(默认最多处理 2ms),剩余的数据包会交给ksoftirqd内核线程继续处理。

四、软中断的衍生机制:Tasklet

软中断虽然性能高,但存在两个问题:

  1. 同一个软中断的处理函数可以在多个 CPU 上并行执行,驱动开发者需要自行处理多核并发问题,容易出错。
  2. 软中断的类型是内核预定义的,驱动不能动态添加新的软中断类型。

为了解决这些问题,内核基于TASKLET_SOFTIRQHI_SOFTIRQ封装了Tasklet 机制,是驱动开发中最常用的下半部方式。

1. Tasklet 的核心特性

  • 串行执行:同一个 Tasklet 只会在一个 CPU 上执行,不会在多个 CPU 上并行运行,无需考虑多核并发。
  • 动态创建:驱动可以随时创建和销毁 Tasklet,无需修改内核代码。
  • 优先级区分 :高优先级 Tasklet 基于HI_SOFTIRQ,普通 Tasklet 基于TASKLET_SOFTIRQ

2. Tasklet 的使用示例

cpp 复制代码
// 定义Tasklet处理函数
void my_tasklet_handler(unsigned long data) {
    // 处理耗时但不紧急的操作(不能睡眠)
    printk("Tasklet executed, data: %ld\n", data);
}

// 声明并初始化Tasklet
DECLARE_TASKLET(my_tasklet, my_tasklet_handler, 123);

// 硬中断处理函数中触发Tasklet
irqreturn_t my_interrupt(int irq, void *dev_id) {
    // 上半部:快速处理硬件操作
    tasklet_schedule(&my_tasklet); // 触发Tasklet
    return IRQ_HANDLED;
}

五、软中断 vs Tasklet vs 工作队列

内核中常用的延迟执行机制有三种:软中断、Tasklet、工作队列,它们的适用场景完全不同:

表格

特性 软中断 Tasklet 工作队列
执行上下文 中断上下文 中断上下文 进程上下文
能否睡眠 不能 不能 可以(睡眠、调度)
并发性 多核可并行 同 Tasklet 串行 多核可并行
实时性 最高
适用场景 内核核心子系统(网络、块设备) 驱动开发(简单延迟任务) 需要睡眠的延迟任务
开发难度 高(需处理并发) 低(无需处理并发) 低(和普通进程一致)

六、软中断的性能优化与常见问题

1. 软中断 CPU 占用过高

当系统出现软中断 CPU 占比过高 (通过top命令查看si指标)时,通常是以下原因:

  • 网络突发流量 :大量网络包导致NET_RX_SOFTIRQ持续执行,此时需要优化网络协议栈、开启 RPS/RFS(多 CPU 分发网络包)。
  • 块设备 I/O 密集 :大量磁盘读写导致BLOCK_SOFTIRQ占用过高,此时需要优化 I/O 调度算法、使用 SSD。
  • 软中断执行时间过长:软中断处理函数中存在耗时操作,需要进一步拆分任务,交给工作队列处理。

2. ksoftirqd 内核线程的作用

每个 CPU 对应一个ksoftirqd/n内核线程,当软中断的执行时间超过阈值(默认 2ms),或者软中断被连续触发时,内核会将剩余的软中断交给ksoftirqd处理。这样可以避免软中断长时间占用 CPU,导致用户进程无法得到调度。

3. NAPI 机制与软中断的结合

NAPI(New API)是 Linux 网络子系统的核心优化,它将中断 + 轮询结合,大幅减少了硬中断的触发次数:

  • 当网卡收到第一个包时,触发硬中断,关闭网卡中断,触发NET_RX_SOFTIRQ
  • 软中断处理函数net_rx_action()轮询 Ring Buffer,批量处理所有已到达的数据包。
  • 处理完成后,重新开启网卡中断,等待下一个包的到来。

这种方式避免了每个数据包都触发一次硬中断,在高并发网络场景下性能提升显著。

七、总结

软中断是 Linux 内核实现高性能异步处理的核心机制,它通过拆分中断处理的上下半部,解决了硬中断耗时过长的问题。其核心要点:

  1. 软中断是内核态、不可睡眠、可并行的延迟执行机制,优先级高于普通进程。
  2. 网络、块设备、定时器等内核核心子系统都依赖软中断实现高性能处理。
  3. Tasklet 是软中断的封装,简化了驱动开发;工作队列则用于需要睡眠的延迟任务。
  4. 软中断的性能瓶颈通常出现在高并发网络或 I/O 场景,可通过 NAPI、多 CPU 分发、硬件加速等方式优化。
相关推荐
妖孽白YoonA1 小时前
xlt-token v1.0.0 正式发布:NestJS / Express 一包接入的 Token 鉴权库
后端·node.js·nestjs
MariaH1 小时前
Stream读写操作
后端
Oo_行者_oO1 小时前
Spring Authorization Server 下 Token 刷新流程自定义实现
后端·面试
alwaysrun1 小时前
C++之灵活易用的YAML解析库yaml-cpp
c++·后端·程序员
pe7er1 小时前
AI为啥会写出if(obj != null && obj.ifEnabled)这样的代码
前端·后端·架构
狗凯之家源码网2 小时前
电商代付系统从零搭建与实战指南
前端·后端·开源
IT_陈寒2 小时前
Vue组件通信这个坑我跳了两次才知道怎么爬出来
前端·人工智能·后端
copyer_xyf2 小时前
Python 文件基本操作
前端·后端·python
西凉的悲伤2 小时前
Spring Security + JWT 登录认证完整实践指南
java·后端·spring·spring security·jwt