title: 并发同步与原子操作 tags: [Linux驱动, 并发, 锁, 原子操作, 嵌入式, 自旋锁, 信号量, 互斥体] created: 2026-09-16 updated: 2026-09-17 pdf_ref:
💡 关联知识: [[FreeRTOS学习笔记/07-中断管理与临界段]] | [[03-Linux驱动开发核心/05-中断下半部机制]] | [[03-Linux驱动开发核心/06-定时器与中断]]
竞态条件(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的结果)
| 场景 | 说明 | 危害程度 |
|---|---|---|
| 中断与进程 | 进程访问共享数据时被中断,中断处理函数也访问同一数据 | ⭐⭐⭐⭐ |
| 多进程/多线程 | 多个进程或线程同时读写共享内存 | ⭐⭐⭐ |
| 多CPU(SMP) | 多核处理器上多个CPU核心同时执行访问同一数据 | ⭐⭐⭐⭐⭐ |
| 抢占调度 | 进程在内核态执行时被高优先级任务抢占 | ⭐⭐⭐ |
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
Linux是多任务操作系统,以下因素导致并发不可避免:
⚠️ 注意:编写驱动程序时必须考虑并发保护,否则会导致难以定位的bug。
原子操作(Atomic Operation) 是指在执行过程中不会被中断的操作,要么完全执行,要么完全不执行。适用于简单的变量操作。
/* 原子变量定义 - include/linux/types.h */
typedef struct {
int counter;
} atomic_t;
| 函数 | 说明 |
|---|---|
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 |
#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;
}
| 函数 | 说明 |
|---|---|
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位并返回原值 |
自旋锁(Spinlock) 是一种忙等待的锁机制。当一个线程尝试获取锁时,如果锁已被持有,该线程会在原地"自旋"等待,直到锁被释放。
stateDiagram-v2
[*] --> 空闲: 初始状态
空闲 --> 已锁定: spin_lock()
已锁定 --> 空闲: spin_unlock()
已锁定 --> 已锁定: 其他CPU尝试获取锁(自旋等待)
note right of 已锁定
持有锁的CPU执行临界区代码
其他CPU在原地循环等待
end note
/* 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;
| 函数 | 说明 | 使用场景 |
|---|---|---|
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) |
检查锁状态 | 调试 |
| 函数 | 说明 |
|---|---|
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) |
释放锁并开启下半部 |
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
spin_lock_irqsave信号量(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: 获取成功
#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);
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;
}
| 特性 | 自旋锁 | 信号量 |
|---|---|---|
| 等待方式 | 忙等待(自旋) | 睡眠等待 |
| 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]
互斥体(Mutex) 是Linux内核中最常用的互斥锁机制。它是一种特殊的信号量,只能由一个线程持有,提供了更严格的互斥保证。
#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);
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;
}
| 特性 | 互斥体 | 自旋锁 |
|---|---|---|
| 睡眠 | ✅ 可以睡眠 | ❌ 不能睡眠 |
| 持有时长 | 可以较长 | 必须很短 |
| 中断上下文 | ❌ 不能使用 | ✅ 可以使用 |
| 优先级继承 | ✅ 支持 | ❌ 不支持 |
| 递归锁 | ❌ 不支持 | ❌ 不支持 |
| 调试支持 | ✅ 丰富 | ⚠️ 有限 |
#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");
| 行号 | 代码 | 说明 |
|---|---|---|
atomic_t lock |
设备结构体中的原子变量 | 用于实现设备互斥访问 |
atomic_set(&gpioled.lock, 1) |
初始化原子变量为1 | 表示设备可用 |
atomic_dec_and_test(&gpioled.lock) |
原子减1并测试是否为0 | 如果为0表示获取成功 |
atomic_inc(&gpioled.lock) |
原子加1 | 释放设备 |
/* 使用自旋锁保护的设备结构体 */
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;
}
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;
}
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;
}
# 1. 编译驱动模块
# 修改Makefile中obj-m变量
obj-m := atomic.o
# 或
obj-m := spinlock.o
# 编译
make -j32
# 2. 编译测试APP
arm-linux-gnueabihf-gcc atomicApp.c -o atomicApp
# 1. 加载驱动模块
depmod # 首次加载需要
modprobe atomic.ko # 加载驱动
# 2. 运行测试程序(后台运行)
./atomicApp /dev/gpioled 1 & # 打开LED
# 3. 尝试第二个程序访问
./atomicApp /dev/gpioled 1 # 应该失败,返回EBUSY
# 4. 卸载驱动
rmmod atomic.ko
| 测试场景 | 预期结果 | 实际结果 |
|---|---|---|
| 首次打开设备 | 成功 | ✅ 成功 |
| 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
| 特性 | IMX6ULL | STM32 | RK3568 |
|---|---|---|---|
| CPU架构 | Cortex-A7 单核 | Cortex-M4/M7 | Cortex-A55 四核 |
| 并发模型 | 单核+中断 | 单核+中断 | 多核SMP+中断 |
| 自旋锁 | ✅ 支持 | ❌ 不需要 | ✅ 核间同步 |
| 原子操作 | ✅ 支持 | ⚠️ 部分支持 | ✅ 支持 |
| 互斥体 | ✅ 支持 | ❌ 不支持 | ✅ 支持 |
| 信号量 | ✅ 支持 | ✅ 支持 | ✅ 支持 |
| 中断优先级 | 32级 | 16级 | 32级 |
| 典型锁选择 | spinlock_irqsave | 临界区 | mutex/spinlock |
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
考察点:锁机制理解
参考答案:
| 特性 | 自旋锁 | 信号量 |
|---|---|---|
| 等待方式 | 忙等待(占用CPU) | 睡眠(释放CPU) |
| 可睡眠 | ❌ 不可以 | ✅ 可以 |
| 中断上下文 | ✅ 可以使用 | ❌ 不可以 |
| 临界区长度 | 短(μs级) | 长(ms级) |
| CPU占用 | 高(忙等) | 低(上下文切换) |
| 适用场景 | 中断处理、短临界区 | 长时间操作、I/O |
考察点:中断安全
参考答案:
spin_lock_irqsave会禁止本地中断,避免中断与进程竞争同一把锁irqsave版本是因为它可以保存并恢复中断状态,比spin_lock_irq更安全考察点:原子操作理解
参考答案:
考察点:死锁预防
参考答案:
trylock避免永久等待spin_lock_irqsave考察点:综合应用
参考答案:
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