1. 并发与竞态基础:什么是并发、竞态条件以及它们在Linux驱动中的典型场景

在Linux驱动开发中,并发与竞态是导致系统崩溃、数据损坏和死锁的头号根源。本章将建立并发编程的核心概念,并揭示它们在驱动代码中的具体表现形式。

1.1 并发:多个执行流同时活跃

并发(Concurrency)指多个执行单元在时间上重叠地执行。在单核CPU上,并发通过时间片轮转实现宏观上的“同时”;在多核CPU上,则是真正的并行执行。

关键认知: 并发 ≠ 并行。并发是逻辑上的同时发生,并行是物理上的同时执行。驱动开发者必须同时应对这两种情况。

Linux内核中的并发执行流来源:

并发源 说明 典型驱动场景
进程上下文 多个用户进程同时调用驱动接口 open/read/write/ioctl 并发调用
中断上下文 硬件中断触发,抢占当前进程 网卡收包中断与数据读取
软中断/tasklet 中断下半部,延迟执行 网络协议栈处理、定时器回调
内核线程 内核创建的后台工作线程 设备轮询、数据批量处理
SMP多核 多个CPU核心同时执行内核代码 共享硬件寄存器访问
Linux内核并发执行流来源 共享资源 进程上下文 中断上下文 软中断/tasklet 内核线程 SMP多核 并发访问共享资源导致竞态 • 多个执行流同时读写同一内存/寄存器 • 操作序列被中断或抢占打断 • 多核CPU同时执行临界区代码

1.2 竞态条件:结果依赖于执行时序

竞态条件(Race Condition)是指多个执行流以不可预测的顺序访问共享资源,导致程序行为依赖于各执行流的相对执行速度。当两个或多个执行流同时进入临界区(Critical Section)时,数据完整性被破坏。

竞态三要素: 共享资源 + 并发访问 + 非原子操作 = 竞态条件。缺少任何一个要素都不会产生竞态。

经典的“检查-执行”竞态模式:

/* 竞态示例:设备引用计数 */
if (dev->ref_count > 0) {      /* 检查 */
    dev->ref_count--;           /* 执行 */
    /* 此时另一个线程可能已将ref_count减为0 */
    if (dev->ref_count == 0) {
        cleanup_device(dev);    /* 双重释放! */
    }
}

上例中,两个线程同时进入if (dev->ref_count > 0)检查,都认为引用计数合法,随后都执行dev->ref_count--,导致引用计数变为-1,且cleanup_device被调用两次。

1.3 驱动中的典型竞态场景

以下是在实际Linux驱动开发中最常遇到的竞态场景:

场景 竞态描述 后果
设备打开/关闭 多个进程同时open/close,引用计数操作非原子 资源泄漏、双重释放
IOCTL并发 多个线程同时发送控制命令,修改设备状态 设备状态不一致
中断与进程共享数据 中断处理函数与进程上下文同时访问环形缓冲区 数据损坏、指针错乱
DMA缓冲区访问 CPU与DMA控制器同时操作同一内存区域 数据不一致、硬件故障
定时器与进程交互 定时器回调与进程同时修改设备超时参数 超时逻辑失效
多核寄存器访问 两个CPU核心同时读写硬件寄存器 寄存器值被覆盖
驱动竞态典型场景:环形缓冲区被中断与进程并发访问 进程A (read) 进程B (write) 中断处理函数 环形缓冲区 head: 0x100 tail: 0x200 count: 32 data[...] 竞态问题 • head/tail同时被修改 • count计数不一致 • 数据被覆盖或丢失 后果 数据损坏 / 系统崩溃 安全漏洞 / 设备故障

1.4 竞态导致的典型问题

  • 数据损坏: 共享变量被部分更新,读取到不一致的中间状态
  • 内存泄漏: 引用计数错误导致资源无法释放
  • 双重释放: 同一内存块被释放两次,导致内核崩溃
  • 死锁: 多个执行流互相等待对方释放锁资源
  • 优先级反转: 低优先级任务持有锁,阻塞高优先级任务
  • 安全漏洞: 竞态窗口被利用进行权限提升或信息泄露
实战警示: 竞态条件是最难调试的bug类型之一。它可能只在特定负载、特定硬件配置下复现,且调试信息本身可能改变执行时序,使bug消失(Heisenbug效应)。因此,预防胜于调试——在设计阶段就考虑并发保护。

1.5 并发控制的核心思路

解决竞态问题的核心是互斥访问——确保同一时刻只有一个执行流可以操作共享资源。Linux内核提供了多种同步机制:

同步机制 适用上下文 特点
自旋锁 (spinlock) 中断上下文、短临界区 忙等待,不可睡眠
互斥锁 (mutex) 进程上下文、长临界区 可睡眠,允许调度
信号量 (semaphore) 进程上下文 允许多个读者
RCU (Read-Copy-Update) 读多写少场景 读者无锁,写者需等待
原子操作 (atomic_t) 简单计数器、标志位 硬件级原子指令

本课程后续章节将深入剖析自旋锁和互斥锁的实现原理、使用规范以及死锁的调试方法。

并发控制机制选择决策树 临界区需要保护? 中断上下文中? 进程上下文中? 自旋锁 互斥锁 信号量/RCU

1.6 小结