跳转至

中断状态跃迁、优先级反转与低功耗竞争状态深度推演

1. 电平中断为何在 EOI 后反复重入:微架构推演

对于电平敏感型中断(Level-sensitive Interrupt),GIC 控制器采样的是物理信号线上的实时电压电平:

sequenceDiagram
    participant Dev as 外设 (如 UART FIFO 满)
    participant Line as 物理中断线 (IRQ Line)
    participant GIC as GIC 中断控制器
    participant CPU as CPU 核心 (ISR 执行流)

    Dev->>Line: FIFO 满, 硬件将 IRQ Line 拉高为高电平 (1b)
    Line->>GIC: GIC 采样到高电平, 状态变更为 Pending
    GIC->>CPU: 投递物理异常, CPU 读取 ICC_IAR1_EL1 响应
    Note over GIC: 中断状态变更为 Active

    Note over CPU: 错误处理: 仅写了 ICC_EOIR1_EL1, 未读空 UART FIFO!
    CPU->>GIC: 写 ICC_EOIR1_EL1 结束中断 (Deactivate)
    Note over GIC: 中断状态复位为 Inactive

    Note over Line,GIC: 但此时物理 IRQ Line 依然被外设维持在高电平 (1b)!
    GIC->>GIC: GIC 硬件再次采样到高电平, 瞬间重新生成 Pending 状态!
    GIC->>CPU: CPU 刚退出 ISR 汇编, 瞬间再次被同一个中断打断 -> 中断死循环!
  • 正确处理原则:
  • 在 ISR 中首先读取外设数据寄存器将 FIFO 排空,或者向中断状态寄存器写入清除位(W1C);
  • 确认外设状态寄存器的原始中断标志已归零(引脚电平已恢复为低电平);
  • 最后向 GIC 写入 ICC_EOIR1_EL1 / ICC_DIR_EL1。

2. 中断优先级反转(Priority Inversion)与死锁风险

中断控制器内部的优先级硬件仲裁(Priority Preemption)仅决定哪个中断能够抢占 CPU 执行流,它完全脱离于操作系统内核的互斥锁(Mutex / Spinlock)体系:

flowchart TD
    subgraph Danger_Scenario ["中断与线程的死锁场景"]
        Thread["低优先级用户/内核线程: 获取自旋锁 spin_lock(&my_lock)"]
        IRQ["高优先级硬中断 ISR: 发生抢占, 在本核进入中断上下文"]
        Deadlock["ISR 内部尝试获取同一把锁: spin_lock(&my_lock)\n• ISR 陷入无限死循环自旋等待锁释放\n• 但持有锁的线程被 ISR 永久打断, 永远无法继续执行并释放锁!\n• 结果: CPU 陷入不可恢复的自旋死锁 (Lockup)"]
    end

    Thread --> IRQ --> Deadlock
  • 规避规范:当共享自旋锁可能被当前 CPU 上的硬中断处理程序(ISR)抢占获取时,线程上下文必须使用 spin_lock_irqsave(&lock, flags)(在获取锁的同时关闭本地中断),防止当前核心在持锁期间被本地 ISR 抢占陷入死锁;若该锁仅在跨核线程之间竞争且明确不在中断上下文中获取,使用普通 spin_lock 即可。

3. 定时器漂移与系统时间累积误差推算

假设某嵌入式平台系统计数器(System Counter)名义频率为 $24\text{MHz}$,但由于外部晶振老化或电容温漂,实际输出频率为 $23.976\text{MHz}$(误差 $\Delta f = -24\text{kHz}$,即 $-1000\text{ppm}$):

$$\text{PPM 误差} = \frac{23.976\text{MHz} - 24.000\text{MHz}}{24.000\text{MHz}} \times 10^6 = \mathbf{-1000 \text{ ppm}}$$

误差的时间累积效应:

  • 每秒钟系统时间偏慢:$1\text{s} \times 1000 \times 10^{-6} = \mathbf{1 \text{ ms}}$;
  • 运行 1 小时后累积误差:$3600\text{s} \times 1\text{ms/s} = \mathbf{3.6 \text{ 秒}}$;
  • 运行 1 天后累积误差:$3.6 \times 24 \approx \mathbf{86.4 \text{ 秒}}$。
  • 工程诊断准则:若系统内所有依赖 nanosleep()、timerfd 的超时时间均按严格相同的比例偏长或偏短,应直接测量外部时钟源晶振频率,严禁通过逐个修改各个驱动的分频系数来“打补丁”。

4. Runtime PM 动态下电与外设 MMIO 访问的竞态条件

sequenceDiagram
    participant CPU_A as CPU Core A (正在执行业务驱动)
    participant RPM as Linux Runtime PM 框架
    participant Power as 电源域硬件控制器
    participant CPU_B as CPU Core B (准备访问该外设)

    CPU_A->>RPM: pm_runtime_put_autosuspend(dev) (引用计数归零)
    RPM->>Power: 启动下电工作队列, 开始关断外设电源域

    Note over CPU_B,Power: 竞态发生: CPU B 未先获取引用即发起访问!
    CPU_B->>Power: 直接执行 readl(dev_reg)
    Note over Power: 此时电源域已断电或隔离已开启, 总线无法响应!
    Power-->>CPU_B: 触发 Synchronous External Abort 崩溃!

    Note over CPU_B,Power: 正确模式: 先增引用, 确认上电后再读写
    CPU_B->>RPM: pm_runtime_get_sync(dev) (同步阻塞等待上电完成)
    RPM->>Power: 恢复电源域供电与时钟
    CPU_B->>Power: 安全执行 readl(dev_reg)
    CPU_B->>RPM: pm_runtime_put(dev)