目录
并发与竞争
并发(Concurrency)指多个执行单元(线程/进程/中断)同时访问共享资源的现象,
竞争(Race Condition)是并发导致的数据不一致问题。
Linux 系统并发产生的主要原因有以下几个:
- 多线程并发访问, Linux 是多任务(线程)的系统,所以多线程访问是最基本的原因。
- 抢占式并发访问,从 2.6 版本内核开始, Linux 内核支持抢占,也就是说调度程序可以在任意时刻抢占正在运行的线程,从而运行其他的线程。
- 中断程序并发访问。
- SMP(多核)核间并发访问,现在 ARM 架构的多核 SOC 很常见,多核 CPU 存在核间并发访问。
如果多个线程同时操作临界区,就表示存在竞争,我们在编写驱动的时候一定要注意避免并发和防止竞争访问。
Linux 内核提供的几种并发和竞争的处理方法如下:
本讲内容我们主要学习一下原子操作。
原子操作
概念
原子操作就是指不能再进一步分割的操作,一般原子操作用于变量或者位操作。
为什么需要原子操作呢?举个例子:
a = 3
这个C语言代码编译为成汇编指令,可能如下:
ldr r0, =0X30000000 /* 变量 a 地址 */
ldr r1, = 3 /* 要写入的值 */
str r1, [r0] /* 将 3 写入到 a 变量中 */
假设现在线程 A要向 a 变量写入 10 这个值,而线程 B 也要向 a 变量写入 20 这个值。
实际上的执行流程可能如图:
结果就是,线程 A 最终将变量 a 设置为了 20。这就是设置变量值时可能遇到的并发与竞争的例子。
解决方法就是让三行汇编指令作为一个整体运行,也就是作为一个原子存在。
特性
原子操作的特性如下:
特性 |
说明 |
---|---|
不可分割性 |
操作要么完整执行,要么完全不执行,不会被线程/中断打断 |
无锁机制 |
无需使用锁(如自旋锁、互斥锁),直接通过CPU指令实现原子性 |
指令级原子性 |
由CPU提供的原子指令(如x86的 |
内存屏障 |
隐含编译器和CPU内存屏障,确保操作顺序符合预期 |
SMP安全 |
多核系统中,操作对全部CPU核心可见且有序 |
中断安全 |
可在中断上下文安全使用(如中断处理函数) |
原子操作的优点如下:
优势 |
详细说明 |
---|---|
零锁争用 |
避免锁带来的性能损耗(如上下文切换、缓存同步) |
极低延迟 |
通常1-3个CPU周期完成操作,远快于锁机制(微秒级) |
无死锁风险 |
不涉及锁的获取/释放,彻底避免死锁问题 |
适用场景广 |
可安全用于中断、进程、多核并发等任何上下文 |
代码简洁 |
单条语句完成操作,无需复杂的锁管理逻辑 |
硬件加速 |
现代CPU直接支持原子指令(如CAS、Fetch-And-Add),效率接近普通变量操作 |
API 函数
Linux 内核提供了一系列原子操作 API 函数:
- 对整形变量进行操作的API 函数。
- 对位进行操作的API 函数
定义原子变量
Linux 内核定义了叫做 atomic_t 的结构体来完成整形数据的原子操作,在使用中用原子变量来代替整形变量。
此结构体定义在 include/linux/types.h 文件中,定义如下:
typedef struct {
int counter;
} atomic_t;
使用示例:
atomic_t a; //定义 a
atomic_t b = ATOMIC_INIT(0); //定义原子变量 b 并赋初值为 0
如果使用 64 位的 SOC 的话,就要用到 64 位的原子变量:
typedef struct {
long long counter;
} atomic64_t;
整形操作 API 函数
Linux 内核提供了对原子变量进行操作,比如读、写、增加、减少等等函数,如表:
示例代码:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/atomic.h>
static atomic_t counter = ATOMIC_INIT(100); // 初始化原子变量为100
static int __init atomic_demo_init(void)
{
printk("=== Atomic Operations Demo ===\n");
// 1. 基础读写操作
printk("Initial value: %d\n", atomic_read(&counter));
atomic_set(&counter, 50);
printk("After set(50): %d\n", atomic_read(&counter));
// 2. 算术运算
atomic_add(10, &counter); // 50 + 10 = 60
atomic_sub(5, &counter); // 60 - 5 = 55
atomic_inc(&counter); // 55 + 1 = 56
atomic_dec(&counter); // 56 - 1 = 55
printk("After math ops: %d\n", atomic_read(&counter));
// 3. 带返回值的操作
printk("inc_return: %d\n", atomic_inc_return(&counter)); // 55→56
printk("dec_return: %d\n", atomic_dec_return(&counter)); // 56→55
// 4. 条件测试操作
printk("sub_and_test(55): %d\n", atomic_sub_and_test(55, &counter)); // 55-55=0 → true(1)
atomic_set(&counter, 10);
printk("dec_and_test: %d\n", atomic_dec_and_test(&counter)); // 10→9 → false(0)
printk("inc_and_test(0): %d\n", atomic_inc_and_test(&counter)); // 9→10 → false(0)
printk("add_negative(-20): %d\n", atomic_add_negative(-20, &counter)); // 10+(-20)=-10 → true(1)
return 0;
}
static void __exit atomic_demo_exit(void)
{
printk("Final counter value: %d\n", atomic_read(&counter));
}
module_init(atomic_demo_init);
module_exit(atomic_demo_exit);
MODULE_LICENSE("GPL");
位操作 API 函数
linux原子位操作是直接对内存进行操作, API 函数如表:
示例代码:
#include <linux/module.h>
#include <linux/bitops.h>
static unsigned long bitflags = 0; // 32位标志位变量
static int __init bitops_demo_init(void)
{
printk(KERN_INFO "Bit Operations Demo\n");
// 1. 基础位操作
set_bit(3, &bitflags); // 0000 1000
clear_bit(3, &bitflags); // 0000 0000
change_bit(5, &bitflags); // 0010 0000 (0→1)
change_bit(5, &bitflags); // 0000 0000 (1→0)
// 2. 测试位状态
printk("Bit 2: %d\n", test_bit(2, &bitflags)); // 输出0
// 3. 原子测试并修改
if (!test_and_set_bit(1, &bitflags)) { // 0000 0010
printk("Bit 1 was 0, now set to 1\n");
}
if (test_and_clear_bit(1, &bitflags)) { // 0000 0000
printk("Bit 1 was 1, now cleared\n");
}
if (!test_and_change_bit(0, &bitflags)) { // 0000 0001
printk("Bit 0 changed from 0 to 1\n");
}
return 0;
}
static void __exit bitops_demo_exit(void)
{
printk(KERN_INFO "Final bitflags: 0x%lx\n", bitflags);
}
module_init(bitops_demo_init);
module_exit(bitops_demo_exit);
MODULE_LICENSE("GPL");