imx6ull-驱动开发篇14——原子操作

目录

并发与竞争

原子操作

概念

特性

[API 函数](#API 函数)

定义原子变量

[整形操作 API 函数](#整形操作 API 函数)

[位操作 API 函数](#位操作 API 函数)


并发与竞争

**并发(Concurrency)**指多个执行单元(线程/进程/中断)同时访问共享资源的现象,

**竞争(Race Condition)**是并发导致的数据不一致问题。

Linux 系统并发产生的主要原因有以下几个:

  • 多线程并发访问, Linux 是多任务(线程)的系统,所以多线程访问是最基本的原因。
  • 抢占式并发访问,从 2.6 版本内核开始, Linux 内核支持抢占,也就是说调度程序可以在任意时刻抢占正在运行的线程,从而运行其他的线程。
  • 中断程序并发访问。
  • SMP(多核)核间并发访问,现在 ARM 架构的多核 SOC 很常见,多核 CPU 存在核间并发访问。

如果多个线程同时操作临界区,就表示存在竞争,我们在编写驱动的时候一定要注意避免并发和防止竞争访问。

Linux 内核提供的几种并发和竞争的处理方法如下:

本讲内容我们主要学习一下原子操作。

原子操作

概念

原子操作就是指不能再进一步分割的操作,一般原子操作用于变量或者位操作。

为什么需要原子操作呢?举个例子:

cpp 复制代码
a = 3

这个C语言代码编译为成汇编指令,可能如下:

cpp 复制代码
ldr r0, =0X30000000 /* 变量 a 地址 */
ldr r1, = 3 /* 要写入的值 */
str r1, [r0] /* 将 3 写入到 a 变量中 */

假设现在线程 A要向 a 变量写入 10 这个值,而线程 B 也要向 a 变量写入 20 这个值。

实际上的执行流程可能如图:

结果就是,线程 A 最终将变量 a 设置为了 20。这就是设置变量值时可能遇到的并发与竞争的例子。

解决方法就是让三行汇编指令作为一个整体运行,也就是作为一个原子存在。

特性

原子操作的特性如下:

特性​​ ​​说明​​
​​不可分割性​​ 操作要么完整执行,要么完全不执行,不会被线程/中断打断
​​无锁机制​​ 无需使用锁(如自旋锁、互斥锁),直接通过CPU指令实现原子性
​​指令级原子性​​ 由CPU提供的原子指令(如x86的LOCK前缀指令、ARM的LDREX/STREX)保证
​​内存屏障​​ 隐含编译器和CPU内存屏障,确保操作顺序符合预期
​​SMP安全​​ 多核系统中,操作对全部CPU核心可见且有序
​​中断安全​​ 可在中断上下文安全使用(如中断处理函数)

原子操作的优点如下:

优势​​ ​​详细说明​​
​​零锁争用​​ 避免锁带来的性能损耗(如上下文切换、缓存同步)
​​极低延迟​​ 通常1-3个CPU周期完成操作,远快于锁机制(微秒级)
​​无死锁风险​​ 不涉及锁的获取/释放,彻底避免死锁问题
​​适用场景广​​ 可安全用于中断、进程、多核并发等任何上下文
​​代码简洁​​ 单条语句完成操作,无需复杂的锁管理逻辑
​​硬件加速​​ 现代CPU直接支持原子指令(如CAS、Fetch-And-Add),效率接近普通变量操作

API 函数

Linux 内核提供了一系列原子操作 API 函数:

  • 对整形变量进行操作的API 函数。
  • 对位进行操作的API 函数

定义原子变量

Linux 内核定义了叫做 atomic_t 的结构体来完成整形数据的原子操作,在使用中用原子变量来代替整形变量。

此结构体定义在 include/linux/types.h 文件中,定义如下:

cpp 复制代码
typedef struct {
            int counter;
} atomic_t;

使用示例:

cpp 复制代码
atomic_t a; //定义 a

atomic_t b = ATOMIC_INIT(0); //定义原子变量 b 并赋初值为 0

如果使用 64 位的 SOC 的话,就要用到 64 位的原子变量:

cpp 复制代码
typedef struct {
            long long counter;
} atomic64_t;

整形操作 API 函数

Linux 内核提供了对原子变量进行操作,比如读、写、增加、减少等等函数,如表:

示例代码:

cpp 复制代码
#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 函数如表:

示例代码:

cpp 复制代码
#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");
相关推荐
HideF42 分钟前
Linux sudo
linux
FlightYe1 小时前
视界原理之3D视频(四):裸眼、眼镜和头显
android·linux·c++·3d·ffmpeg·音视频
蹊黎2 小时前
Linux 下用 C 语言实现进度条
linux·c语言·单片机
mg6683 小时前
安卓玩机工具----告别面具!SukiSU Ultra 内核级 Root 与终极隐藏实战指南
驱动开发·root系统
牛马鸡niumasi3 小时前
【Linux】C语言文件操作
linux
say_fall3 小时前
【Linux系统编程】Ext 文件系统:磁盘与存储、文件与目录、现代计算机究竟如何寻找文件
linux·运维·服务器·数据库·c++
wacsii4 小时前
【Linux进程(一)】Linux 进程初探:操作系统如何管理进程?fork() 为什么返回两次?
linux·运维·服务器
AI+程序员在路上4 小时前
RV1126B 上支持的模型及全流程应用--从CNN及语音到 LLMVLM 与跨平台选型
linux·c语言·人工智能
默|笙5 小时前
【Linux网络】传输层协议TCP(1)---TCP报文介绍
linux·网络·tcp/ip
相思难忘成疾5 小时前
从零搭建 Kubernetes 1.30 集群:kubeadm 部署一主多从实战(RHEL 9.3 + Docker + flannel)
linux·运维·kubernetes