深入理解 Linux 中断三层机制与 1-Wire 时序锁原理

一、中断是什么

中断是硬件主动通知 CPU 的机制。一个外设产生了数据或者发生了事件,不需要 CPU 轮询去问"你好了没",而是直接发一个信号给 CPU,CPU 暂停当前工作去处理这个信号。

ARM 架构里,这个"接收开关"是 CPSR 寄存器(当前程序状态寄存器,全局硬件中断总闸)里的 I 位,当前位=0 表示开中断,CPU 回应;=1 表示关中断,CPU 不理。


二、中断信号的三段接力(设备 → GIC → CPU)

一个中断从产生到被 CPU 处理,经过三个硬件关卡:

第一段:设备发出中断信号。 网卡收完一帧数据、串口 FIFO 到了阈值、定时器计数溢出------设备内部的硬件电路把一根信号线拉高,告诉中断控制器"我有事"。这一步和 CPU 无关,设备自己完成。

第二段:GIC 仲裁与分发。 GIC(Generic Interrupt Controller)是 ARM 的通用中断控制器。所有设备的中断信号汇集到这里。GIC 做的三件事:给每个中断分配一个编号(IRQ number)、按优先级排序、把优先级最高的那个发给 CPU。GIC 内部还有一个 pending 寄存器------如果一个中断来了但 CPU 没理,GIC 就把对应位设为 1,等 CPU 开门后立刻补送。

注:优先级分配

IRQ 编号分配 设备树 dts 里写interrupts = <中断类型 中断号 触发方式>,内核解析后给外设绑定固定 IRQ 编号;无硬件固定编号的外设,内核动态分配空闲 IRQ 号。

第三段:CPU 硬件判断。 GIC 把中断信号送到 CPU 的 IRQ 引脚上。CPU 硬件检查 CPSR 的 I 位------如果 当前位=0,CPU 保存当前上下文、跳转到中断向量表、进入中断处理流程;如果 当前位=1,CPU 在硬件层面直接忽略这个信号,连进入异常模式的流程都不走。

三个环节各有各的控制开关,这就是下面要说的三道门。


三、中断的三道屏蔽门

一道中断信号要从设备到达内核中断处理函数,要穿过三道门,每道门由不同的人控制:

门一:设备的中断使能寄存器。

每个设备芯片内部有一个"开关面板"(叫中断使能寄存器),是一堆 0 或 1 的比特位。比如:

  • 第 0 位设成 1 → 收完一包数据就发中断
  • 第 0 位设成 0 → 收完一包数据不发中断

驱动往这个寄存器写 0 或 1 来拨动开关。这是设备内部的事,改了只影响这一个设备。别的设备不知道也不受影响。

门二:GIC 的中断线屏蔽。 GIC 为每个中断号维护一个使能位。Linux 内核函数 disable_irq(n) 就是清掉第 n 号中断的使能位。关掉之后,这个设备的中断信号到了 GIC 就被拦截,不会传给 CPU。但其他中断号不受影响。控制者:内核代码调用 disable_irq / enable_irq

门三:CPU 的全局中断使能位(CPSR.I 全局硬件中断总闸**)。** 这个位一清零,不管 GIC 送来什么、不管哪个设备的、不管优先级多高------CPU 一概不理。Linux 内核函数 local_irq_save 就是做这件事,它先读取当前 CPSR 值保存到 flags 变量,然后执行汇编指令 cpsid i 把 当前位 置 1,中断全关。local_irq_restore 把之前保存的 CPSR 值写回去,恢复原状态。控制者:任意内核代码都能调。

三道门的关系:门一和门二是"精准控制",想关谁关谁;门三是"粗暴控制",一关全关。 DS18B20 驱动用的是门三。


四、中断通路 vs 数据通路(为什么关总闸不会丢数据)

这是最容易被搞混的地方------中断不负责传数据,中断只负责递通知。硬件里是两条完全独立的通路:

数据通路: 设备硬件自动运行。网卡的 MAC 控制器自己收帧、组帧、写入内部 FIFO 缓冲区。串口的 UART 控制器自己采样 RX 线、组装字节、推入硬件 FIFO。整个过程不需要 CPU 参与,不需要中断参与。通电就能跑。中断关掉时,数据通路照常运转,数据持续存入硬件缓冲区。(中断恢复后,CPU 在中断处理函数里通过读硬件 FIFO 寄存器把数据取出来)

中断通路: 设备填了一定量的数据到 FIFO 后,额外拉一根信号线通知 GIC,GIC 再通知 CPU。这条通路的唯一作用就是"喊 CPU 来取数据"。DS18B20 驱动关的是第三条通路的 CPU 入口,数据通路完全不受影响。

缓冲区就是兜底机制。 不是"关中断时临时调用的机制",而是设备硬件本身的标配。网卡 FIFO 通常几 KB,串口 FIFO 至少 16 字节。几百微秒的时间,对于硬件数据速率来说远不够填满缓冲区。中断恢复后 CPU 正常取数,一个字节都不会丢。


五、注册中断 vs 关中断 ------ 两个完全独立的概念

request_irq(n, handler, ...) 是软件层面的注册行为------你告诉内核"如果第 n 号中断来了,请调用我的 handler 函数"。它登记的是一个"响应者"。没有注册的话,来了中断也没人处理,但不影响中断信号本身能不能到达 CPU。

local_irq_save(flags) 是硬件层面的开关操作------它直接修改 CPU 的 CPSR 寄存器,把全局中断响应能力关掉。它不关心你注册了什么中断、有没有注册中断。它就是一条 CPU 指令。

DS18B20 驱动的真实情况: 驱动根本没有调用过 request_irq,它不注册任何中断。它靠轮询方式主动读写 GPIO。调用 local_irq_save 不是为了关"自己的中断"(本来就没有),而是为了防止别人的中断(时钟、网卡、其他外设)打断它的微秒级 GPIO 时序。


六、优先级机制及其边界

GIC 支持中断优先级------每个中断号有一个优先级值,数值越小优先级越高。GIC 同时收到多个中断时,把优先级最高的发给 CPU。CPU 正在处理低优先级中断时,高优先级中断来了可以抢占(中断嵌套)。

但优先级在门二(GIC 层)起作用,门三(CPU 层)一关全部失效。 local_irq_save 关的是门三的 CPSR.I 位。这个时候 GIC 按优先级把中断信号送到 CPU 门口,CPU 硬件直接不接------管你优先级多高,一概拒之门外。

DS18B20 场景不需要考虑优先级。 因为关中断时间太短(一次 read_byte 全部 8 个 bit 总共不超过 600μs),就算时钟中断被延迟了 500μs,调度器完全感知不到。不需要高优先级中断来"抢先处理"。


七、DS18B20 为什么必须关总闸

根本原因:i.MX6ULL 没有硬件 1-Wire 控制器。 1-Wire 协议完全靠 CPU 手动操作 GPIO 来模拟,这种方式叫 bit-banging (软件位模拟 / 手动比特脉冲模拟)。

bit-banging 对时间的精度要求是微秒级------DS18B20 的写 0 时序要求 GPIO 保持低电平至少 60μs(范围 60~120μs)。如果在这 60μs 内 CPU 被中断打断去处理了 30μs 的网卡中断函数,DS18B20 看到的就是"5μs 低 + 中断打断间隙 + 25μs 低",而不是连续 60μs 低电平------通信失败。

mutex、spin_lock、preempt_disable 都不够用。 mutex 只让当前线程睡觉,中断照样来。spin_lock 只防止其他 CPU 核访问同一数据,不关中断。preempt_disable 只阻止调度器切走当前线程,但硬件中断一来照样跳到中断处理函数。

唯一的保证微秒级时序的手段就是关中断。 这是 bit-banging 方案的必然代价,绕不过去。


八、关总闸的风险与兜底机制

风险: 关中断期间,所有设备的中断都无法得到 CPU 的即时响应。如果关太久,网络丢包、定时器漂移、系统变卡。

兜底一:GIC pending 寄存。 关中断期间设备产生的中断不会被丢弃。GIC 把对应的 pending 位置 1。总闸一开,GIC 立刻按优先级把排队的中断送给 CPU。中断信号不丢,只是延迟处理。

兜底二:设备硬件缓冲区。 中断虽然没被处理,但数据通路不受影响。网卡 FIFO 继续收帧,串口 FIFO 继续收字节,定时器硬件计数器继续计。几百微秒远不够填满硬件缓冲区。

兜底三:内核代码规范。 内核里任何关中断的代码段都控制在几百微秒以内。这是铁律。如果关中断超过几毫秒,系统表现为明显卡顿。开启 CONFIG_IRQSOFF_TRACER 后内核会自动检测并打印 warning。

兜底四:DS18B20 本身的特性。 一次完整的 1-Wire 操作(reset + write_byte + read_byte + write_byte + ...)总时长约 1~1.5ms。两次操作之间间隔至少 750ms(温度转换时间)。99.9999% 的时间中断是开着的。关中断占比极低,对系统整体调度几乎无影响。


九、内核中关中断的真实分布

实际 Linux 内核中,会用到 local_irq_save 的地方非常少。绝大部分驱动的同步(保护内核空间数据)需求靠 mutex 和 spin_lock 就能满足:

  • mutex(可睡眠锁): 用于线程上下文,等锁时会睡眠让出 CPU,中断正常响应。绝大多数驱动用这个。
  • spin_lock(忙等锁): 用于中断上下文或临界区极短的场景。不关中断,只防其他 CPU 核。网卡驱动中断处理函数常用。
  • spin_lock_irqsave(忙等锁 + 关本核中断): 比 local_irq_save 多一层锁保护。比关总闸安全,因为只关本核。
  • local_irq_save(关总闸): 只用于 bit-banging 时序保护等极端场景。一个系统里可能就一个驱动需要。

"每个人都关几百微秒"是对生产环境内核的误解。 真实情况是:一个加载了 50 个驱动的系统,需要关总闸的可能只有 0 到 1 个。而那个驱动的关中断时间占总运行时间不到 0.0001%。


十、i.MX6ULL 上的替代方案

如果不想要关中断这套,有且只有以下替代路径:

硬件方案:外接 DS2482 芯片。 I2C 转 1-Wire 桥接芯片,1-Wire 时序由 DS2482 硬件完成。i.MX6ULL 通过 I2C 和它通信。内核有现成的 ds2482 驱动。不需要写 1-Wire 时序,不需要关中断。代价是硬件成本增加,PCB 多一颗芯片两根线。


十一、全链路总结

设备的中断信号要经过三层门(设备使能 → GIC 屏蔽 → CPU 总闸),DS18B20 驱动因为用 GPIO bit-banging 模拟 1-Wire 时序,必须关 CPU 总闸来保证微秒级精度,但关的时间极短(几百微秒),中断不会丢失(GIC pending 兜底),数据不会丢失(设备 FIFO 兜底),对系统调度影响可忽略 (关中断占比不到 0.0001%)

相关推荐
运维老郭1 小时前
PostgreSQL编译安装实战
运维·postgresql
无限码农2 小时前
Linux上通过cmake编译uchardet
linux·运维·服务器
运维大师3 小时前
【Linux运维极简教程】06-网络配置与管理
linux·运维
007张三丰4 小时前
软件测试专栏(15/20):REST Assured接口自动化框架实战
运维·自动化·jenkins·接口自动化·rest·assured
ACP广源盛139246256734 小时前
IX8024@ACP# 搭配此芯 AI 服务器 + 爱芯元智产品完整方案
大数据·运维·服务器·人工智能·分布式·嵌入式硬件
Hiyajo pray5 小时前
SDN 访问控制性能调优方法
服务器·网络·网络安全
探索云原生6 小时前
终于搞懂 Kueue:5 个核心对象一次讲透
linux·docker·ai·云原生·kubernetes
huainingning6 小时前
交换机通过ftp下载文件或者传输文件到ftp服务器
运维·服务器·网络
An_s7 小时前
机器学习python之识别图中物品信息
java·linux·开发语言