《Linux 设备驱动开发详解:基于最新的 Linux 4.0 内核》 第 7 章 Linux 设备驱动中的并发控制

《Linux 设备驱动开发详解:基于最新的 Linux 4.0 内核》

第 7 章 Linux 设备驱动中的并发控制

参考:宋宝华 著,机械工业出版社,2015年版


7.1 并发与竞态

7.1.1 并发的概念

**并发(Concurrency)**是指多个执行单元同时、并行地被执行。在 Linux 内核中,并发来源于以下几个方面:

复制代码
Linux 内核中并发的来源:

1. 多处理器(SMP,Symmetric Multi-Processing)
   多个 CPU 核心真正同时执行不同的代码路径
   CPU0 正在执行驱动的 read 函数
   CPU1 同时执行驱动的 write 函数
   → 真正的并行执行

2. 内核抢占(Preemption)
   高优先级任务可以抢占低优先级任务
   进程A 正在执行驱动代码
   → 被高优先级进程B 抢占
   → 进程B 也访问同一驱动
   → 进程A 恢复执行时数据已被修改

3. 中断
   硬件中断可以在任何时刻打断当前执行的代码
   驱动的 read 函数正在执行
   → 硬件中断触发,执行中断处理函数
   → 中断处理函数也访问同一数据
   → read 函数恢复时数据已被修改

4. 软中断和 tasklet
   软中断和 tasklet 可以在进程上下文之外执行
   与进程上下文代码并发执行

5. 内核线程
   多个内核线程并发访问共享资源

7.1.2 竞态的概念

**竞态(Race Condition)**是指多个执行单元并发访问共享资源,且最终结果依赖于执行顺序的情况。竞态会导致数据损坏、系统崩溃等严重问题。

经典竞态案例:银行账户转账

c 复制代码
/* 错误的驱动代码:没有并发保护 */
static int counter = 0;   /* 共享变量 */

/* 进程A 和进程B 同时执行以下代码 */
static ssize_t my_write(struct file *filp, const char __user *buf,
                         size_t count, loff_t *ppos)
{
    int tmp;

    tmp = counter;          /* 步骤1:读取 counter */
    tmp = tmp + 1;          /* 步骤2:加1 */
    counter = tmp;          /* 步骤3:写回 counter */

    return count;
}

/*
 * 竞态场景(初始 counter = 0):
 *
 * 进程A                    进程B
 * tmp = counter;  (tmp=0)
 *                          tmp = counter;  (tmp=0)  ← 进程A还没写回!
 * tmp = tmp + 1;  (tmp=1)
 *                          tmp = tmp + 1;  (tmp=1)
 * counter = tmp;  (counter=1)
 *                          counter = tmp;  (counter=1)
 *
 * 预期结果:counter = 2
 * 实际结果:counter = 1  ← 数据丢失!
 */

7.1.3 临界区

**临界区(Critical Section)**是访问共享资源的代码段,同一时刻只能有一个执行单元进入临界区:

复制代码
临界区的保护原则:

进入临界区前:获取锁(加锁)
执行临界区代码:访问共享资源
离开临界区后:释放锁(解锁)

┌─────────────────────────────────────────────────────┐
│  进程A                    进程B                      │
│  lock()                                             │
│  ┌──────────────────┐    lock()(阻塞等待)          │
│  │  临界区           │                               │
│  │  访问共享资源     │                               │
│  └──────────────────┘                               │
│  unlock()              → lock()(获得锁,进入临界区)│
└─────────────────────────────────────────────────────┘

7.1.4 并发控制机制总览

Linux 内核提供了多种并发控制机制,适用于不同场景:

复制代码
Linux 并发控制机制对比:

机制              适用场景                    是否可睡眠    开销
─────────────────────────────────────────────────────────────
中断屏蔽          单CPU,防止中断竞争          否           极低
原子操作          简单整数/位操作              否           极低
自旋锁            短临界区,中断上下文         否           低
读写自旋锁        读多写少,短临界区           否           低
顺序锁            读多写少,写优先             否           低
RCU               读多写少,读性能极致         读:否       极低
信号量            长临界区,进程上下文         是           中
互斥体(mutex)   长临界区,进程上下文         是           中
完成量            同步事件通知                 是           中

7.2 编译乱序和执行乱序

7.2.1 编译乱序

编译器为了优化性能,可能会重新排列指令的执行顺序,这在单线程程序中没有问题,但在多线程/多处理器环境中可能导致竞态:

c 复制代码
/* 原始代码 */
int flag = 0;
int data = 0;

/* 线程A(生产者) */
data = 100;    /* 步骤1:写数据 */
flag = 1;      /* 步骤2:设置标志 */

/* 线程B(消费者) */
while (!flag); /* 等待标志 */
use(data);     /* 使用数据 */

/*
 * 问题:编译器可能将步骤1和步骤2重排为:
 * flag = 1;      ← 先设置标志
 * data = 100;    ← 再写数据
 *
 * 线程B 看到 flag=1 后立即读取 data,但 data 可能还是0!
 */

编译屏障(Compiler Barrier)

c 复制代码
#include <linux/compiler.h>

/*
 * barrier():编译屏障
 * 告诉编译器:不要将 barrier() 前后的指令重排
 * 只影响编译器,不影响 CPU 执行顺序
 */
data = 100;
barrier();     /* 编译屏障:确保 data 的写入在 flag 之前 */
flag = 1;

/* 在驱动中的典型用法 */
static void producer(void)
{
    dev->data = prepare_data();
    barrier();              /* 确保数据写入完成后再设置 ready 标志 */
    dev->ready = 1;
}

7.2.2 执行乱序(CPU 乱序执行)

现代 CPU 为了提高性能,会乱序执行指令(Out-of-Order Execution)。即使编译器生成了正确顺序的指令,CPU 也可能以不同顺序执行它们:

复制代码
CPU 乱序执行示例(多处理器):

CPU0 执行:              CPU1 执行:
store A = 1             store B = 1
load B                  load A

预期:CPU0 看到 B=1 或 CPU1 看到 A=1(至少一个)
实际:由于 CPU 乱序,可能 CPU0 看到 B=0 且 CPU1 看到 A=0!

内存屏障(Memory Barrier)

c 复制代码
#include <asm/barrier.h>

/*
 * Linux 内核提供的内存屏障:
 *
 * mb():完全内存屏障(读+写)
 *       mb() 之前的所有内存访问必须在 mb() 之后的访问之前完成
 *
 * rmb():读内存屏障
 *        确保 rmb() 之前的读操作在 rmb() 之后的读操作之前完成
 *
 * wmb():写内存屏障
 *        确保 wmb() 之前的写操作在 wmb() 之后的写操作之前完成
 *
 * smp_mb():SMP 内存屏障(单处理器上为空操作)
 * smp_rmb():SMP 读内存屏障
 * smp_wmb():SMP 写内存屏障
 */

/* 生产者(CPU0) */
void producer(void)
{
    ring_buf[head] = data;   /* 写入数据 */
    wmb();                   /* 写内存屏障:确保数据写入后再更新 head */
    head = (head + 1) % SIZE;
}

/* 消费者(CPU1) */
void consumer(void)
{
    while (head == tail);    /* 等待数据 */
    rmb();                   /* 读内存屏障:确保先读 head 再读数据 */
    data = ring_buf[tail];
    tail = (tail + 1) % SIZE;
}

/*
 * I/O 内存屏障(访问设备寄存器时使用):
 * readl()/writel() 等函数内部已包含必要的内存屏障
 * 但在某些特殊情况下需要手动添加
 */
writel(val1, reg1);
wmb();                       /* 确保 reg1 写入完成后再写 reg2 */
writel(val2, reg2);

7.3 中断屏蔽

7.3.1 中断屏蔽的原理

中断屏蔽是最简单的并发控制手段,通过禁止本地 CPU 的中断来防止中断处理函数与当前代码并发执行:

c 复制代码
#include <linux/irqflags.h>

/*
 * local_irq_disable():禁止本地 CPU 中断
 * local_irq_enable():使能本地 CPU 中断
 *
 * 注意:只禁止本地 CPU 的中断,不影响其他 CPU
 * 在 SMP 系统中,其他 CPU 仍然可以访问共享资源
 * 因此中断屏蔽通常与自旋锁配合使用
 */

/* 基本用法(不推荐,可能丢失中断状态) */
local_irq_disable();
/* 临界区 */
local_irq_enable();

/*
 * 推荐用法:保存和恢复中断状态
 * 避免在中断已经被禁止的情况下调用 local_irq_enable() 导致问题
 */
unsigned long flags;

local_irq_save(flags);    /* 保存当前中断状态并禁止中断 */
/* 临界区 */
local_irq_restore(flags); /* 恢复之前的中断状态 */

7.3.2 中断屏蔽的适用场景

c 复制代码
/* 场景:驱动的进程上下文代码与中断处理函数共享数据 */

struct my_dev {
    int data;
    int irq_count;
};

/* 中断处理函数(中断上下文) */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    struct my_dev *dev = dev_id;
    dev->irq_count++;    /* 修改共享数据 */
    dev->data = read_hw_data();
    return IRQ_HANDLED;
}

/* 进程上下文代码(如 read 函数) */
static ssize_t my_read(struct file *filp, char __user *buf,
                        size_t count, loff_t *ppos)
{
    struct my_dev *dev = filp->private_data;
    unsigned long flags;
    int data;

    /*
     * 必须禁止中断,防止 my_irq_handler 在读取 dev->data 时
     * 修改 dev->data,导致读到不一致的数据
     */
    local_irq_save(flags);
    data = dev->data;        /* 临界区:读取共享数据 */
    local_irq_restore(flags);

    return copy_to_user(buf, &data, sizeof(data)) ? -EFAULT : sizeof(data);
}

7.3.3 中断屏蔽的局限性

复制代码
中断屏蔽的局限性:

1. 只对本地 CPU 有效
   SMP 系统中,其他 CPU 的中断不受影响
   其他 CPU 上的代码仍然可以并发访问共享资源

2. 禁止中断时间不能太长
   中断被禁止期间,所有硬件中断都无法响应
   可能导致中断丢失、系统响应延迟

3. 不能在中断屏蔽期间睡眠
   睡眠会导致调度,而调度需要时钟中断

结论:中断屏蔽通常与自旋锁配合使用(spin_lock_irqsave)
      单独使用中断屏蔽只适用于单处理器系统

7.4 原子操作

7.4.1 原子整数操作

原子操作是不可分割的操作,在执行过程中不会被中断或其他 CPU 干扰,适用于简单的整数计数场景:

c 复制代码
#include <linux/atomic.h>

/* ── 原子整数类型 ──────────────────────────────────────── */
atomic_t v;                    /* 32位原子整数 */
atomic64_t v64;                /* 64位原子整数 */

/* ── 初始化 ────────────────────────────────────────────── */
atomic_t v = ATOMIC_INIT(0);   /* 静态初始化为0 */
atomic_set(&v, 5);             /* 动态设置值为5 */

/* ── 读取 ──────────────────────────────────────────────── */
int val = atomic_read(&v);     /* 读取原子变量的值 */

/* ── 加减操作 ──────────────────────────────────────────── */
atomic_inc(&v);                /* v++ */
atomic_dec(&v);                /* v-- */
atomic_add(5, &v);             /* v += 5 */
atomic_sub(3, &v);             /* v -= 3 */

/* ── 操作并返回结果 ────────────────────────────────────── */
int old = atomic_inc_return(&v);   /* v++,返回新值 */
int old = atomic_dec_return(&v);   /* v--,返回新值 */
int old = atomic_add_return(5, &v);/* v+=5,返回新值 */

/* ── 操作并测试结果 ────────────────────────────────────── */
/* 返回操作后是否为零 */
if (atomic_dec_and_test(&v)) {
    /* v 减1后变为0,执行清理操作 */
    pr_info("引用计数归零,释放资源\n");
}

if (atomic_inc_and_test(&v)) {
    /* v 加1后变为0(溢出情况,极少见) */
}

/* ── 条件操作 ──────────────────────────────────────────── */
/* 如果 v == old,则将 v 设置为 new,返回 v 的原始值 */
int result = atomic_cmpxchg(&v, old_val, new_val);
if (result == old_val) {
    /* 交换成功 */
}

7.4.2 原子操作的典型应用

应用一:设备引用计数

c 复制代码
struct my_dev {
    atomic_t ref_count;   /* 引用计数 */
    /* ... */
};

static int my_open(struct inode *inode, struct file *filp)
{
    struct my_dev *dev = container_of(inode->i_cdev, struct my_dev, cdev);

    atomic_inc(&dev->ref_count);   /* 引用计数加1 */
    filp->private_data = dev;
    return 0;
}

static int my_release(struct inode *inode, struct file *filp)
{
    struct my_dev *dev = filp->private_data;

    if (atomic_dec_and_test(&dev->ref_count)) {
        /* 引用计数归零,最后一个用户关闭了设备 */
        pr_info("最后一个用户关闭设备,执行清理\n");
        /* 执行设备关闭操作 */
    }
    return 0;
}

应用二:限制设备只能被一个进程打开

c 复制代码
static atomic_t device_available = ATOMIC_INIT(1);  /* 1表示可用 */

static int my_open(struct inode *inode, struct file *filp)
{
    /*
     * atomic_dec_and_test:将 device_available 减1
     * 如果减1后为0,说明之前是1(设备可用),返回 true
     * 如果减1后不为0(变为负数),说明设备已被占用
     */
    if (!atomic_dec_and_test(&device_available)) {
        atomic_inc(&device_available);  /* 恢复计数 */
        return -EBUSY;   /* 设备忙 */
    }

    /* 成功获取设备 */
    return 0;
}

static int my_release(struct inode *inode, struct file *filp)
{
    atomic_inc(&device_available);  /* 释放设备 */
    return 0;
}

7.4.3 原子位操作

c 复制代码
#include <linux/bitops.h>

unsigned long flags = 0;

/* 设置位 */
set_bit(3, &flags);          /* 设置第3位为1 */
__set_bit(3, &flags);        /* 非原子版本(更快,但不安全) */

/* 清除位 */
clear_bit(3, &flags);        /* 清除第3位 */
__clear_bit(3, &flags);      /* 非原子版本 */

/* 翻转位 */
change_bit(3, &flags);       /* 翻转第3位 */

/* 测试位 */
int val = test_bit(3, &flags);  /* 返回第3位的值(0或1) */

/* 测试并设置/清除(原子操作,返回旧值) */
int old = test_and_set_bit(3, &flags);   /* 设置第3位,返回旧值 */
int old = test_and_clear_bit(3, &flags); /* 清除第3位,返回旧值 */

/* 典型应用:标志位管理 */
#define FLAG_INITIALIZED  0
#define FLAG_RUNNING      1
#define FLAG_ERROR        2

unsigned long dev_flags = 0;

/* 设置初始化标志 */
set_bit(FLAG_INITIALIZED, &dev_flags);

/* 检查是否正在运行 */
if (test_bit(FLAG_RUNNING, &dev_flags)) {
    pr_info("设备正在运行\n");
}

/* 原子地检查并设置运行标志(防止重复启动) */
if (test_and_set_bit(FLAG_RUNNING, &dev_flags)) {
    pr_err("设备已在运行\n");
    return -EBUSY;
}

7.5 自旋锁

7.5.1 自旋锁的原理

自旋锁(Spinlock)是一种忙等待 锁:当一个执行单元试图获取已被占用的自旋锁时,它会循环等待(自旋),直到锁被释放,而不是进入睡眠状态。

复制代码
自旋锁工作原理:

CPU0(持锁者)              CPU1(等待者)
lock()                      lock()
  获取锁成功                  锁被占用
  进入临界区                  while(lock_is_held) { /* 自旋 */ }
  ...                         ...
  ...                         ...
unlock()                    → 获取锁成功
                              进入临界区

特点:
  ✓ 不会引起进程睡眠,适用于中断上下文
  ✓ 临界区很短时,比睡眠锁效率高(避免上下文切换开销)
  ✗ 持锁期间 CPU 一直在忙等,浪费 CPU 资源
  ✗ 不能在持锁期间睡眠(会导致死锁)
  ✗ 临界区很长时,效率低于睡眠锁

7.5.2 自旋锁的 API

c 复制代码
#include <linux/spinlock.h>

/* ── 定义和初始化 ──────────────────────────────────────── */
spinlock_t my_lock;
spin_lock_init(&my_lock);    /* 动态初始化 */

DEFINE_SPINLOCK(my_lock);    /* 静态定义并初始化 */

/* ── 基本操作 ──────────────────────────────────────────── */
spin_lock(&my_lock);         /* 获取锁(自旋等待) */
/* 临界区(不能睡眠!) */
spin_unlock(&my_lock);       /* 释放锁 */

/* ── 禁止中断版本(最常用)────────────────────────────── */
unsigned long flags;
spin_lock_irqsave(&my_lock, flags);    /* 禁止本地中断并获取锁 */
/* 临界区 */
spin_unlock_irqrestore(&my_lock, flags); /* 释放锁并恢复中断状态 */

/* ── 禁止下半部版本 ────────────────────────────────────── */
spin_lock_bh(&my_lock);      /* 禁止软中断(下半部)并获取锁 */
/* 临界区 */
spin_unlock_bh(&my_lock);    /* 释放锁并使能软中断 */

/* ── 尝试获取锁(非阻塞)──────────────────────────────── */
if (spin_trylock(&my_lock)) {
    /* 成功获取锁 */
    /* 临界区 */
    spin_unlock(&my_lock);
} else {
    /* 锁已被占用,立即返回 */
    pr_info("锁忙,稍后重试\n");
}

7.5.3 自旋锁的使用规则

复制代码
自旋锁使用的黄金规则:

规则1:持锁期间不能睡眠
  ✗ 错误:spin_lock(&lock); msleep(100); spin_unlock(&lock);
  ✗ 错误:spin_lock(&lock); kmalloc(size, GFP_KERNEL); spin_unlock(&lock);
  ✓ 正确:spin_lock(&lock); kmalloc(size, GFP_ATOMIC); spin_unlock(&lock);

规则2:持锁期间不能调用可能睡眠的函数
  ✗ 错误:spin_lock(&lock); mutex_lock(&mutex); spin_unlock(&lock);
  ✗ 错误:spin_lock(&lock); copy_to_user(...); spin_unlock(&lock);

规则3:中断处理函数中使用自旋锁时,进程上下文必须用 irqsave 版本
  进程上下文:spin_lock_irqsave(&lock, flags);
  中断上下文:spin_lock(&lock);  /* 中断上下文中中断已被禁止 */

规则4:不能递归获取同一个自旋锁(会死锁)
  ✗ 错误:spin_lock(&lock); spin_lock(&lock); /* 死锁! */

规则5:临界区应尽量短
  自旋锁持有时间越长,其他 CPU 等待时间越长,系统性能越差

7.5.4 自旋锁的完整使用案例

c 复制代码
struct my_device {
    spinlock_t    lock;
    int           data;
    int           irq_count;
    struct list_head pending_list;
};

/* 中断处理函数 */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    struct my_device *dev = dev_id;
    struct pending_item *item;

    /*
     * 中断上下文中使用 spin_lock(不用 irqsave,因为中断已被禁止)
     * 但如果中断处理函数可能被更高优先级中断打断,需要用 irqsave
     */
    spin_lock(&dev->lock);

    dev->irq_count++;
    dev->data = read_hw_register();

    /* 添加待处理项到链表 */
    item = kzalloc(sizeof(*item), GFP_ATOMIC);  /* 中断上下文用 GFP_ATOMIC */
    if (item) {
        item->data = dev->data;
        list_add_tail(&item->list, &dev->pending_list);
    }

    spin_unlock(&dev->lock);
    return IRQ_HANDLED;
}

/* 进程上下文(read 函数) */
static ssize_t my_read(struct file *filp, char __user *buf,
                        size_t count, loff_t *ppos)
{
    struct my_device *dev = filp->private_data;
    unsigned long flags;
    int data;

    /*
     * 进程上下文中,如果中断处理函数也访问同一数据,
     * 必须使用 spin_lock_irqsave 禁止本地中断
     * 防止在持锁期间被中断打断,中断处理函数再次尝试获取锁导致死锁
     */
    spin_lock_irqsave(&dev->lock, flags);
    data = dev->data;
    spin_unlock_irqrestore(&dev->lock, flags);

    return copy_to_user(buf, &data, sizeof(data)) ? -EFAULT : sizeof(data);
}

7.6 读写自旋锁

7.6.1 读写自旋锁的原理

读写自旋锁(rwlock)允许多个读者同时持锁,但写者必须独占:

复制代码
读写自旋锁的并发规则:

状态          读者请求    写者请求
─────────────────────────────────
无锁          允许        允许
有读锁(N个) 允许        阻塞
有写锁        阻塞        阻塞

适用场景:读操作远多于写操作的共享数据
例如:路由表、配置参数、设备状态信息

7.6.2 读写自旋锁的 API

c 复制代码
#include <linux/rwlock.h>

/* ── 定义和初始化 ──────────────────────────────────────── */
rwlock_t my_rwlock;
rwlock_init(&my_rwlock);

DEFINE_RWLOCK(my_rwlock);    /* 静态定义并初始化 */

/* ── 读操作(允许多个读者同时持锁)────────────────────── */
read_lock(&my_rwlock);
/* 读临界区(只读,不修改共享数据) */
read_unlock(&my_rwlock);

/* 禁止中断版本 */
unsigned long flags;
read_lock_irqsave(&my_rwlock, flags);
/* 读临界区 */
read_unlock_irqrestore(&my_rwlock, flags);

/* ── 写操作(独占,排斥所有读者和写者)────────────────── */
write_lock(&my_rwlock);
/* 写临界区(修改共享数据) */
write_unlock(&my_rwlock);

/* 禁止中断版本 */
write_lock_irqsave(&my_rwlock, flags);
/* 写临界区 */
write_unlock_irqrestore(&my_rwlock, flags);

7.6.3 读写自旋锁的使用案例

c 复制代码
/* 场景:设备配置参数,读多写少 */
struct device_config {
    rwlock_t    lock;
    int         baudrate;
    int         data_bits;
    int         parity;
    int         stop_bits;
};

static struct device_config config = {
    .lock      = __RW_LOCK_UNLOCKED(config.lock),
    .baudrate  = 115200,
    .data_bits = 8,
    .parity    = 0,
    .stop_bits = 1,
};

/* 读取配置(多个线程可以同时读) */
int get_baudrate(void)
{
    int rate;
    read_lock(&config.lock);
    rate = config.baudrate;
    read_unlock(&config.lock);
    return rate;
}

/* 更新配置(独占写) */
void set_baudrate(int rate)
{
    write_lock(&config.lock);
    config.baudrate = rate;
    /* 同时更新相关硬件寄存器 */
    write_hw_baudrate(rate);
    write_unlock(&config.lock);
}

7.7 顺序锁

7.7.1 顺序锁的原理

顺序锁(seqlock)是一种写者优先的读写锁,读者不阻塞写者,但读者需要检测是否发生了写操作:

复制代码
顺序锁工作原理:

写者:
  获取写锁 → 序列号加1(变为奇数)→ 修改数据 → 序列号加1(变为偶数)→ 释放写锁

读者:
  读取序列号(seq1)→ 读取数据 → 读取序列号(seq2)
  如果 seq1 == seq2 且为偶数:读取成功
  如果 seq1 != seq2 或为奇数:写者正在写,重试

特点:
  ✓ 读者不阻塞写者(写者优先)
  ✓ 适合写操作很少但对写实时性要求高的场景
  ✗ 读者可能需要多次重试(写操作频繁时性能差)
  ✗ 读者不能修改数据(否则破坏一致性)
  ✗ 读者不能持有指针(写者可能释放指针指向的内存)

7.7.2 顺序锁的 API

c 复制代码
#include <linux/seqlock.h>

/* ── 定义和初始化 ──────────────────────────────────────── */
seqlock_t my_seqlock;
seqlock_init(&my_seqlock);

DEFINE_SEQLOCK(my_seqlock);  /* 静态定义并初始化 */

/* ── 写操作 ────────────────────────────────────────────── */
write_seqlock(&my_seqlock);
/* 写临界区:修改共享数据 */
write_sequnlock(&my_seqlock);

/* 禁止中断版本 */
unsigned long flags;
write_seqlock_irqsave(&my_seqlock, flags);
/* 写临界区 */
write_sequnlock_irqrestore(&my_seqlock, flags);

/* ── 读操作(可能需要重试)────────────────────────────── */
unsigned int seq;
do {
    seq = read_seqbegin(&my_seqlock);  /* 读取序列号 */
    /* 读取共享数据(不能修改!) */
    x = shared_data.x;
    y = shared_data.y;
} while (read_seqretry(&my_seqlock, seq)); /* 检测是否有写操作 */
/* 读取成功,x 和 y 是一致的 */

7.7.3 顺序锁的典型应用

c 复制代码
/* Linux 内核中 jiffies 的读取就使用了顺序锁 */

/* 内核时钟更新(写操作,在时钟中断中执行) */
write_seqlock(&xtime_lock);
xtime.tv_sec++;
xtime.tv_nsec = 0;
write_sequnlock(&xtime_lock);

/* 用户空间读取时间(读操作) */
struct timespec ts;
unsigned int seq;
do {
    seq = read_seqbegin(&xtime_lock);
    ts = xtime;
} while (read_seqretry(&xtime_lock, seq));
/* ts 是一致的时间值 */

7.8 读-复制-更新(RCU)

7.8.1 RCU 的原理

RCU(Read-Copy-Update)是 Linux 内核中最高效的并发控制机制之一,专为读多写少场景设计:

复制代码
RCU 工作原理:

读者:
  rcu_read_lock()    ← 进入 RCU 读临界区(几乎零开销)
  读取数据指针
  访问数据
  rcu_read_unlock()  ← 离开 RCU 读临界区

写者(更新数据):
  1. 复制(Copy):复制要修改的数据结构
  2. 修改(Update):修改副本
  3. 替换(Replace):原子地替换指针,使读者看到新数据
  4. 等待(Wait):等待所有正在读旧数据的读者完成
  5. 释放(Free):释放旧数据

关键特性:
  ✓ 读者几乎零开销(不需要获取任何锁)
  ✓ 读者不会阻塞写者
  ✓ 写者不会阻塞读者
  ✗ 写者需要等待所有读者完成(宽限期)
  ✗ 只适用于指针保护的数据结构
  ✗ 读者不能修改数据

7.8.2 RCU 的 API

c 复制代码
#include <linux/rcupdate.h>

/* ── 读者 API ──────────────────────────────────────────── */
rcu_read_lock();              /* 进入 RCU 读临界区 */
/* 读取 RCU 保护的指针 */
struct my_data *p = rcu_dereference(rcu_ptr);  /* 安全地读取指针 */
/* 使用 p 指向的数据 */
rcu_read_unlock();            /* 离开 RCU 读临界区 */

/* ── 写者 API ──────────────────────────────────────────── */
/* 1. 分配新数据并初始化 */
struct my_data *new_data = kmalloc(sizeof(*new_data), GFP_KERNEL);
*new_data = *old_data;        /* 复制旧数据 */
new_data->value = new_value;  /* 修改副本 */

/* 2. 原子地替换指针(读者立即看到新数据) */
rcu_assign_pointer(rcu_ptr, new_data);

/* 3. 等待所有读者完成(宽限期) */
synchronize_rcu();            /* 阻塞等待,直到所有读者离开临界区 */
/* 或异步版本 */
call_rcu(&old_data->rcu_head, my_rcu_callback); /* 异步,回调中释放 */

/* 4. 释放旧数据 */
kfree(old_data);

7.8.3 RCU 的完整案例

c 复制代码
/* 场景:全局配置结构体,读操作极频繁,写操作极少 */
#include <linux/rcupdate.h>
#include <linux/slab.h>

struct global_config {
    int    speed;
    int    mode;
    char   name[32];
    struct rcu_head rcu;   /* RCU 回调头 */
};

/* RCU 保护的全局指针 */
static struct global_config __rcu *g_config;
static DEFINE_SPINLOCK(config_update_lock);  /* 保护写操作 */

/* 读取配置(高频操作,几乎零开销) */
int get_speed(void)
{
    struct global_config *cfg;
    int speed;

    rcu_read_lock();
    cfg = rcu_dereference(g_config);
    speed = cfg->speed;
    rcu_read_unlock();

    return speed;
}

/* RCU 回调:宽限期结束后释放旧数据 */
static void config_rcu_free(struct rcu_head *head)
{
    struct global_config *cfg = container_of(head,
                                              struct global_config, rcu);
    kfree(cfg);
}

/* 更新配置(低频操作) */
int update_config(int new_speed, int new_mode)
{
    struct global_config *new_cfg, *old_cfg;

    /* 分配新配置 */
    new_cfg = kmalloc(sizeof(*new_cfg), GFP_KERNEL);
    if (!new_cfg)
        return -ENOMEM;

    /* 使用写锁保护写操作(防止多个写者并发) */
    spin_lock(&config_update_lock);

    /* 复制旧配置 */
    old_cfg = rcu_dereference_protected(g_config,
                                         lockdep_is_held(&config_update_lock));
    *new_cfg = *old_cfg;

    /* 修改新配置 */
    new_cfg->speed = new_speed;
    new_cfg->mode  = new_mode;

    /* 原子替换指针 */
    rcu_assign_pointer(g_config, new_cfg);

    spin_unlock(&config_update_lock);

    /* 异步等待宽限期,然后释放旧配置 */
    call_rcu(&old_cfg->rcu, config_rcu_free);

    return 0;
}

7.9 信号量

7.9.1 信号量的原理

信号量(Semaphore)是一种可以睡眠等待的锁,当无法获取信号量时,进程会进入睡眠状态,让出 CPU:

复制代码
信号量工作原理:

信号量内部维护一个计数值(count):
  count > 0:可以获取,count--
  count = 0:不可获取,进程进入睡眠队列等待
  释放时:count++,唤醒等待队列中的进程

二值信号量(count初始为1):等价于互斥锁
计数信号量(count初始为N):允许N个进程同时访问

与自旋锁的区别:
  自旋锁:等待时 CPU 忙等(自旋),不睡眠
  信号量:等待时进程睡眠,让出 CPU
  
  适用场景:
  自旋锁 → 临界区很短(微秒级),中断上下文
  信号量 → 临界区较长(毫秒级),进程上下文

7.9.2 信号量的 API

c 复制代码
#include <linux/semaphore.h>

/* ── 定义和初始化 ──────────────────────────────────────── */
struct semaphore sem;
sema_init(&sem, 1);          /* 初始化为1(二值信号量,相当于互斥锁) */
sema_init(&sem, 5);          /* 初始化为5(计数信号量,允许5个并发) */

/* 静态初始化 */
static DEFINE_SEMAPHORE(sem);  /* 初始化为1的二值信号量 */

/* ── P 操作(获取信号量)──────────────────────────────── */
down(&sem);                  /* 获取信号量(不可被信号中断) */

/* 可被信号中断的版本(推荐在驱动中使用) */
if (down_interruptible(&sem)) {
    return -ERESTARTSYS;     /* 被信号中断,返回错误 */
}

/* 非阻塞版本 */
if (down_trylock(&sem)) {
    /* 获取失败,信号量已被占用 */
    return -EBUSY;
}

/* ── V 操作(释放信号量)──────────────────────────────── */
up(&sem);                    /* 释放信号量,唤醒等待的进程 */

7.9.3 信号量的使用案例

c 复制代码
/* 场景:限制同时访问设备的进程数量(最多3个) */
static struct semaphore access_sem;

static int __init my_init(void)
{
    sema_init(&access_sem, 3);  /* 最多允许3个进程同时访问 */
    /* ... */
    return 0;
}

static int my_open(struct inode *inode, struct file *filp)
{
    /* 获取信号量(如果已有3个进程在访问,则等待) */
    if (down_interruptible(&access_sem))
        return -ERESTARTSYS;

    /* 成功获取,进行初始化 */
    return 0;
}

static int my_release(struct inode *inode, struct file *filp)
{
    up(&access_sem);  /* 释放信号量 */
    return 0;
}

7.10 互斥体

7.10.1 互斥体的原理

互斥体(Mutex)是 Linux 2.6.16 引入的专用互斥锁,比信号量更高效,语义更清晰:

复制代码
互斥体 vs 信号量:

互斥体(mutex):
  ✓ 专为互斥设计,语义清晰
  ✓ 比信号量更快(优化的实现)
  ✓ 支持优先级继承(防止优先级反转)
  ✓ 调试支持更好(lockdep 检测死锁)
  ✗ 只能用于进程上下文(不能在中断上下文使用)
  ✗ 只能由持锁者释放(不能在一个函数加锁,另一个函数解锁)

信号量(semaphore):
  ✓ 可以在一个上下文加锁,另一个上下文解锁
  ✓ 支持计数(允许多个并发访问)
  ✗ 比互斥体慢
  ✗ 没有优先级继承

结论:在进程上下文的互斥场景,优先使用 mutex

7.10.2 互斥体的 API

c 复制代码
#include <linux/mutex.h>

/* ── 定义和初始化 ──────────────────────────────────────── */
struct mutex my_mutex;
mutex_init(&my_mutex);       /* 动态初始化 */

DEFINE_MUTEX(my_mutex);      /* 静态定义并初始化 */

/* ── 加锁 ──────────────────────────────────────────────── */
mutex_lock(&my_mutex);       /* 获取锁(不可被信号中断) */

/* 可被信号中断的版本(推荐在驱动中使用) */
if (mutex_lock_interruptible(&my_mutex)) {
    return -ERESTARTSYS;
}

/* 可被任何信号中断的版本 */
if (mutex_lock_killable(&my_mutex)) {
    return -EINTR;
}

/* 非阻塞版本 */
if (!mutex_trylock(&my_mutex)) {
    return -EBUSY;
}

/* ── 解锁 ──────────────────────────────────────────────── */
mutex_unlock(&my_mutex);     /* 释放锁 */

/* ── 检查锁状态 ────────────────────────────────────────── */
bool locked = mutex_is_locked(&my_mutex);  /* 是否已被锁定 */

7.10.3 互斥体的使用规则

复制代码
互斥体使用规则:

1. 只能在进程上下文使用
   ✗ 不能在中断处理函数中使用 mutex_lock
   ✗ 不能在 tasklet 或软中断中使用

2. 只能由持锁者解锁
   ✗ 错误:线程A 加锁,线程B 解锁

3. 不能递归加锁
   ✗ 错误:mutex_lock(&m); mutex_lock(&m); /* 死锁! */

4. 持锁期间不能退出进程
   持锁的进程退出会导致锁永远无法释放

5. 多个锁的加锁顺序必须一致(防止死锁)
   所有代码路径都必须按相同顺序获取多个锁

7.10.4 互斥体的完整使用案例

c 复制代码
struct globalmem_dev {
    struct cdev   cdev;
    unsigned char mem[GLOBALMEM_SIZE];
    struct mutex  mutex;   /* 互斥体保护 mem 数组 */
};

static int globalmem_open(struct inode *inode, struct file *filp)
{
    struct globalmem_dev *dev = container_of(inode->i_cdev,
                                              struct globalmem_dev, cdev);
    filp->private_data = dev;
    return 0;
}

static ssize_t globalmem_read(struct file *filp, char __user *buf,
                               size_t size, loff_t *ppos)
{
    struct globalmem_dev *dev = filp->private_data;
    unsigned long p = *ppos;
    unsigned int count = size;
    int ret = 0;

    if (p >= GLOBALMEM_SIZE) return 0;
    if (count > GLOBALMEM_SIZE - p) count = GLOBALMEM_SIZE - p;

    /*
     * 使用 mutex_lock_interruptible 而不是 mutex_lock
     * 允许用户通过 Ctrl+C 中断等待,提高用户体验
     */
    if (mutex_lock_interruptible(&dev->mutex))
        return -ERESTARTSYS;

    if (copy_to_user(buf, dev->mem + p, count))
        ret = -EFAULT;
    else {
        *ppos += count;
        ret = count;
    }

    mutex_unlock(&dev->mutex);
    return ret;
}

static ssize_t globalmem_write(struct file *filp, const char __user *buf,
                                size_t size, loff_t *ppos)
{
    struct globalmem_dev *dev = filp->private_data;
    unsigned long p = *ppos;
    unsigned int count = size;
    int ret = 0;

    if (p >= GLOBALMEM_SIZE) return 0;
    if (count > GLOBALMEM_SIZE - p) count = GLOBALMEM_SIZE - p;

    if (mutex_lock_interruptible(&dev->mutex))
        return -ERESTARTSYS;

    if (copy_from_user(dev->mem + p, buf, count))
        ret = -EFAULT;
    else {
        *ppos += count;
        ret = count;
    }

    mutex_unlock(&dev->mutex);
    return ret;
}

7.11 完成量

7.11.1 完成量的原理

完成量(Completion)用于线程间的同步,一个线程等待另一个线程完成某项工作:

复制代码
完成量工作原理:

线程A(等待者):
  init_completion(&comp)
  wait_for_completion(&comp)  ← 阻塞等待
  /* 线程B 完成工作后继续执行 */

线程B(完成者):
  /* 执行某项工作 */
  complete(&comp)             ← 通知线程A

典型应用场景:
  1. 驱动初始化:等待硬件初始化完成
  2. DMA 传输:等待 DMA 传输完成
  3. 中断同步:等待中断处理完成
  4. 内核线程:等待内核线程启动完成

7.11.2 完成量的 API

c 复制代码
#include <linux/completion.h>

/* ── 定义和初始化 ──────────────────────────────────────── */
struct completion my_comp;
init_completion(&my_comp);   /* 动态初始化 */

DECLARE_COMPLETION(my_comp); /* 静态定义并初始化 */

/* ── 等待完成 ──────────────────────────────────────────── */
wait_for_completion(&my_comp);          /* 无限等待 */

/* 带超时的等待(返回剩余 jiffies,0 表示超时) */
unsigned long ret = wait_for_completion_timeout(&my_comp,
                                                 msecs_to_jiffies(5000));
if (ret == 0) {
    pr_err("等待超时!\n");
    return -ETIMEDOUT;
}

/* 可被信号中断的等待 */
if (wait_for_completion_interruptible(&my_comp)) {
    return -ERESTARTSYS;
}

/* ── 发出完成信号 ──────────────────────────────────────── */
complete(&my_comp);          /* 唤醒一个等待者 */
complete_all(&my_comp);      /* 唤醒所有等待者 */

/* ── 重新初始化(复用完成量)──────────────────────────── */
reinit_completion(&my_comp);

7.11.3 完成量的使用案例

c 复制代码
/* 场景:等待 DMA 传输完成 */
struct dma_dev {
    struct completion dma_done;
    /* ... */
};

/* DMA 完成中断处理函数 */
static irqreturn_t dma_irq_handler(int irq, void *dev_id)
{
    struct dma_dev *dev = dev_id;

    /* DMA 传输完成,发出完成信号 */
    complete(&dev->dma_done);
    return IRQ_HANDLED;
}

/* 发起 DMA 传输并等待完成 */
static int do_dma_transfer(struct dma_dev *dev, void *buf, size_t len)
{
    unsigned long timeout;

    /* 初始化完成量 */
    init_completion(&dev->dma_done);

    /* 启动 DMA 传输 */
    start_dma(dev, buf, len);

    /* 等待 DMA 完成(最多等待 5 秒) */
    timeout = wait_for_completion_timeout(&dev->dma_done,
                                           msecs_to_jiffies(5000));
    if (timeout == 0) {
        pr_err("DMA 传输超时!\n");
        stop_dma(dev);
        return -ETIMEDOUT;
    }

    pr_info("DMA 传输完成\n");
    return 0;
}

/* 场景:等待内核线程启动 */
static DECLARE_COMPLETION(thread_started);

static int my_thread_func(void *data)
{
    /* 线程初始化工作 */
    pr_info("内核线程启动\n");

    /* 通知主线程:线程已启动 */
    complete(&thread_started);

    /* 线程主循环 */
    while (!kthread_should_stop()) {
        /* 执行工作 */
        msleep(100);
    }
    return 0;
}

static int __init my_init(void)
{
    struct task_struct *thread;

    thread = kthread_run(my_thread_func, NULL, "my_thread");
    if (IS_ERR(thread))
        return PTR_ERR(thread);

    /* 等待线程启动完成 */
    wait_for_completion(&thread_started);
    pr_info("内核线程已就绪\n");

    return 0;
}

7.12 增加并发控制后的 globalmem

7.12.1 并发问题分析

在第 6 章的 globalmem 驱动中,已经使用了 mutex 保护设备内存。本节对并发控制进行完整分析和优化:

复制代码
globalmem 驱动的并发场景分析:

场景1:多进程同时 read/write
  进程A 执行 read,进程B 执行 write
  → 可能读到写了一半的数据
  → 解决:mutex 保护 mem 数组

场景2:多进程同时 open
  进程A 和进程B 同时 open
  → open 函数本身无共享数据,无需保护

场景3:ioctl MEM_CLEAR 与 read/write 并发
  进程A 执行 read,进程B 执行 ioctl MEM_CLEAR
  → 可能读到清空了一半的数据
  → 解决:同一个 mutex 保护所有对 mem 的访问

场景4:多个设备实例的并发
  每个设备实例有独立的 mutex
  不同设备实例之间不需要同步

7.12.2 完整的并发安全 globalmem 驱动

c 复制代码
/*
 * globalmem_safe.c ------ 增加完整并发控制的 globalmem 驱动
 *
 * 并发控制策略:
 * 1. 使用 mutex 保护设备内存(mem 数组)的所有访问
 * 2. 使用 atomic_t 实现设备引用计数
 * 3. 使用 completion 实现初始化同步
 */

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/atomic.h>
#include <linux/completion.h>

#define GLOBALMEM_SIZE   0x1000
#define GLOBALMEM_MAJOR  230
#define DEVICE_NUM       2

/* ioctl 命令定义 */
#define GLOBALMEM_MAGIC  'g'
#define MEM_CLEAR        _IO(GLOBALMEM_MAGIC, 0)
#define MEM_GETSIZE      _IOR(GLOBALMEM_MAGIC, 1, int)

struct globalmem_dev {
    struct cdev    cdev;
    unsigned char  mem[GLOBALMEM_SIZE];

    /* 并发控制 */
    struct mutex   mutex;          /* 保护 mem 数组 */
    atomic_t       open_count;     /* 当前打开的文件描述符数量 */
    struct completion init_done;   /* 初始化完成信号 */
};

static int globalmem_major = GLOBALMEM_MAJOR;
static struct globalmem_dev globalmem_devs[DEVICE_NUM];
static struct class *globalmem_class;

/* ── open ────────────────────────────────────────────────── */
static int globalmem_open(struct inode *inode, struct file *filp)
{
    struct globalmem_dev *dev = container_of(inode->i_cdev,
                                              struct globalmem_dev, cdev);

    /* 等待设备初始化完成(如果初始化需要时间) */
    wait_for_completion(&dev->init_done);

    /* 增加引用计数 */
    atomic_inc(&dev->open_count);

    filp->private_data = dev;

    pr_info("globalmem: 设备打开,当前打开数 = %d\n",
            atomic_read(&dev->open_count));
    return 0;
}

/* ── release ─────────────────────────────────────────────── */
static int globalmem_release(struct inode *inode, struct file *filp)
{
    struct globalmem_dev *dev = filp->private_data;

    /* 减少引用计数 */
    atomic_dec(&dev->open_count);

    pr_info("globalmem: 设备关闭,当前打开数 = %d\n",
            atomic_read(&dev->open_count));
    return 0;
}

/* ── read ────────────────────────────────────────────────── */
static ssize_t globalmem_read(struct file *filp, char __user *buf,
                               size_t size, loff_t *ppos)
{
    unsigned long p = *ppos;
    unsigned int count = size;
    int ret = 0;
    struct globalmem_dev *dev = filp->private_data;

    if (p >= GLOBALMEM_SIZE) return 0;
    if (count > GLOBALMEM_SIZE - p) count = GLOBALMEM_SIZE - p;

    /*
     * 使用 mutex_lock_interruptible:
     * 允许用户通过信号(如 Ctrl+C)中断等待
     * 比 mutex_lock 更友好
     */
    if (mutex_lock_interruptible(&dev->mutex))
        return -ERESTARTSYS;

    if (copy_to_user(buf, dev->mem + p, count))
        ret = -EFAULT;
    else {
        *ppos += count;
        ret = count;
        pr_info("globalmem: 读取 %u 字节,位置 %lu\n", count, p);
    }

    mutex_unlock(&dev->mutex);
    return ret;
}

/* ── write ───────────────────────────────────────────────── */
static ssize_t globalmem_write(struct file *filp, const char __user *buf,
                                size_t size, loff_t *ppos)
{
    unsigned long p = *ppos;
    unsigned int count = size;
    int ret = 0;
    struct globalmem_dev *dev = filp->private_data;

    if (p >= GLOBALMEM_SIZE) return 0;
    if (count > GLOBALMEM_SIZE - p) count = GLOBALMEM_SIZE - p;

    if (mutex_lock_interruptible(&dev->mutex))
        return -ERESTARTSYS;

    if (copy_from_user(dev->mem + p, buf, count))
        ret = -EFAULT;
    else {
        *ppos += count;
        ret = count;
        pr_info("globalmem: 写入 %u 字节,位置 %lu\n", count, p);
    }

    mutex_unlock(&dev->mutex);
    return ret;
}

/* ── llseek ──────────────────────────────────────────────── */
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
    loff_t ret = 0;

    switch (orig) {
    case SEEK_SET:
        if (offset < 0 || (unsigned int)offset > GLOBALMEM_SIZE) {
            ret = -EINVAL;
            break;
        }
        filp->f_pos = (unsigned int)offset;
        ret = filp->f_pos;
        break;
    case SEEK_CUR:
        if ((filp->f_pos + offset) > GLOBALMEM_SIZE ||
            (filp->f_pos + offset) < 0) {
            ret = -EINVAL;
            break;
        }
        filp->f_pos += offset;
        ret = filp->f_pos;
        break;
    default:
        ret = -EINVAL;
    }
    return ret;
}

/* ── ioctl ───────────────────────────────────────────────── */
static long globalmem_ioctl(struct file *filp, unsigned int cmd,
                             unsigned long arg)
{
    struct globalmem_dev *dev = filp->private_data;
    int size;

    if (_IOC_TYPE(cmd) != GLOBALMEM_MAGIC)
        return -ENOTTY;

    switch (cmd) {
    case MEM_CLEAR:
        if (mutex_lock_interruptible(&dev->mutex))
            return -ERESTARTSYS;
        memset(dev->mem, 0, GLOBALMEM_SIZE);
        mutex_unlock(&dev->mutex);
        pr_info("globalmem: 内存已清空\n");
        break;

    case MEM_GETSIZE:
        size = GLOBALMEM_SIZE;
        if (copy_to_user((int __user *)arg, &size, sizeof(int)))
            return -EFAULT;
        break;

    default:
        return -EINVAL;
    }
    return 0;
}

static const struct file_operations globalmem_fops = {
    .owner          = THIS_MODULE,
    .llseek         = globalmem_llseek,
    .read           = globalmem_read,
    .write          = globalmem_write,
    .unlocked_ioctl = globalmem_ioctl,
    .open           = globalmem_open,
    .release        = globalmem_release,
};

/* ── 模块加载 ─────────────────────────────────────────────── */
static int __init globalmem_init(void)
{
    int ret, i;
    dev_t devno = MKDEV(globalmem_major, 0);

    ret = register_chrdev_region(devno, DEVICE_NUM, "globalmem");
    if (ret < 0) return ret;

    globalmem_class = class_create(THIS_MODULE, "globalmem");
    if (IS_ERR(globalmem_class)) {
        ret = PTR_ERR(globalmem_class);
        goto fail_class;
    }

    for (i = 0; i < DEVICE_NUM; i++) {
        /* 初始化并发控制机制 */
        mutex_init(&globalmem_devs[i].mutex);
        atomic_set(&globalmem_devs[i].open_count, 0);
        init_completion(&globalmem_devs[i].init_done);

        /* 初始化 cdev */
        cdev_init(&globalmem_devs[i].cdev, &globalmem_fops);
        globalmem_devs[i].cdev.owner = THIS_MODULE;
        ret = cdev_add(&globalmem_devs[i].cdev,
                       MKDEV(globalmem_major, i), 1);
        if (ret) goto fail_cdev;

        /* 创建设备文件 */
        device_create(globalmem_class, NULL,
                      MKDEV(globalmem_major, i),
                      NULL, "globalmem%d", i);

        /* 模拟初始化完成(实际驱动中可能需要等待硬件就绪) */
        complete(&globalmem_devs[i].init_done);
    }

    pr_info("globalmem: %d 个设备初始化完成\n", DEVICE_NUM);
    return 0;

fail_cdev:
    while (--i >= 0) {
        device_destroy(globalmem_class, MKDEV(globalmem_major, i));
        cdev_del(&globalmem_devs[i].cdev);
    }
    class_destroy(globalmem_class);
fail_class:
    unregister_chrdev_region(devno, DEVICE_NUM);
    return ret;
}

/* ── 模块卸载 ─────────────────────────────────────────────── */
static void __exit globalmem_exit(void)
{
    int i;
    for (i = 0; i < DEVICE_NUM; i++) {
        device_destroy(globalmem_class, MKDEV(globalmem_major, i));
        cdev_del(&globalmem_devs[i].cdev);
    }
    class_destroy(globalmem_class);
    unregister_chrdev_region(MKDEV(globalmem_major, 0), DEVICE_NUM);
    pr_info("globalmem: 驱动已卸载\n");
}

module_init(globalmem_init);
module_exit(globalmem_exit);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("并发安全的 globalmem 字符设备驱动");

本章小结

章节 核心知识点 关键 API
7.1 并发与竞态 并发的4个来源(SMP/抢占/中断/软中断);竞态的概念;临界区 概念理解
7.2 编译/执行乱序 编译器重排;CPU 乱序执行;编译屏障;内存屏障 barrier()mb()rmb()wmb()
7.3 中断屏蔽 禁止本地中断;保存/恢复中断状态;局限性 local_irq_save()local_irq_restore()
7.4 原子操作 原子整数操作;原子位操作;引用计数;设备独占 atomic_inc()atomic_dec_and_test()set_bit()
7.5 自旋锁 忙等待原理;irqsave 版本;使用规则 spin_lock_irqsave()spin_unlock_irqrestore()
7.6 读写自旋锁 读者共享/写者独占;读多写少场景 read_lock()write_lock()
7.7 顺序锁 写者优先;序列号机制;读者重试 write_seqlock()read_seqbegin()read_seqretry()
7.8 RCU 读-复制-更新;宽限期;零开销读 rcu_read_lock()rcu_dereference()synchronize_rcu()
7.9 信号量 可睡眠等待;计数信号量;P/V 操作 down_interruptible()up()
7.10 互斥体 专用互斥锁;优先级继承;使用规则 mutex_lock_interruptible()mutex_unlock()
7.11 完成量 线程同步;等待/通知机制;超时等待 wait_for_completion_timeout()complete()
7.12 并发安全globalmem mutex保护内存;atomic引用计数;completion初始化同步 综合应用

并发控制机制选择指南

复制代码
选择并发控制机制的决策树:

是否在中断上下文?
  是 → 使用原子操作 或 自旋锁(spin_lock)
  否 → 继续判断

临界区是否很短(< 几十微秒)?
  是 → 使用自旋锁(spin_lock_irqsave)
  否 → 继续判断

是否读多写少?
  是 → 使用读写自旋锁(rwlock)或 RCU
  否 → 继续判断

是否需要等待某个事件完成?
  是 → 使用完成量(completion)
  否 → 使用互斥体(mutex)

特殊场景:
  简单整数计数 → 原子操作(atomic_t)
  写者优先的读写 → 顺序锁(seqlock)
  极致读性能 → RCU

参考文献:宋宝华《Linux设备驱动开发详解:基于最新的Linux 4.0内核》,机械工业出版社,2015年