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

发布于:2025-08-09 ⋅ 阅读:(16) ⋅ 点赞:(0)

目录

并发与竞争

原子操作

概念

特性

API 函数

定义原子变量

整形操作 API 函数

位操作 API 函数


并发与竞争

并发(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的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 文件中,定义如下:

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");

网站公告

今日签到

点亮在社区的每一天
去签到