在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一、前置思考

1.1 中断是系统的"神经系统"

硬件(传感器、网卡、按键、定时器)怎么通知 CPU"有事发生"?答案是中断

按键按下 → 按键控制器发中断 → CPU 暂停当前任务 → 处理按键 → 恢复任务
网络数据到达 → 网卡发中断 → CPU 暂停 → 拷贝数据 → 恢复
传感器数据就绪 → 发中断 → CPU 读取 → 恢复

中断处理快慢直接决定系统实时性:中断响应延迟 1ms,音频就爆音;延迟 100ms,按键就失灵。

1.2 中断处理不当的灾难

❌ 灾难1: 中断处理函数里做耗时操作
   → 中断长时间关中断,其他中断全堵住 → 系统"死机"

❌ 灾难2: 高负载下中断风暴
   → 网卡频繁中断,CPU 全在跑中断 → 用户任务饿死

❌ 灾难3: 中断线程优先级错误
   → 关键中断延迟处理 → 音频/传感器丢数据

1.3 本文价值

剖析硬中断/软中断/Bottom Half 下半部三级处理机制、中断嵌套中断线程化中断亲和性绑定,讲清"中断来了怎么跑、怎么不堵、怎么绑核"。

二、核心原理

2.1 中断处理三级流水线

┌─────────────────────────────────────────┐
│ ① 硬中断 (Top Half 上半部)               │
│    硬件触发 → 立即响应 (us 级)            │
│    只做最少的事: 确认中断 + 标记 + 唤醒    │
├─────────────────────────────────────────┤
│ ② 软中断 (Softirq)                      │
│    内核延后处理: 网络收包/块设备/调度     │
│    在安全点批量执行 (不关中断)            │
├─────────────────────────────────────────┤
│ ③ Bottom Half (下半部: tasklet/workqueue)│
│    最耗时的部分延后到进程上下文            │
│    tasklet: 软中断上下文 (不可睡眠)        │
│    workqueue: 进程上下文 (可睡眠)          │
└─────────────────────────────────────────┘

关键思想:中断处理必须快进快出——硬中断只标记,重活交给下半部延后处理。

2.2 硬中断 vs 软中断

硬中断 (Hard IRQ):
  由硬件触发(GIC 中断控制器分发)
  响应要求: 尽快(us 级)
  约束: 关中断执行,不能睡眠
  处理: 置标志 → 触发软中断 → 返回

软中断 (Softirq):
  由内核触发(软件事件)
  执行时机: 中断返回前 / 调度点
  约束: 可以开中断,不能睡眠
  典型: NET_RX(收包) / NET_TX(发包) / BLOCK(块设备)

2.3 中断嵌套与线程化

中断嵌套:
  高优先级中断可以打断低优先级中断处理
  例: 网卡中断处理中被时钟中断打断
  风险: 嵌套过深 → 栈溢出 + 响应变慢
  原则: 硬中断处理尽量短,减少嵌套窗口

中断线程化 (Threaded IRQ):
  将中断处理放到内核线程中(进程上下文)
  优点: 可睡眠、可被调度、降低优先级饥饿
  适用: 非关键实时、可延后的中断处理

三、源码/API 深度解析

3.1 中断注册与处理(驱动视角)

// Linux/HDF 驱动: 中断注册
#include <linux/interrupt.h>

// 中断处理函数(上半部)
static irqreturn_t sensor_irq_handler(int irq, void *dev_id)
{
    // 1. 确认中断(读状态寄存器)
    unsigned int status = readl(SENSOR_STATUS_REG);
    // 2. 清除中断标志(必须,否则重复触发)
    writel(status, SENSOR_STATUS_REG);
    // 3. 触发下半部处理(tasklet)
    tasklet_schedule(&sensor_tasklet);
    // 4. 快速返回(不在这里做耗时操作)
    return IRQ_HANDLED;
}

// 下半部处理(tasklet: 软中断上下文)
static void sensor_tasklet_handler(unsigned long data)
{
    // 读取传感器数据(耗时操作)
    struct sensor_data *d = (struct sensor_data *)data;
    d->value = readl(SENSOR_DATA_REG);
    // 唤醒等待数据的进程
    wake_up_interruptible(&d->wait_queue);
}

// 注册中断
static int sensor_probe(struct platform_device *pdev)
{
    int irq = platform_get_irq(pdev, 0);
    // 注册: 边沿触发, 共享中断
    return request_irq(irq, sensor_irq_handler,
                       IRQF_TRIGGER_RISING, "sensor", dev);
}

3.2 中断线程化(Threaded IRQ)

// 中断线程化: 将处理放到内核线程
static irqreturn_t audio_irq_thread(int irq, void *dev_id)
{
    // 线程上下文: 可以睡眠、可以等待
    struct audio_dev *a = (struct audio_dev *)dev_id;
    // 等待数据可用
    wait_event_interruptible(a->wait, a->data_ready);
    // 处理音频数据(耗时允许)
    process_audio_data(a);
    return IRQ_HANDLED;
}

// 注册线程化中断 (IRQF_ONESHOT: 处理完成前不重新触发)
request_threaded_irq(
    irq, NULL,               // handler=NULL → 全走线程
    audio_irq_thread,
    IRQF_TRIGGER_LOW | IRQF_ONESHOT,
    "audio", dev);

3.3 中断亲和性(绑定核)

# 中断亲和性: 指定中断由哪个核处理
# /proc/irq/<irq>/smp_affinity (位图)

# 例: 网卡中断 IRQ 132 绑定到核 2 (bit2 = 0x04)
echo 04 > /proc/irq/132/smp_affinity

# 查看当前亲和性
cat /proc/irq/132/smp_affinity

# 意义:
# ① 减少核间中断迁移(缓存命中率高)
# ② 网卡/音频中断绑定特定核 → 其他核专心跑业务
# ③ 关键中断独享核 → 延迟可控

3.4 GIC 中断控制器

GIC (Generic Interrupt Controller) 分发流程:
  外设触发中断 → GIC 识别 → 优先级仲裁
  → 分发到指定核 → CPU 跳转中断向量
  → 执行 handler → EOI (End Of Interrupt)

GIC 特性:
  支持中断优先级(高优先打断低优先)
  支持亲和性(中断指定核)
  支持共享中断(多个设备共用)
  支持 SPI (共享外设中断) / PPI (私有外设中断)

四、企业级实战落地

4.1 中断设计清单

环节 原则 反例
硬中断 只确认+标记,us 级返回 在中断里读大块数据
下半部 tasklet 处理轻量延后 tasklet 里睡眠
workqueue 耗时操作进程上下文 耗时操作放硬中断
嵌套 尽量短,减少嵌套 深层嵌套导致栈溢出
亲和性 关键中断绑核 中断到处迁移

4.2 完整示例:中断机制演示

@Entry
@ComponentV2
struct InterruptHandleDemo {
  @Local stage: number = 0;
  @Local logs: string[] = [];
  @Local totalLatency: number = 0;

  private runInterruptFlow(): void {
    this.logs = [];
    this.stage = 1;
    this.log('⚡ 中断事件到达: 传感器数据就绪');
    setTimeout(() => {
      this.stage = 2;
      this.log('① 硬中断 (Top Half): 确认 + 清标志 (2us)');
      this.log('   → 关中断执行,不做耗时操作');
      setTimeout(() => {
        this.stage = 3;
        this.log('② 软中断 (Softirq): 延后处理挂载');
        this.log('   → 网络收包/块设备在此批量执行');
        setTimeout(() => {
          this.stage = 4;
          this.log('③ 下半部 (tasklet/workqueue):');
          this.log('   → 读取传感器数据 + 唤醒进程');
          this.log('   → 耗时操作放这里,不影响中断响应');
          this.totalLatency = 12;  // 总延迟 12us
        }, 400);
      }, 400);
    }, 400);
  }

  private log(msg: string): void {
    this.logs.push(msg);
  }

  build() {
    Column({ space: 12 }) {
      Text('⚡ 中断处理底层机制').fontSize(20).fontWeight(FontWeight.Bold)

      // 三级流水线
      Row({ space: 8 }) {
        Column({ space: 4 }) {
          Text('①硬中断').fontSize(12).fontWeight(FontWeight.Bold)
          Text('2us').fontSize(10).fontColor('#4FC3F7')
        }.layoutWeight(1).padding(8)
          .backgroundColor(this.stage >= 2 ? 'rgba(79,195,247,0.2)' : 'rgba(255,255,255,0.05)')
          .borderRadius(8)

        Column({ space: 4 }) {
          Text('②软中断').fontSize(12).fontWeight(FontWeight.Bold)
          Text('延后').fontSize(10).fontColor('#69F0AE')
        }.layoutWeight(1).padding(8)
          .backgroundColor(this.stage >= 3 ? 'rgba(105,240,174,0.2)' : 'rgba(255,255,255,0.05)')
          .borderRadius(8)

        Column({ space: 4 }) {
          Text('③下半部').fontSize(12).fontWeight(FontWeight.Bold)
          Text('耗时操作').fontSize(10).fontColor('#FFD54F')
        }.layoutWeight(1).padding(8)
          .backgroundColor(this.stage >= 4 ? 'rgba(255,213,79,0.2)' : 'rgba(255,255,255,0.05)')
          .borderRadius(8)
      }
      .width('100%')

      Button('▶ 模拟中断处理流程').width('100%').height(46)
        .onClick(() => this.runInterruptFlow())

      if (this.totalLatency > 0) {
        Text('总延迟: ' + this.totalLatency + 'us(满足实时要求)')
          .fontSize(13).fontColor('#69F0AE').width('100%')
      }

      Scroll() {
        Column() {
          ForEach(this.logs, (l: string) => {
            Text(l).fontSize(11).lineHeight(18).fontColor('rgba(255,255,255,0.8)').width('100%')
          }, (l: string, i: number) => l + i)
        }.width('100%')
      }
      .layoutWeight(1).width('100%').scrollBar(BarState.Off)
    }
    .width('100%').height('100%').padding(16)
    .backgroundColor('#0D1B2A')
  }
}

4.3 中断延迟预算

中断端到端延迟预算:
  硬件触发到 CPU 响应: 1~3us
  硬中断处理: 2~5us(只确认+标记)
  软中断批量执行: 5~20us(网络收包)
  下半部进程调度: 100us+(可延后)
  ──────────────────────────
  关键路径 (硬中断): < 10us ✅
  非关键路径 (下半部): 可容忍延迟

五、问题排查与性能优化

问题 原因 解决
系统"卡死" 中断处理耗时过长 耗时移下半部
中断风暴 未清中断标志 确认时立即清标志
音频爆音 音频中断被抢占 提高优先级 + 绑核
栈溢出 中断嵌套过深 硬中断尽量短
核间抖动 中断到处迁移 亲和性绑核
数据丢失 下半部处理不及时 提高 tasklet 优先级

5.1 中断优化要点

1. 硬中断函数只做三件事: 确认、清标志、调度下半部
2. 高频中断合并处理(网卡 NAPI: 攒批再收包)
3. 关键中断绑核 + 高优先级(音频/传感器)
4. 中断线程化用于可延后的非实时处理
5. 监控中断延迟(/proc/interrupts + ftrace)

六、高阶总结与最佳实践

  1. 快进快出:硬中断只标记,重活交给下半部——这是中断处理的第一原则。
  2. 三级流水线:硬中断(us 响应)+ 软中断(批量延后)+ 下半部(进程上下文),各司其职。
  3. 禁止睡眠:硬中断/软中断上下文不能睡眠,需要等待的操作必须放到 workqueue。
  4. 绑核控延迟:关键中断绑定特定核,延迟可控且缓存友好。
  5. 监控不可少:中断延迟是系统实时性的晴雨表,要持续监控。

一句话记住:中断处理 = 硬中断快进快出(只标记)+ 软中断批量延后(安全点执行)+ 下半部耗时兜底(进程上下文)——中断函数里绝不能做重活,重活一律延后。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐