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");
相关推荐
科大饭桶14 分钟前
Linux系统编程Day9 -- gdb (linux)和lldb(macOS)调试工具
linux·服务器·c语言·c++
绵绵细雨中的乡音1 小时前
Linux多线程——生产者消费者模型
linux
君科程序定做3 小时前
Linux 内核发包流程与路由控制实战
linux·运维·服务器
sukalot3 小时前
window显示驱动开发—验证覆盖支持
驱动开发
Lovyk3 小时前
NFS 服务器
linux·服务器
Hello_Embed4 小时前
STM32HAL 快速入门(二):用 CubeMX 配置点灯程序 —— 从工程生成到 LED 闪烁
笔记·stm32·单片机·学习·嵌入式软件
wdfk_prog11 小时前
[Linux]学习笔记系列 -- [arm][debug]
linux·运维·arm开发·笔记·学习
Arthurmoo12 小时前
Linux系统之Docker命令与镜像、容器管理
linux·docker·eureka
Hadesls13 小时前
Almalinux 10安装L20显卡驱动
linux·运维