驱动开发-并发与竞争02原子操作
原子操作
原子操作就是指不能再进一步分割的操作,一般原子操作用于变量或者位操作。
原子操作 API 函数
Linux内核定义了叫做 atomic_t
的结构体来完成用于 32 位整数的原子操作。在使用中用原子变量来代替整形变量,此结构体定义在 include/linux/types.h
文件中,定义如下:
1 | typedef struct { |
用于 64 位整数的原子操作 (在支持 64 位原子操作的体系结构上)。
1 | typedef struct { |
初始化
1 | //-------编译时初始化:----- |
读/写设置
1 | //---------写------------- |
基本算数运算
加法 (
v += i
):1
2
3
4int atomic_add_return(int i, atomic_t *v); // v 加 i, 返回新值
int atomic64_add_return(long long i, atomic64_t *v); // 64位版本
void atomic_add(int i, atomic_t *v); // v 加 i, 不返回值 (较老API,性能可能不如带_return的)
void atomic64_add(long long i, atomic64_t *v);减法 (
v -= i
):1
2
3
4int atomic_sub_return(int i, atomic_t *v); // v 减 i, 返回新值
int atomic64_sub_return(long long i, atomic64_t *v); // 64位版本
void atomic_sub(int i, atomic_t *v); // v 减 i, 不返回值 (较老API)
void atomic64_sub(long long i, atomic64_t *v);自增 (
v++
):1
2
3
4int atomic_inc_return(atomic_t *v); // v 加 1, 返回新值
long long atomic64_inc_return(atomic64_t *v); // 64位版本
void atomic_inc(atomic_t *v); // v 加 1, 不返回值
void atomic64_inc(atomic64_t *v);自减 (
v--
):1
2
3
4int atomic_dec_return(atomic_t *v); // v 减 1, 返回新值
long long atomic64_dec_return(atomic64_t *v); // 64位版本
void atomic_dec(atomic_t *v); // v 减 1, 不返回值
void atomic64_dec(atomic64_t *v);
条件操作
减后测试是否为 0 (常用于引用计数释放):
1
2int atomic_dec_and_test(atomic_t *v); // v 减 1, 如果结果为0返回1 (true), 否则返回0 (false)
int atomic64_dec_and_test(atomic64_t *v); // 64位版本加后测试是否为 0:
1
2int atomic_inc_and_test(atomic_t *v); // v 加 1, 如果结果为0返回1 (true), 否则返回0 (false) (较少用)
int atomic64_inc_and_test(atomic64_t *v);减后测试是否为负:
1
2int atomic_sub_and_test(int i, atomic_t *v); // v 减 i, 如果结果为负返回1 (true), 否则返回0 (false)
int atomic64_sub_and_test(long long i, atomic64_t *v);
位操作
内核还提供了一组直接作用于内存地址上特定位的原子操作函数。这些函数不是作用于 atomic_t
,而是直接作用于一个指向内存的指针和一个位号。 (作用于 unsigned long *
或 void *
+ 位偏移)
设置位 (
set_bit
):1
void set_bit(unsigned int nr, volatile unsigned long *addr); // 将 addr 指向的内存区域的第 nr 位设置为 1
清除位 (
clear_bit
):1
void clear_bit(unsigned int nr, volatile unsigned long *addr); // 将 addr 指向的内存区域的第 nr 位设置为 0
改变位 (
change_bit
):1
void change_bit(unsigned int nr, volatile unsigned long *addr); // 翻转 addr 指向的内存区域的第 nr 位 (1变0, 0变1)
测试位 (
test_bit
):1
int test_bit(unsigned int nr, const volatile unsigned long *addr); // 测试 addr 指向的内存区域的第 nr 位是否为 1 (返回非0表示1, 0表示0)
测试并设置位 (
test_and_set_bit
):1
int test_and_set_bit(unsigned int nr, volatile unsigned long *addr); // 将第 nr 位设置为 1, 返回该位原来的值
测试并清除位 (
test_and_clear_bit
):1
int test_and_clear_bit(unsigned int nr, volatile unsigned long *addr); // 将第 nr 位设置为 0, 返回该位原来的值
测试并改变位 (
test_and_change_bit
):1
int test_and_change_bit(unsigned int nr, volatile unsigned long *addr); // 翻转第 nr 位, 返回该位原来的值
内存序 (Memory Ordering)
现代体系结构(如 ARM, POWER)通常具有宽松的内存模型。这意味着编译器和 CPU 为了提高性能,可能会对内存访问指令进行重排序。单纯的原子操作只保证了操作本身的原子性,不保证操作前后其他内存访问的相对顺序。
屏障 (Barriers): 为了保证内存访问的顺序符合程序逻辑(例如,确保修改一个共享数据结构的指针 之后 再更新表示该结构有效的标志位),需要使用内存屏障。
隐式屏障: 大多数上面列出的
_return
后缀的原子操作函数(如atomic_add_return
,atomic_inc_return
,atomic_cmpxchg
)以及test_and_xxx_bit
函数,在成功执行后都包含一个完整的内存屏障 (smp_mb()
)。这意味着:- 在屏障之前的所有内存访问(读/写)操作,都会在屏障之后的所有内存访问操作之前完成。
- 其他 CPU 看到的屏障之前的写操作,一定发生在看到屏障之后的写操作(或由屏障保护的状态变化)之前。
无屏障操作: 一些不返回值的操作(如
atomic_add
,atomic_inc
)和set_bit
,clear_bit
等位操作,默认不包含内存屏障。如果这些操作需要在多核间提供顺序保证,必须显式添加内存屏障 (smp_mb()
,smp_wmb()
,smp_rmb()
)。例如,在设置一个“数据就绪”标志位之前,需要确保数据本身已经写入完成:1
2
3data = ...; // 准备数据
smp_wmb(); // 写内存屏障:确保上面的写操作在下面的写操作之前完成
set_bit(DATA_READY, &flags); // 设置标志位显式屏障变体: 较新的内核版本提供了带有显式内存序参数的原子操作 API(如
atomic_add_return_relaxed
,atomic_cmpxchg_acquire
),允许开发者在性能关键路径上精细控制屏障强度(relaxed
,acquire
,release
,full
)。这属于更高级的用法。
实验:
要实现设备互斥访问,需明确原子变量的初始化和操作逻辑:
初始化原子变量为 1(表示设备初始可用):
1
2atomic_t lock;
atomic_set(&leddev->lock, 1); // 初始值设为1,代表“可用”open
函数逻辑(抢占使用权):1
2
3
4
5
6
7
8
9
10static int ledopen(struct inode *node, struct file *file)
{
// 尝试获取锁:减1后若为0,说明成功抢占;若<0,说明已被占用
if (atomic_dec_return(&leddev->lock) < 0) {
atomic_inc(&leddev->lock); // 恢复锁状态(加1)
return -EBUSY; // 设备忙
}
file->private_data = leddev;
return 0;
}- 若
lock
初始为 1,第一个进程调用后lock=0
(成功占用)。 - 第二个进程调用时
lock
先减为 - 1(<0),触发-EBUSY
,并恢复lock=0
(保持被占用状态)。
- 若
release
函数逻辑(释放使用权):1
2
3
4
5
6static int ledrelease(struct inode *node, struct file *file)
{
struct leddev_struct *leddev = (struct leddev_struct *)file->private_data;
atomic_inc(&leddev->lock); // 释放锁(加1,恢复为1,允许其他进程使用)
return 0;
}
代码:
驱动代码:
1 |
|
应用代码:
1 |
|
实验效果:
我们首先在开发板上启动驱动模块,随后使用命令打开led灯

我们再通过mobaxterm使用命令关闭led灯:
可以发现显示open failed: Device or resource busy
,通过结果可以知道该设备已经被占用了。