3.2.1.2 汇编版 原子操作 CAS

news/2024/12/18 18:20:56/

基本原理说明 

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


1. x86 架构的原子操作

(1)原子递增

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

实现原子递增
global atomic_increment
section .text
atomic_increment:mov eax, 1          ; 设置递增值为 1lock xadd [rdi], eax ; 对地址 rdi 所指向的变量执行原子加操作add rax, 1          ; xadd 返回的是原值,rax = 原值 + 1ret
解释:
  • 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        ; 将期望值放入 raxlock 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] 的值到 r0add r0, r0, r1      ; r0 = r0 + 1strex r2, r0, [r0]  ; 尝试将 r0 写回 [r0],如果成功 r2 = 0cmp 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 = 0cmp 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 1000volatile 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.ppmy.cn/news/1556174.html

相关文章

云计算HCIP-OpenStack03

书接上回&#xff1a; 云计算HCIP-OpenStack02-CSDN博客 10.KeyStone keystone-Openstack&#xff0c;IAM服务&#xff08;统一身份认证&#xff09;-云服务 建议先去了解Hadoop&#xff08;大数据生态系统&#xff09;中的kerberos&#xff08;LDAPkerberos的鉴权机制&#xf…

YOLOv11改进,YOLOv11添加DLKA-Attention可变形大核注意力,WACV2024 ,二次创新C3k2结构

摘要 作者引入了一种称为可变形大核注意力 (D-LKA Attention) 的新方法来增强医学图像分割。这种方法使用大型卷积内核有效地捕获体积上下文,避免了过多的计算需求。D-LKA Attention 还受益于可变形卷积,以适应不同的数据模式。 理论介绍 大核卷积(Large Kernel Convolu…

微积分复习笔记 Calculus Volume 2 - 4.2 Direction Fields and Numerical Methods

4.2 Direction Fields and Numerical Methods - Calculus Volume 2 | OpenStax

【机器学习】在向量的流光中,揽数理星河为衣,以线性代数为钥,轻启机器学习黎明的瑰丽诗章

文章目录 线性代数入门&#xff1a;机器学习零基础小白指南前言一、向量&#xff1a;数据的基本单元1.1 什么是向量&#xff1f;1.1.1 举个例子&#xff1a; 1.2 向量的表示与维度1.2.1 向量的维度1.2.2 向量的表示方法 1.3 向量的基本运算1.3.1 向量加法1.3.2 向量的数乘1.3.3…

Apache Commons Utils 类库使用

Apache Commons Utils 是一组开源的 Java 工具类库&#xff0c;提供了许多在开发中常用且实用的功能&#xff0c;涵盖了字符串处理、集合操作、日期时间处理、文件操作等多个方面。下面是对 Apache Commons Utils 中一些主要工具类的详细介绍和使用示例。 1. Commons Lang (or…

获取微信用户openid

附上开发文档:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html 开发之前,准备事项 一个已认证过的服务号|基本信息配置js域名和网站授权域名配置最后确认当前账号网页授权功能是否开通,没有开通的无法获取到用户授权开发人…

docker springboot 运维部署详细实例

环境安装 [rootiZbp1dcnzq7pzpg9607m6pZ ~]# docker -v Docker version 26.1.4, build 5650f9b镜像构建 Dockerfile 文件内容 FROM openjdk:8 # Author Info 创建人信息 MAINTAINER ratelcloudfoxmail.com ENV PORT20001 EXPOSE 20001 RUN mkdir /usr/local/ratel-boot-serv…

PostgreSQL数据库序列信息查询

PostgreSQL序列信息查询 说明&#xff1a; 在PostgreSQL数据库中序列和表都是序列的对象。 数据库中不应该存在孤儿序列&#xff0c;序列应该和表对应的字段绑定起来。绑定后删除表或表对应的字段后&#xff0c;序列会自动被删除。 创建测试表和序列 create table test_t(…