04-并发同步与原子操作.md 28 KB


title: 并发同步与原子操作 tags: [Linux驱动, 并发, 锁, 原子操作, 嵌入式, 自旋锁, 信号量, 互斥体] created: 2026-09-16 updated: 2026-09-17 pdf_ref:

  • "【正点原子】I.MX6U嵌入式Linux驱动开发指南V2.0.1 - 第四十七章 Linux并发与竞争"
  • "【正点原子】I.MX6U嵌入式Linux驱动开发指南V2.0.1 - 第四十八章 Linux并发与竞争实验" ---

并发同步与原子操作

💡 关联知识: [[FreeRTOS学习笔记/07-中断管理与临界段]] | [[03-Linux驱动开发核心/05-中断下半部机制]] | [[03-Linux驱动开发核心/06-定时器与中断]]


一、并发问题概述

1.1 什么是竞态条件

竞态条件(Race Condition) 是指多个执行单元(线程、中断、进程)同时访问共享资源,且最终结果依赖于执行时序的现象。

💡 生活类比:公司只有一台打印机,小明和小红同时发送打印任务。如果小明的文档先发送但打印较慢,小红的文档后发送但打印较快,最终小明可能拿到小红的文档——这就是竞态条件。

sequenceDiagram
    participant P1 as 进程A
    participant P2 as 进程B
    participant R as 共享资源

    Note over P1,R: 理想执行顺序
    P1->>R: 读取资源
    P1->>R: 修改资源
    P2->>R: 读取资源
    P2->>R: 修改资源

    Note over P1,R: 实际可能的执行顺序
    P1->>R: 读取资源
    P2->>R: 读取资源
    P1->>R: 修改资源
    P2->>R: 修改资源(覆盖P1的结果)

1.2 竞态条件产生场景

场景 说明 危害程度
中断与进程 进程访问共享数据时被中断,中断处理函数也访问同一数据 ⭐⭐⭐⭐
多进程/多线程 多个进程或线程同时读写共享内存 ⭐⭐⭐
多CPU(SMP) 多核处理器上多个CPU核心同时执行访问同一数据 ⭐⭐⭐⭐⭐
抢占调度 进程在内核态执行时被高优先级任务抢占 ⭐⭐⭐

1.3 竞态条件的危害

  1. 数据不一致:共享变量值被意外覆盖
  2. 逻辑错误:程序执行流程偏离预期
  3. 系统崩溃:内核数据结构被破坏导致kernel panic
  4. 难以复现:时序相关的问题极难调试

1.4 并发问题示意图

flowchart TD
    A[并发访问共享资源] --> B{资源类型}
    B --> C[简单变量]
    B --> D[复杂数据结构]
    B --> E[硬件寄存器]

    C --> F[原子操作]
    D --> G[自旋锁/互斥体]
    E --> H[原子操作/自旋锁]

    F --> I[保证操作不可中断]
    G --> J[保证临界区互斥]
    H --> K[保证硬件访问原子性]

    style A fill:#fee2e2,stroke:#dc2626
    style F fill:#dcfce7,stroke:#16a34a
    style G fill:#dcfce7,stroke:#16a34a
    style H fill:#dcfce7,stroke:#16a34a

1.5 为什么需要关注并发

Linux是多任务操作系统,以下因素导致并发不可避免:

  • 多线程/多进程:用户空间程序并发执行
  • 抢占式调度:2.6版本后Linux内核支持抢占
  • 中断程序并发:硬件中断随时可能发生
  • SMP多核并行:多CPU核心同时执行内核代码

⚠️ 注意:编写驱动程序时必须考虑并发保护,否则会导致难以定位的bug。


二、原子操作

2.1 什么是原子操作

原子操作(Atomic Operation) 是指在执行过程中不会被中断的操作,要么完全执行,要么完全不执行。适用于简单的变量操作。

/* 原子变量定义 - include/linux/types.h */
typedef struct {
    int counter;
} atomic_t;

2.2 原子变量操作API

函数 说明
ATOMIC_INIT(int i) 定义原子变量时初始化值
int atomic_read(atomic_t *v) 读取原子变量的值
void atomic_set(atomic_t *v, int i) 设置原子变量的值
void atomic_add(int i, atomic_t *v) 原子加法
void atomic_sub(int i, atomic_t *v) 原子减法
void atomic_inc(atomic_t *v) 原子自增
void atomic_dec(atomic_t *v) 原子自减
int atomic_inc_and_test(atomic_t *v) 自增并测试是否为0
int atomic_dec_and_test(atomic_t *v) 自减并测试是否为0
int atomic_sub_and_test(int i, atomic_t *v) 减法并测试是否为0

2.3 原子操作使用示例

#include <linux/atomic.h>

/* 定义并初始化原子变量 */
static atomic_t led_status = ATOMIC_INIT(0);

/* 在驱动初始化时设置 */
atomic_set(&led_status, 1);  /* 设置为1,表示LED可用 */

/* 检查并获取设备 */
static int led_open(struct inode *inode, struct file *filp)
{
    /* 原子减1并测试是否为0 */
    if (!atomic_dec_and_test(&led_status)) {
        atomic_inc(&led_status);  /* 恢复原值 */
        return -EBUSY;           /* 设备被占用 */
    }
    return 0;
}

/* 关闭设备时释放 */
static int led_release(struct inode *inode, struct file *filp)
{
    atomic_inc(&led_status);  /* 原子加1,释放设备 */
    return 0;
}

2.4 原子位操作API

函数 说明
void set_bit(int nr, void *p) 设置第nr位为1
void clear_bit(int nr, void *p) 清除第nr位
void change_bit(int nr, void *p) 反转第nr位
int test_bit(int nr, void *p) 读取第nr位的值
int test_and_set_bit(int nr, void *p) 设置第nr位并返回原值
int test_and_clear_bit(int nr, void *p) 清除第nr位并返回原值

三、自旋锁

3.1 什么是自旋锁

自旋锁(Spinlock) 是一种忙等待的锁机制。当一个线程尝试获取锁时,如果锁已被持有,该线程会在原地"自旋"等待,直到锁被释放。

stateDiagram-v2
    [*] --> 空闲: 初始状态
    空闲 --> 已锁定: spin_lock()
    已锁定 --> 空闲: spin_unlock()
    已锁定 --> 已锁定: 其他CPU尝试获取锁(自旋等待)

    note right of 已锁定
        持有锁的CPU执行临界区代码
        其他CPU在原地循环等待
    end note

3.2 自旋锁类型定义

/* include/linux/spinlock_types.h */
typedef struct spinlock {
    union {
        struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
        struct {
            u8 __padding[LOCK_PADSIZE];
            struct lockdep_map dep_map;
        };
#endif
    };
} spinlock_t;

3.3 自旋锁API

函数 说明 使用场景
DEFINE_SPINLOCK(spinlock_t lock) 静态定义并初始化 全局锁
void spin_lock_init(spinlock_t *lock) 动态初始化 运行时初始化
void spin_lock(spinlock_t *lock) 获取锁(忙等待) 进程上下文
void spin_unlock(spinlock_t *lock) 释放锁 进程上下文
int spin_trylock(spinlock_t *lock) 尝试获取锁,失败返回0 非阻塞场景
int spin_is_locked(spinlock_t *lock) 检查锁状态 调试

3.4 中断安全的自旋锁API

函数 说明
void spin_lock_irq(spinlock_t *lock) 禁止本地中断并获取锁
void spin_unlock_irq(spinlock_t *lock) 释放锁并开启本地中断
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags) 保存中断状态、禁止中断、获取锁
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags) 恢复中断状态、释放锁
void spin_lock_bh(spinlock_t *lock) 禁止下半部并获取锁
void spin_unlock_bh(spinlock_t *lock) 释放锁并开启下半部

3.5 自旋锁使用场景

flowchart TD
    A[需要保护共享资源] --> B{访问上下文}
    B -->|进程上下文| C{是否可能被中断访问?}
    B -->|中断上下文| D[使用spin_lock]
    C -->|是| E[使用spin_lock_irqsave]
    C -->|否| F[使用spin_lock]

    E --> G[临界区执行]
    D --> G
    F --> G

    G --> H{需要保护下半部?}
    H -->|是| I[使用spin_lock_bh]
    H -->|否| J[使用spin_unlock]

    style A fill:#fef3c7,stroke:#f59e0b
    style G fill:#dbeafe,stroke:#2563eb

3.6 自旋锁注意事项

  1. 临界区要尽可能短:自旋锁会忙等待,占用CPU资源
  2. 不能在持有自旋锁时睡眠:会导致死锁
  3. 不能递归获取:同一CPU不能多次获取同一把锁
  4. 考虑中断安全:如果临界区可能被中断访问,必须使用spin_lock_irqsave
  5. 不能在中断处理函数中使用可能睡眠的API

四、信号量

4.1 什么是信号量

信号量(Semaphore) 是一种可睡眠的同步机制。当进程获取信号量失败时会进入睡眠状态,释放CPU给其他任务使用。

sequenceDiagram
    participant P1 as 进程A
    participant S as 信号量
    participant P2 as 进程B

    P1->>S: down() - 获取信号量
    S-->>P1: 成功,继续执行
    P2->>S: down() - 尝试获取
    S-->>P2: 失败,进入睡眠
    P1->>S: up() - 释放信号量
    S->>P2: 唤醒进程B
    P2->>S: 获取成功

4.2 信号量API

#include <linux/semaphore.h>

struct semaphore {
    raw_spinlock_t lock;
    unsigned int count;
    struct list_head wait_list;
};

/* 初始化信号量 */
sem_init(&sem, 1);      /* 互斥信号量(计数为1) */
sem_init(&sem, 10);     /* 计数信号量(允许10个并发) */

/* 获取信号量 */
down(&sem);              /* 不可中断,可能睡眠 */
down_trylock(&sem);      /* 非阻塞,失败返回非0 */
down_interruptible(&sem); /* 可被信号中断 */

/* 释放信号量 */
up(&sem);

4.3 信号量使用示例

static struct semaphore led_sem;

static int __init led_init(void)
{
    sema_init(&led_sem, 1);  /* 初始化为互斥信号量 */
    return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf,
                         size_t cnt, loff_t *offt)
{
    down(&led_sem);          /* 获取信号量,可能睡眠 */
    /* 临界区(可以睡眠的操作) */
    gpiod_set_value(dev->led_gpio, ledstat);
    up(&led_sem);            /* 释放信号量 */
    return 0;
}

4.4 自旋锁 vs 信号量对比表

特性 自旋锁 信号量
等待方式 忙等待(自旋) 睡眠等待
CPU占用 占用CPU 不占用CPU
可睡眠 ❌ 不可以 ✅ 可以
中断上下文 ✅ 可以使用 ❌ 不可以
临界区长度 短(μs级) 长(ms级)
性能开销 低(忙等) 高(上下文切换)
适用场景 中断处理、短临界区 长时间操作、I/O操作
quadrantChart
    title 自旋锁与信号量选择
    x-axis 短临界区 --> 长临界区
    y-axis 不可睡眠 --> 可睡眠
    "中断处理": [0.2, 0.1]
    "短临界区": [0.3, 0.2]
    "驱动初始化": [0.7, 0.8]
    "文件操作": [0.6, 0.9]
    "网络传输": [0.8, 0.7]
    "磁盘I/O": [0.9, 0.9]

五、互斥体

5.1 什么是互斥体

互斥体(Mutex) 是Linux内核中最常用的互斥锁机制。它是一种特殊的信号量,只能由一个线程持有,提供了更严格的互斥保证。

5.2 互斥体API

#include <linux/mutex.h>

/* 定义互斥体 */
struct mutex my_mutex;

/* 初始化 */
mutex_init(&my_mutex);

/* 获取锁 */
mutex_lock(&my_mutex);           /* 可能睡眠 */
mutex_trylock(&my_mutex);        /* 非阻塞,失败返回0 */

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

/* 销毁 */
mutex_destroy(&my_mutex);

5.3 互斥体使用示例

static struct mutex led_mutex;

static int __init led_init(void)
{
    mutex_init(&led_mutex);
    return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf,
                         size_t cnt, loff_t *offt)
{
    mutex_lock(&led_mutex);      /* 获取互斥锁 */
    /* 临界区 */
    gpiod_set_value(dev->led_gpio, ledstat);
    mutex_unlock(&led_mutex);    /* 释放互斥锁 */
    return 0;
}

5.4 互斥体 vs 自旋锁对比

特性 互斥体 自旋锁
睡眠 ✅ 可以睡眠 ❌ 不能睡眠
持有时长 可以较长 必须很短
中断上下文 ❌ 不能使用 ✅ 可以使用
优先级继承 ✅ 支持 ❌ 不支持
递归锁 ❌ 不支持 ❌ 不支持
调试支持 ✅ 丰富 ⚠️ 有限

六、完整源码分析

6.1 原子操作实验 - atomic.c

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define GPIOLED_CNT     1           /* 设备号个数 */
#define GPIOLED_NAME    "gpioled"   /* 名字 */
#define LEDOFF          0           /* 关灯 */
#define LEDON           1           /* 开灯 */

/* gpioled设备结构体 */
struct gpioled_dev {
    dev_t devid;                /* 设备号 */
    struct cdev cdev;           /* cdev */
    struct class *class;        /* 类 */
    struct device *device;      /* 设备 */
    int major;                  /* 主设备号 */
    int minor;                  /* 次设备号 */
    struct device_node *nd;     /* 设备节点 */
    int led_gpio;               /* led所使用的GPIO编号 */
    atomic_t lock;              /* 原子变量 */
};

struct gpioled_dev gpioled;     /* led设备 */

/*
 * @description  : 打开设备
 * @param - inode : 传递给驱动的inode
 * @param - filp  : 设备文件,file结构体有个叫做private_data的成员变量
 *                    一般在open的时候将private_data指向设备结构体
 * @return       : 0 成功; 其他 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
    /* 通过判断原子变量的值来检查LED有没有被别的应用使用 */
    if (!atomic_dec_and_test(&gpioled.lock)) {
        atomic_inc(&gpioled.lock);  /* 小于0的话就加1,使其原子变量等于0 */
        return -EBUSY;              /* LED被使用,返回忙 */
    }

    filp->private_data = &gpioled;  /* 设置私有数据 */
    return 0;
}

/*
 * @description  : 向设备写数据
 * @param - filp  : 设备文件,表示打开的文件描述符
 * @param - buf   : 要写给设备写入的数据
 * @param - cnt   : 要写入的数据长度
 * @param - offt  : 相对于文件首地址的偏移
 * @return        : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t led_write(struct file *filp, const char __user *buf,
                         size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;
    struct gpioled_dev *dev = filp->private_data;

    retvalue = copy_from_user(databuf, buf, cnt);
    if (retvalue < 0) {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    ledstat = databuf[0];       /* 获取状态值 */

    if (ledstat == LEDON) {
        gpio_set_value(dev->led_gpio, 0);   /* 打开LED灯 */
    } else if (ledstat == LEDOFF) {
        gpio_set_value(dev->led_gpio, 1);   /* 关闭LED灯 */
    }
    return 0;
}

/*
 * @description  : 关闭/释放设备
 * @param - filp  : 要关闭的设备文件(文件描述符)
 * @return        : 0 成功; 其他 失败
 */
static int led_release(struct inode *inode, struct file *filp)
{
    struct gpioled_dev *dev = filp->private_data;

    /* 关闭驱动文件的时候释放原子变量 */
    atomic_inc(&dev->lock);
    return 0;
}

/* 设备操作函数 */
static struct file_operations gpioled_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    .release = led_release,
};

/*
 * @description : 驱动入口函数
 * @return       : 无
 */
static int __init led_init(void)
{
    int ret = 0;

    /* 初始化原子变量 */
    atomic_set(&gpioled.lock, 1);   /* 原子变量初始值为1 */

    /* 设置LED所使用的GPIO */
    /* 1、获取设备节点:gpioled */
    gpioled.nd = of_find_node_by_path("/gpioled");
    if (gpioled.nd == NULL) {
        printk("gpioled node not find!\r\n");
        return -EINVAL;
    } else {
        printk("gpioled node find!\r\n");
    }

    /* 2、获取设备树中的gpio属性,得到LED所使用的LED编号 */
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
    if (gpioled.led_gpio < 0) {
        printk("can't get led-gpio");
        return -EINVAL;
    }
    printk("led-gpio num = %d\r\n", gpioled.led_gpio);

    /* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */
    ret = gpio_direction_output(gpioled.led_gpio, 1);
    if (ret < 0) {
        printk("can't set gpio!\r\n");
    }

    /* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (gpioled.major) {
        gpioled.devid = MKDEV(gpioled.major, 0);
        register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
    } else {
        alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
        gpioled.major = MAJOR(gpioled.devid);
        gpioled.minor = MINOR(gpioled.devid);
    }
    printk("gpioled major=%d,minor=%d\r\n", gpioled.major, gpioled.minor);

    /* 2、初始化cdev */
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);

    /* 3、添加一个cdev */
    cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);

    /* 4、创建类 */
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if (IS_ERR(gpioled.class)) {
        return PTR_ERR(gpioled.class);
    }

    /* 5、创建设备 */
    gpioled.device = device_create(gpioled.class, NULL,
                                   gpioled.devid, NULL, GPIOLED_NAME);
    if (IS_ERR(gpioled.device)) {
        return PTR_ERR(gpioled.device);
    }

    return 0;
}

/*
 * @description : 驱动出口函数
 * @return       : 无
 */
static void __exit led_exit(void)
{
    /* 注销字符设备驱动 */
    cdev_del(&gpioled.cdev);
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);

    device_destroy(gpioled.class, gpioled.devid);
    class_destroy(gpioled.class);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

6.2 代码逐行解析

行号 代码 说明
atomic_t lock 设备结构体中的原子变量 用于实现设备互斥访问
atomic_set(&gpioled.lock, 1) 初始化原子变量为1 表示设备可用
atomic_dec_and_test(&gpioled.lock) 原子减1并测试是否为0 如果为0表示获取成功
atomic_inc(&gpioled.lock) 原子加1 释放设备

6.3 自旋锁实验 - spinlock.c

/* 使用自旋锁保护的设备结构体 */
struct gpioled_dev {
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
    struct device_node *nd;
    int led_gpio;
    spinlock_t lock;            /* 自旋锁 */
    int dev_stats;              /* 设备使用统计 */
};

static int led_open(struct inode *inode, struct file *filp)
{
    unsigned long flags;
    struct gpioled_dev *dev = filp->private_data;

    spin_lock_irqsave(&dev->lock, flags);   /* 获取自旋锁 */

    if (dev->dev_stats != 0) {
        spin_unlock_irqrestore(&dev->lock, flags);
        return -EBUSY;
    }
    dev->dev_stats++;                       /* 标记设备被使用 */

    spin_unlock_irqrestore(&dev->lock, flags);
    return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
    unsigned long flags;
    struct gpioled_dev *dev = filp->private_data;

    spin_lock_irqsave(&dev->lock, flags);   /* 获取自旋锁 */

    dev->dev_stats--;                       /* 释放设备 */

    spin_unlock_irqrestore(&dev->lock, flags);
    return 0;
}

6.4 信号量实验 - semaphore.c

struct gpioled_dev {
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
    struct device_node *nd;
    int led_gpio;
    struct semaphore sem;       /* 信号量 */
};

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

    down(&dev->sem);           /* 获取信号量,可能睡眠 */

    return 0;
}

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

    up(&dev->sem);             /* 释放信号量 */

    return 0;
}

6.5 互斥体实验 - mutex.c

struct gpioled_dev {
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
    struct device_node *nd;
    int led_gpio;
    struct mutex lock;         /* 互斥体 */
};

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

    mutex_lock(&dev->lock);   /* 获取互斥锁 */

    return 0;
}

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

    mutex_unlock(&dev->lock); /* 释放互斥锁 */

    return 0;
}

七、实验验证

7.1 编译方法

# 1. 编译驱动模块
# 修改Makefile中obj-m变量
obj-m := atomic.o
# 或
obj-m := spinlock.o

# 编译
make -j32

# 2. 编译测试APP
arm-linux-gnueabihf-gcc atomicApp.c -o atomicApp

7.2 测试步骤

# 1. 加载驱动模块
depmod                  # 首次加载需要
modprobe atomic.ko      # 加载驱动

# 2. 运行测试程序(后台运行)
./atomicApp /dev/gpioled 1 &   # 打开LED

# 3. 尝试第二个程序访问
./atomicApp /dev/gpioled 1     # 应该失败,返回EBUSY

# 4. 卸载驱动
rmmod atomic.ko

7.3 结果分析

测试场景 预期结果 实际结果
首次打开设备 成功 ✅ 成功
25秒内再次打开 失败(EBUSY) ✅ 失败
25秒后再次打开 成功 ✅ 成功
关闭设备后再打开 成功 ✅ 成功
gantt
    title 设备访问时序测试
    dateFormat X
    axisFormat %s

    section 进程A
    打开设备     :a1, 0, 5
    占用设备     :a2, 5, 25
    关闭设备     :a3, 25, 30

    section 进程B
    尝试打开     :b1, 10, 11
    失败返回     :b2, 11, 11
    等待         :b3, 11, 30
    打开成功     :b4, 30, 35

八、跨平台对比

8.1 IMX6ULL vs STM32 vs RK3568并发机制差异

特性 IMX6ULL STM32 RK3568
CPU架构 Cortex-A7 单核 Cortex-M4/M7 Cortex-A55 四核
并发模型 单核+中断 单核+中断 多核SMP+中断
自旋锁 ✅ 支持 ❌ 不需要 ✅ 核间同步
原子操作 ✅ 支持 ⚠️ 部分支持 ✅ 支持
互斥体 ✅ 支持 ❌ 不支持 ✅ 支持
信号量 ✅ 支持 ✅ 支持 ✅ 支持
中断优先级 32级 16级 32级
典型锁选择 spinlock_irqsave 临界区 mutex/spinlock

8.2 多核 vs 单核的锁选择

flowchart TD
    A{是否为SMP多核系统?} -->|是| B{临界区是否可能被中断访问?}
    A -->|否| C{临界区是否可能被中断访问?}

    B -->|是| D[spin_lock_irqsave]
    B -->|否| E[spin_lock]

    C -->|是| F[local_irq_disable + spin_lock]
    C -->|否| G{临界区长度}

    G -->|短| H[自旋锁/原子操作]
    G -->|长| I[信号量/互斥体]

    style D fill:#dbeafe,stroke:#2563eb
    style E fill:#dbeafe,stroke:#2563eb
    style F fill:#fef3c7,stroke:#f59e0b
    style H fill:#dcfce7,stroke:#16a34a
    style I fill:#dcfce7,stroke:#16a34a

九、面试精选

题目1:自旋锁和信号量有什么区别?

考察点:锁机制理解

参考答案

特性 自旋锁 信号量
等待方式 忙等待(占用CPU) 睡眠(释放CPU)
可睡眠 ❌ 不可以 ✅ 可以
中断上下文 ✅ 可以使用 ❌ 不可以
临界区长度 短(μs级) 长(ms级)
CPU占用 高(忙等) 低(上下文切换)
适用场景 中断处理、短临界区 长时间操作、I/O

题目2:为什么要在中断处理函数中使用spin_lock_irqsave?

考察点:中断安全

参考答案

  1. 中断可能打断持有锁的进程
  2. 如果中断也尝试获取同一个锁,会导致死锁
  3. spin_lock_irqsave会禁止本地中断,避免中断与进程竞争同一把锁
  4. 使用irqsave版本是因为它可以保存并恢复中断状态,比spin_lock_irq更安全

题目3:原子操作适用于什么场景?

考察点:原子操作理解

参考答案

  • 简单变量操作:整数的加减、位操作
  • 状态标志:设备占用标志、错误标志
  • 引用计数:模块引用计数、文件引用计数
  • 不适合复杂操作:涉及多个变量的操作不能用原子操作

题目4:如何避免死锁?

考察点:死锁预防

参考答案

  1. 锁的顺序:多把锁按固定顺序获取
  2. 超时机制:使用trylock避免永久等待
  3. 锁粒度:锁的范围尽可能小
  4. 避免嵌套:尽量减少锁的嵌套使用
  5. 中断安全:在可能被中断访问的临界区使用spin_lock_irqsave

题目5:写一个简单的设备互斥访问示例

考察点:综合应用

参考答案

struct my_device {
    struct mutex lock;
    int in_use;
};

static int device_open(struct inode *inode, struct file *filp)
{
    struct my_device *dev = &my_dev;

    mutex_lock(&dev->lock);
    if (dev->in_use) {
        mutex_unlock(&dev->lock);
        return -EBUSY;
    }
    dev->in_use = 1;
    mutex_unlock(&dev->lock);

    return 0;
}

static int device_release(struct inode *inode, struct file *filp)
{
    struct my_device *dev = &my_dev;

    mutex_lock(&dev->lock);
    dev->in_use = 0;
    mutex_unlock(&dev->lock);

    return 0;
}

代码来源: Linux驱动例程 07_atomic, 08_spinlock, 09_semaphore, 10_mutex 最后更新: 2026-09-17