3.2.1.2 汇编版 原子操作 CAS

news2024/12/19 1:57:31

基本原理说明 

x86ARM 架构上,原子操作通常利用硬件提供的原子指令来实现,比如 LOCK 前缀(x86)或 LDREX/STREX(ARM)。以下是一些关键的原子操作(例如原子递增和比较交换)的汇编实现。


1. x86 架构的原子操作

(1)原子递增

x86 的 LOCK 前缀可以用来保证指令在多核处理器上的原子性。例如原子递增操作:

实现原子递增
global atomic_increment
section .text
atomic_increment:
    mov eax, 1          ; 设置递增值为 1
    lock xadd [rdi], eax ; 对地址 rdi 所指向的变量执行原子加操作
    add rax, 1          ; xadd 返回的是原值,rax = 原值 + 1
    ret
解释:
  • LOCK XADD [rdi], eax:对 rdi 指向的地址执行原子加操作,将原值存入 eax,并把 eax 加上后写回 rdi
  • 使用 LOCK 前缀可以保证多核环境下的原子性。

(2)比较并交换(CAS)

x86 提供了 CMPXCHG 指令,可以用来实现原子的比较并交换(CAS)。

实现原子 CAS
global atomic_compare_exchange
section .text
atomic_compare_exchange:
    mov rax, rsi        ; 将期望值放入 rax
    lock cmpxchg [rdi], rdx ; 如果 [rdi] 的值等于 rax(期望值),将 rdx 写入 [rdi]
    ret
参数说明:
  • rdi:目标地址。
  • rsi:期望值。
  • rdx:需要交换的值。
解释:
  • LOCK CMPXCHG [rdi], rdx
    • 如果 [rdi] 的值等于 RAX,则将 rdx 写入 [rdi]
    • 否则将 [rdi] 的当前值写入 RAX
  • 返回值:RAX 中存储的是 rdi 的旧值,调用者可以根据是否等于 rsi 判断交换是否成功。

2. ARM 架构的原子操作

ARM 使用 LDREX/STREX 指令对原子操作进行支持。这是一种 负载-存储排他模型,可以保证在加载到存储之间没有其他线程或进程修改目标变量。


(1)原子递增

在 ARM 架构下,使用 LDREX/STREX 实现原子递增:

实现原子递增
.global atomic_increment
atomic_increment:
    mov r1, #1          ; 将 1 存入 r1(递增值)
1:
    ldrex r0, [r0]      ; 使用 LDREX 加载 [r0] 的值到 r0
    add r0, r0, r1      ; r0 = r0 + 1
    strex r2, r0, [r0]  ; 尝试将 r0 写回 [r0],如果成功 r2 = 0
    cmp r2, #0          ; 检查写入是否成功
    bne 1b              ; 如果不成功,重试
    dmb                 ; 数据内存屏障,确保操作完成
    bx lr
解释:
  • LDREX:加载目标地址的值,同时标记为“独占”访问。
  • STREX:尝试存储值到目标地址,如果目标地址在此期间被其他核修改,存储操作会失败,返回非 0。
  • 使用循环保证操作成功。

(2)比较并交换(CAS)

实现原子 CAS
.global atomic_compare_exchange
atomic_compare_exchange:
    ldrex r0, [r1]      ; 从地址 r1 加载值到 r0(旧值)
    cmp r0, r2          ; 比较 r0 和 r2(期望值)
    bne 1f              ; 如果不相等,跳转到 1f,返回旧值
    strex r3, r3, [r1]  ; 尝试将 r3 写入 [r1],如果成功 r3 = 0
    cmp r3, #0          ; 检查是否存储成功
    bne atomic_compare_exchange ; 如果失败,重新尝试
1:
    dmb                 ; 数据内存屏障,保证一致性
    bx lr
参数说明:
  • r1:目标地址。
  • r2:期望值。
  • r3:要交换的值。
解释:
  • 如果 LDREX 加载的值与 期望值(r2)相等,则尝试存储 r3
  • 如果存储失败,则重复尝试。
  • 成功后返回原值。

3. 汇编操作的注意事项

  1. x86 的强内存模型
    • x86 硬件有一个相对强的内存序,很多情况下 LOCK 前缀可以满足原子操作需求。
  2. ARM 的弱内存模型
    • ARM 是弱内存模型,使用 LDREX/STREX 操作时,通常需要内存屏障(如 DMB)来确保指令顺序和数据同步。
  3. 重试机制
    • 对于 ARM 的 LDREX/STREX 模式,如果存储失败需要通过循环重试。
  4. 指令可用性
    • 需要确保目标平台支持这些指令。比如 CMPXCHG 在早期的 x86 处理器(如 80386)上不可用。

4. 比较总结

操作x86ARM
原子递增LOCK XADDLDREX + ADD + STREX
原子比较交换LOCK CMPXCHGLDREX + CMP + STREX
内存屏障MFENCE(可选)DMB

x86 汇编通常较为简单,因为其强内存序模型降低了编程复杂性;ARM 则需要更明确的同步和屏障操作。

代码实现

my_atomic.h

#include <stdint.h>
#include <stdio.h>

// 平台区分
#if defined(__x86_64__)
    #define ATOMIC_X86
#elif defined(__aarch64__)
    #define ATOMIC_ARM
#else
    #error "Unsupported platform"
#endif

// ===================== x86 实现 =====================
#ifdef ATOMIC_X86

// 原子递增
static inline int atomic_increment(int *addr) {
    int result;
    __asm__ __volatile__(
        "lock xaddl %0, %1"
        : "=r"(result), "+m"(*addr)
        : "0"(1)
        : "memory"
    );
    return result + 1; // 返回递增后的值
}

// 原子递减
static inline int atomic_decrement(int *addr) {
    int result;
    __asm__ __volatile__(
        "lock xaddl %0, %1"
        : "=r"(result), "+m"(*addr)
        : "0"(-1) // 减 1
        : "memory"
    );
    return result - 1; // 返回递减后的值
}

// 原子比较交换
static inline int atomic_compare_exchange(int *addr, int expected, int desired) {
    int old;
    __asm__ __volatile__(
        "lock cmpxchgl %2, %1"
        : "=a"(old), "+m"(*addr)
        : "r"(desired), "0"(expected)
        : "memory"
    );
    return old; // 返回旧值
}

// 原子读取
static inline int atomic_load(int *addr) {
    int value;
    __asm__ __volatile__(
        "movl %1, %0"
        : "=r"(value)
        : "m"(*addr)
        : "memory"
    );
    return value; // 返回读取值
}

#endif // ATOMIC_X86

// ===================== ARM 实现 =====================
#ifdef ATOMIC_ARM

// 原子递增
static inline int atomic_increment(int *addr) {
    int old, tmp;
    do {
        __asm__ __volatile__(
            "ldrex %0, [%2]      \n" // 读取旧值到 old
            "add   %1, %0, #1    \n" // tmp = old + 1
            "strex %0, %1, [%2]  \n" // 尝试写回
            : "=&r"(old), "=&r"(tmp)
            : "r"(addr)
            : "memory", "cc"
        );
    } while (old != 0); // 如果写失败,重试
    return tmp; // 返回递增后的值
}

// 原子递减
static inline int atomic_decrement(int *addr) {
    int old, tmp;
    do {
        __asm__ __volatile__(
            "ldrex %0, [%2]      \n" // 读取旧值到 old
            "sub   %1, %0, #1    \n" // tmp = old - 1
            "strex %0, %1, [%2]  \n" // 尝试写回
            : "=&r"(old), "=&r"(tmp)
            : "r"(addr)
            : "memory", "cc"
        );
    } while (old != 0); // 如果写失败,重试
    return tmp; // 返回递减后的值
}

// 原子比较交换
static inline int atomic_compare_exchange(int *addr, int expected, int desired) {
    int old, status;
    do {
        __asm__ __volatile__(
            "ldrex %0, [%2]       \n" // 加载到 old
            "cmp   %0, %3         \n" // 比较 old 和 expected
            "bne   1f             \n" // 如果不相等,跳转到 1
            "strex %1, %4, [%2]   \n" // 尝试写回 desired
            "1:"
            : "=&r"(old), "=&r"(status)
            : "r"(addr), "r"(expected), "r"(desired)
            : "memory", "cc"
        );
    } while (status != 0); // 如果写失败,重试
    return old; // 返回旧值
}

// 原子读取
static inline int atomic_load(int *addr) {
    int value;
    __asm__ __volatile__(
        "ldrex %0, [%1] \n" // 加载值到 value
        : "=&r"(value)
        : "r"(addr)
        : "memory"
    );
    return value; // 返回读取值
}

#endif // ATOMIC_ARM

my_atomic_test.c

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include "my_atomic.h"

#define NUM_THREADS 4
#define ITERATIONS 1000

volatile int shared_counter = 0; // 全局共享计数器

// 线程函数:执行递增操作
void* thread_increment(void* arg) {
    for (int i = 0; i < ITERATIONS; i++) {
        atomic_increment((int*)&shared_counter);
    }
    return NULL;
}

// 线程函数:执行递减操作
void* thread_decrement(void* arg) {
    for (int i = 0; i < ITERATIONS; i++) {
        atomic_decrement((int*)&shared_counter);
    }
    return NULL;
}

int main() {
    pthread_t threads[NUM_THREADS];

    // 创建增量线程
    for (int i = 0; i < NUM_THREADS / 2; i++) {
        if (pthread_create(&threads[i], NULL, thread_increment, NULL) != 0) {
            perror("pthread_create");
            exit(EXIT_FAILURE);
        }
    }

    // 创建减量线程
    for (int i = NUM_THREADS / 2; i < NUM_THREADS; i++) {
        if (pthread_create(&threads[i], NULL, thread_decrement, NULL) != 0) {
            perror("pthread_create");
            exit(EXIT_FAILURE);
        }
    }

    // 等待所有线程完成
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }

    // 最终结果
    int final_value = atomic_load((int*)&shared_counter);
    printf("最终计数器值: %d\n", final_value);

    return 0;
}

ubuntu x86编译运行

$ gcc -pthread -o a.out my_atomic_test.c
$ ./a.out
最终计数器值: 0


gitlab.0voice.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2261876.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GLB格式转换为STL格式

GLB与STL格式简介 GLB格式 GLB代表“GL传输格式二进制文件”&#xff08;GL Transmission Format Binary&#xff09;。GLB主要用于共享3D数据&#xff0c;包含三维模型、场景、光源、材质、节点层次和动画等详细信息&#xff0c;是一种标准化的文件格式&#xff0c;适用于多…

Qt编译MySQL数据库驱动

目录 Qt编译MySQL数据库驱动 测试程序 Qt编译MySQL数据库驱动 &#xff08;1&#xff09;先找到MySQL安装路径以及Qt安装路径 C:\Program Files\MySQL\MySQL Server 8.0 D:\qt\5.12.12 &#xff08;2&#xff09;在D:\qt\5.12.12\Src\qtbase\src\plugins\sqldrivers\mysql下…

MySQL通过binlog日志进行数据恢复

记录一次阿里云MySQL通过binlog日志进行数据回滚 问题描述由于阿里云远程mysql没有做安全策略 所以服务器被别人远程攻击把数据库给删除&#xff0c;通过查看binlog日志可以看到进行了drop操作&#xff0c;下面将演示通过binlog日志进行数据回滚操作。 1、查询是否开始binlog …

如何在 Ubuntu 22.04 上安装和使用 Rust 编程语言环境

简介 Rust 是一门由 Mozilla 开发的系统编程语言&#xff0c;专注于性能、可靠性和内存安全。它在没有垃圾收集的情况下实现了内存安全&#xff0c;这使其成为构建对性能要求苛刻的应用程序&#xff08;如操作系统、游戏引擎和嵌入式系统&#xff09;的理想选择。 接下来&…

前端项目初始化搭建(二)

一、使用 Vite 创建 Vue 3 TypeScript 项目 PS E:\web\cursor-project\web> npm create vitelatest yf-blog -- --template vue-ts> npx > create-vite yf-blog --template vue-tsScaffolding project in E:\web\cursor-project\web\yf-blog...Done. Now run:cd yf-…

生活小妙招之UE CaptureRT改

需求&#xff0c;四个不同的相机拍摄结果同屏分屏显示 一般的想法是四个Capture拍四张RT&#xff0c;然后最后在面片/UI上组合。这样的开销是创建4张RT&#xff0c;材质中采样4次RT。 以更省的角度&#xff0c;想要对以上流程做优化&#xff0c;4个相机拍摄是必须的&#xff…

【AIGC进阶-ChatGPT提示词副业解析】探索生活的小确幸:在平凡中寻找幸福

引言 在这个快节奏的现代社会中,我们常常被各种压力和焦虑所困扰,忘记了生活中那些细小而珍贵的幸福时刻。本文将探讨如何在日常生活中发现和珍惜那些"小确幸",以及如何通过尝试新事物来丰富我们的生活体验。我们还将讨论保持神秘感和期待感对于维持生活乐趣的重要性…

C#编程报错- “ComboBox”是“...ComboBox”和“...ComboBox”之间的不明确的引用

1、问题描述 在学习使用C#中的Winform平台编写一个串口助手程序时&#xff0c; 在编写一个更新ComboBox列表是遇到了问题&#xff0c;出错的代码是 2、报错信息 CS1503 参数 2: 无法从“System.Windows.Forms.ComboBox”转换为“System.Windows.Forms.ComboBox” CS1503 …

ollama+open-webui,本地部署自己的大模型

目录 一、效果预览 二、部署ollama 1.ollama说明 2.安装流程 2.1 windows系统 2.1.1下载安装包 2.1.2验证安装结果 2.1.3设置模型文件保存地址 2.1.4拉取大模型镜像 2.2linux系统 2.2.1下载并安装ollama 2.2.2设置环境变量 2.2.3拉取模型文件 三、部署open-webui…

leetcode_203. 移除链表元素

203. 移除链表元素 - 力扣&#xff08;LeetCode&#xff09; 开始写的时候没有想明白的问题 1. 开始我是想头节点 尾节点 中间节点 分开处理 如果删除的是头节点 然后又要删除头节点的后继节点 那么 这样子的话头节点分开处理就毫无意义了 接着是尾节点 开始我定义的是curr h…

【大模型微调学习5】-大模型微调技术LoRA

【大模型微调学习5】-大模型微调技术LoRA LoRa微调1.现有 PEFT 方法的局限与挑战2.LoRA: 小模型有大智慧 (2021)3.AdaLoRA: 自适应权重矩阵的高效微调 (2023)4.QLoRA: 高效微调量化大模型 (2023) LoRa微调 1.现有 PEFT 方法的局限与挑战 Adapter方法&#xff0c;通过增加模型深…

.NET 技术系列 | 通过CreatePipe函数创建管道

01阅读须知 此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等&#xff08;包括但不限于&#xff09;进行检测或维护参考&#xff0c;未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失&#xf…

DS18B20温度传感器(STM32)

一、介绍 DS18B20是一种常见的数字型温度传感器&#xff0c;具备独特的单总线接口方式。其控制命令和数据都是以数字信号的方式输入输出&#xff0c;相比较于模拟温度传感器&#xff0c;具有功能强大、硬件简单、易扩展、抗干扰性强等特点。 传感器参数 测温范围为-55℃到1…

shell编程2 永久环境变量和字符串显位

声明 学习视频来自B站UP主 泷羽sec 常见变量 echo $HOME &#xff08;家目录 root用户&#xff09; /root cd /root windows的环境变量可以去设置里去新建 为什么输入ls dir的命令的时候就会输出相应的内容呢 因为这些命令都有相应的变量 which ls 通过这个命令查看ls命令脚本…

MaskGCT——开源文本转语音模型,可模仿任何人说话声音

前期介绍过很多语音合成的模型&#xff0c;比如ChatTTS&#xff0c;微软语音合成大模型&#xff0c;字节跳动自家发布的语音合成模型Seed-TTS。其模型随着技术的不断发展&#xff0c;模型说话的声音也越来越像人类&#xff0c;虽然 seed-tts 可以进行语音合成等功能&#xff0c…

java全栈day16--Web后端实战(数据库)

一、数据库介绍 二、Mysql安装&#xff08;自行在网上找&#xff0c;教程简单&#xff09; 安装好了进行Mysql连接 连接语法&#xff1a;winr输入cmd&#xff0c;在命令行中再输入mysql -uroot -p密码 方法二&#xff1a;winr输入cmd&#xff0c;在命令行中再输入mysql -uroo…

geoserver 瓦片地图,tomcat和nginx实现负载均衡

在地理信息系统&#xff08;GIS&#xff09;领域&#xff0c;GeoServer作为一个强大的开源服务器&#xff0c;能够发布各种地图服务&#xff0c;包括瓦片地图服务。为了提高服务的可用性和扩展性&#xff0c;结合Tomcat和Nginx实现负载均衡成为了一个有效的解决方案。本文将详细…

达梦8-达梦数据的示例用户和表

1、示例库说明&#xff1a; 创建达梦数据的示例用户和表&#xff0c;导入测试数据。 在完成达梦数据库的安装之后&#xff0c;在/opt/dmdbms/samples/instance_script目录下有用于创建示例用户的SQL文件。samples目录前的路径根据实际安装情况进行修改&#xff0c;本文将达梦…

利用notepad++删除特定关键字所在的行

1、按组合键Ctrl H&#xff0c;查找模式选择 ‘正则表达式’&#xff0c;不选 ‘.匹配新行’ 2、查找目标输入 &#xff1a; ^.*关键字.*\r\n (不保留空行) ^.*关键字.*$ (保留空行)3、替换为&#xff1a;&#xff08;空&#xff09; 配置界面参考下图&#xff1a; ​​…

Qt学习笔记第61到70讲

第61讲 记事本实现当前行高亮功能 实现策略&#xff1a; 获取当前行的光标位置&#xff0c;使用的信号和获取行列值是一样的&#xff0c;即通过ExtraSelection 来配置相关属性。 关键API&#xff1a; QList<QTextEdit::ExtraSelection> extraSelections; void setExtraSe…