1. Linux驱动并发与竞争的本质理解
第一次在设备树里看到interrupts = <0 56 4>这样的定义时,我盯着这个数字组合发呆了半小时。中断号56到底对应哪个外设?那个神秘的"4"又代表什么?这就是驱动开发者每天都要面对的并发世界——一个由中断、多核、抢占和DMA交织而成的复杂战场。
驱动并发问题的根源在于硬件资源的有限性。以常见的GPIO控制器为例,当CPU0正在通过gpiod_set_value()修改引脚状态时,突然来了个中断服务程序也要操作同一个GPIO组。更糟的是,另一个CPU核心可能正通过DMA往这个GPIO的配置寄存器疯狂写入数据。这三种访问路径就像三辆同时冲向单车道隧道的跑车,没有交通管制必然导致事故。
现代SoC的并发场景比想象中复杂得多。以瑞萨RZ/V2M处理器为例,其硬件手册第17章明确标注:"GPIO寄存器组不保证原子性访问"。这意味着即使是对同一个32位寄存器的不同位域进行写操作,也可能被其他执行流打断。我曾用逻辑分析仪抓取过这样的场景:CPU写bit0-15时被高优先级任务抢占,等恢复执行后写bit16-31时,之前写入的低16位已经被中断服务程序修改——这就是典型的位撕裂(bit tearing)现象。
2. 并发问题的五种典型场景
2.1 中断与进程上下文的竞争
在调试i2c-dev驱动时,我遇到过这样的崩溃日志:
code复制Unable to handle kernel paging request at virtual address dead0000
根本原因是进程上下文调用i2c_transfer()时被中断打断,中断处理程序恰好也操作了同一个i2c适配器。解决方案是在i2c-core中使用spin_lock_irqsave():
c复制unsigned long flags;
spin_lock_irqsave(&i2c->lock, flags);
/* 临界区操作 */
spin_unlock_irqrestore(&i2c->lock, flags);
这个flags参数至关重要,它保存了中断状态,确保解锁时能恢复之前的中断使能状态。我曾见过有开发者直接调用spin_lock_irq(),结果在UP(单核)系统上导致中断永久关闭
