本页目录

锁机制

覆盖: spinlock (qspinlock) → mutex (乐观自旋) → rw_semaphore (percpu) → seqlock → atomic → memory barriers (acquire/release) 内核版本: 2.6 ~ 6.x,重点标注 qspinlock (4.2+) 和 percpu rwsem (5.x+)

概述

内核并发控制的核心问题:多个 CPU 同时访问共享数据。解决方案从最轻量的原子操作到重量级的互斥锁,形成一条清晰的"锁选择链"。选错锁的代价从不必要的 cache line bouncing 到死锁。

关键原则:⁠锁保护的是数据,不是代码⁠。设计锁策略时,先确定"哪些数据被哪些上下文访问",再选合适的锁。

锁选择决策树

flowchart TD
    START["访问共享数据<br/>需要同步?"]

    START --> Q1{"只是单个整型<br/>(加/减/置位)?"}
    Q1 -->|"是"| ATOMIC["atomic_t / atomic64_t<br/>无锁, 硬件保证原子性 ✅"]

    Q1 -->|"否"| Q2{"可以睡眠?"}

    Q2 -->|"是"| Q3{"读多写少?"}
    Q3 -->|"是"| RWSEM["rw_semaphore<br/>多个读者可同时持锁"]
    Q3 -->|"否"| MUTEX["mutex (首选)<br/>乐观自旋 → 睡眠"]

    Q2 -->|"否<br/>(中断/softirq 上下文)"| Q4{"写少读多<br/>+ 读不能阻塞?"}
    Q4 -->|"是"| RCU["RCU / seqlock<br/>读者零开销"]

    Q4 -->|"否"| Q5{"需要多个读者<br/>同时持锁?"}
    Q5 -->|"是"| RWLOCK["rwlock_t"]
    Q5 -->|"否"| SPIN["spinlock"]

    SPIN --> Q6{"防什么?"}
    Q6 -->|"只防 softirq"| SPIN_BH["spin_lock_bh"]
    Q6 -->|"防中断+softirq"| SPIN_IRQ["spin_lock_irqsave"]

    classDef start fill:#e3f2fd,stroke:#1565c0
    classDef decision fill:#fff3e0,stroke:#ef6c00
    classDef lock fill:#e8f5e9,stroke:#2e7d32
    classDef api fill:#f3e5f5,stroke:#7b1fa2
    class START start
    class Q1,Q2,Q3,Q4,Q5,Q6 decision
    class ATOMIC,RWSEM,MUTEX,RCU,RWLOCK,SPIN lock
    class SPIN_BH,SPIN_IRQ api

Spinlock: qspinlock (4.2+)

为什么需要 qspinlock

旧 ticket spinlock 的问题:
  每个 CPU 在 lock word 上自旋 → 所有 CPU 读同一个 cache line
  → cache line 在所有 CPU 之间弹跳 (bouncing)
  → 16 核以上: 性能灾难

qspinlock (queued spinlock):
  每个 CPU 在自己的 per-CPU MCS node 上自旋
  → 每个 CPU 自旋在不同的 cache line!
  → 只有 unlock 时需要写下一个 CPU 的 node → 一次 cache line transfer
  → 128 核以上仍然有效
// kernel/locking/qspinlock.c
// qspinlock 的 4 字节 atomic word:
//   byte 0: locked (1 bit) + pending (1 bit)
//   byte 1: tail index (指向 MCS node 队列尾)
//   bytes 2-3: tail CPU

void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val) {
    // 1. 如果锁空闲 → 直接拿 (locked byte CAS)
    // 2. 如果 pending bit 未设 → 设 pending, 等 locked bit 清
    // 3. 否则 → 加入 MCS node 队列 (在自己 node 上自旋)
}

// API (include/linux/spinlock.h):
spin_lock(&lock);            // 拿锁 (假定已关抢占)
spin_unlock(&lock);          // 释放
spin_lock_irq(&lock);        // 关本地中断 + 拿锁
spin_unlock_irq(&lock);
spin_lock_irqsave(&lock, flags);   // 保存 IF 状态 + 关中断 + 拿锁
spin_unlock_irqrestore(&lock, flags);
spin_lock_bh(&lock);         // 关 bottom half (softirq) + 拿锁
spin_unlock_bh(&lock);

Mutex: 乐观自旋

// kernel/locking/mutex.c
struct mutex {
    atomic_long_t       owner;          // 当前持有者的 task_struct *
    raw_spinlock_t      wait_lock;      // 保护 wait_list
    struct list_head    wait_list;      // 等待队列
};

mutex_lock(&lock):
  1. 快速路径: 无人持锁 → atomic_long_cmpxchg 设 owner = current, 返回
  2. 乐观自旋: owner 在另一个 CPU 上正在运行?自旋等待 (不用睡眠, 期望 owner 很快释放)
     → owner 不在运行 (睡眠了) → 停止自旋, 进入睡眠
  3. 睡眠等待: 设 TASK_UNINTERRUPTIBLE, 加入 wait_list, schedule()

mutex_unlock(&lock):
  1. 设 owner = 0
  2. 如果 wait_list 非空 → 唤醒第一个等待者
      → wake_up_process() → 等待者回到 mutex_lock 第 2

rw_semaphore: 读写信号量

// kernel/locking/rwsem.c
// percpu rwsem (5.x+ 优化):
//   读锁: 增加 percpu refcount → 不需要全局锁 (本地 CPU 操作)
//   写锁: 遍历所有 CPU 的 percpu refcount → 等待全零

// 读者饥饿保护:
//   rwsem 跟踪是否有等待的写者
//   如果有等待的写者 → 新读者不进入 → 写者不会被饿死

down_read(&sem);              // 获取读锁
up_read(&sem);
down_write(&sem);             // 获取写锁
up_write(&sem);
down_read_trylock(&sem);      // 非阻塞

Seqlock: 序列锁

// include/linux/seqlock.h
// 写者互斥 (拿 spinlock), 读者无锁
// 读者通过序列号检测"写者是否在修改" → 重试

struct seqlock {
    seqcount_t seq;       // 序列号 (偶数=稳定, 奇数=写中)
    spinlock_t lock;      // 保护写者
};

// 读者:
do {
    seq = read_seqbegin(&seqlock);   // 记录序列号
    // ... 读数据 ...
} while (read_seqretry(&seqlock, seq));  // 序列号变了? 重试

// 写者:
write_seqlock(&seqlock);
seq++;    // 奇数 → 读者看到后重试
// ... 修改数据 ...
seq++;    // 偶数 → 读者可以读了
write_sequnlock(&seqlock);

// 经典用途: timekeeper (gettimeofday 无锁读, 极少写)

原子操作

// include/linux/atomic/atomic-instrumented.h
// 所有架构保证的基本原子操作:

// 算术
atomic_inc(&v);  atomic_dec(&v);
atomic_add(n, &v);  atomic_sub(n, &v);

// CAS (Compare-And-Swap)
atomic_cmpxchg(&v, old, new);  // if (v==old) v=new; return old;

// RMW with memory ordering (5.x+)
atomic_add_return(n, &v);       // return v+n (full barrier)
atomic_add_return_acquire(n, &v);  // ACQUIRE semantics
atomic_add_return_release(n, &v);  // RELEASE semantics

// 位操作
set_bit(nr, addr);  clear_bit(nr, addr);
test_and_set_bit(nr, addr);  // 原子的 test + set → 用于实现简单锁

Memory Barriers

编译屏障:
  barrier(): 防编译器重排 (不影响 CPU 硬件重排)

CPU 内存屏障:
  smp_mb():  全屏障 (前后所有内存操作的有序性)
  smp_rmb(): 读屏障 (load 不跨越)
  smp_wmb(): 写屏障 (store 不跨越)

Acquire/Release 语义:
  ACQUIRE: 这条操作后面的 load/store 不能重排到前面
  RELEASE: 前面的 load/store 不能重排到后面
  
  spin_lock()   = ACQUIRE (临界区操作不泄漏到锁外)
  spin_unlock() = RELEASE

何时需要显式 barrier?
  → 无锁数据结构 (环形缓冲区, RCU)
  → 设备 DMA (确保 DMA 缓冲区写入在启动 DMA 之前完成)

调试与锁分析

# lockdep: 锁依赖验证 (死锁检测)
dmesg | grep -i lockdep   # 启动时自动检查

# lockstat: 锁竞争统计
cat /proc/lock_stat | head -20

# 触发锁竞争追踪
echo 1 > /sys/kernel/debug/tracing/events/lock/lock_acquire/enable
echo 1 > /sys/kernel/debug/tracing/events/lock/lock_release/enable

参考

  • 源码⁠: kernel/locking/ (qspinlock, mutex, rwsem, rtmutex), include/linux/seqlock.h, include/linux/atomic/
  • 内核文档⁠: Documentation/locking/ 目录
  • LWN: "Queued spinlocks", "The percpu rwsem", "Memory barriers for kernel hackers"

关键词: qspinlock, mutex, rw_semaphore, seqlock, atomic, CAS, memory barrier, acquire/release, lockdep