通过内联汇编锁定内存操作

2024-02-07

我对低级的东西很陌生,所以我完全不知道你可能会遇到什么样的问题,我什至不确定我是否正确理解“原子”一词。现在我正在尝试通过扩展程序集围绕内存操作制作简单的原子锁。为什么?为了好奇心。我知道我正在重新发明轮子,并且可能过度简化了整个过程。

问题是? 我在这里提供的代码是否实现了使内存操作既线程安全又可重入的目标?

  • 如果有效,为什么?
  • 如果不起作用,为什么?
  • 还不够好?例如,我应该使用registerC 中的关键字?

我只想做的事...

  • 在操作内存之前,先锁定。
  • 内存操作完成后,解锁。

代码:

volatile int atomic_gate_memory = 0;

static inline void atomic_open(volatile int *gate)
{
    asm volatile (
        "wait:\n"
        "cmp %[lock], %[gate]\n"
        "je wait\n"
        "mov %[lock], %[gate]\n"
        : [gate] "=m" (*gate)
        : [lock] "r" (1)
    );
}

static inline void atomic_close(volatile int *gate)
{
    asm volatile (
        "mov %[lock], %[gate]\n"
        : [gate] "=m" (*gate)
        : [lock] "r" (0)
    );
}

然后是这样的:

void *_malloc(size_t size)
{
        atomic_open(&atomic_gate_memory);
        void *mem = malloc(size);
        atomic_close(&atomic_gate_memory);
        return mem;
}
#define malloc(size) _malloc(size)

.. 对于 calloc、realloc、free 和 fork(对于 linux)也是如此。

#ifdef _UNISTD_H
int _fork()
{
        pid_t pid;
        atomic_open(&atomic_gate_memory);
        pid = fork();
        atomic_close(&atomic_gate_memory);
        return pid;
}
#define fork() _fork()
#endif

加载atomic_open的堆栈帧后,objdump生成:

00000000004009a7 <wait>:
4009a7: 39 10                   cmp    %edx,(%rax)
4009a9: 74 fc                   je     4009a7 <wait>
4009ab: 89 10                   mov    %edx,(%rax)

另外,考虑到上面的反汇编;我可以假设我正在进行原子操作,因为它只是一条指令吗?


我认为在 x86 上不存在任何真正主要/明显的性能问题的简单自旋锁就是这样的。当然,真正的互斥锁实现将使用系统调用(如 Linuxfutex http://man7.org/linux/man-pages/man2/futex.2.html)旋转一段时间后,解锁必须检查是否需要通过另一个系统调用通知任何服务员。这个很重要;你不想永远浪费 CPU 时间(和能量/热量)无所事事。但从概念上讲,这是在采取后备路径之前互斥锁的旋转部分。这是如何做到这一点的一个重要部分轻量级锁定 http://preshing.com/20111124/always-use-a-lightweight-mutex/已实施。 (在调用内核之前只尝试获取一次锁是一个有效的选择,而不是旋转。)

在内联汇编中尽可能多地实现此功能,或者最好使用 C11stdatomic, 像这样信号量实现 https://stackoverflow.com/a/36097001/224132。这是 NASM 语法。如果使用 GNU C 内联汇编,请确保使用"memory"破坏停止编译时内存访问重新排序 https://stackoverflow.com/questions/66855137/ttas-coherence-issue。但不要使用内联汇编;使用C_Atomic uint8_t or C++ std::atomic<uint8_t> with .exchange(1, std::memory_order_acquire) and .store(0, std::memory_order_release), and _mm_pause() from immintrin.h.

;;; UNTESTED ;;;;;;;;
;;; TODO: **IMPORTANT** fall back to OS-supported sleep/wakeup after spinning some
;;; e.g. Linux futex
    ; first arg in rdi as per AMD64 SysV ABI (Linux / Mac / etc)

;;;;;void spin_lock  (volatile char *lock)
global spin_unlock
spin_unlock:
       ; movzx  eax, byte [rdi]  ; debug check for double-unlocking.  Expect 1
    mov   byte [rdi], 0        ; lock.store(0, std::memory_order_release)
    ret

align 16
;;;;;void spin_unlock(volatile char *lock)
global spin_lock
spin_lock:
    mov   eax, 1                 ; only need to do this the first time, otherwise we know al is non-zero
.retry:
    xchg  al, [rdi]

    test  al,al                  ; check if we actually got the lock
    jnz   .spinloop
    ret                          ; no taken branches on the fast-path

align 8
.spinloop:                    ; do {
    pause
    cmp   byte [rdi], al      ; C++11
    jne   .retry              ; if (lock.load(std::memory_order_acquire) != 1)
    jmp   .spinloop

; if not translating this to inline asm, you could put the spin loop *before* the function entry point, saving the last jmp
; but since this is probably too simplistic for real use, I'm going to leave it as-is.

普通存储具有发布语义,但不具有顺序一致性(您可以从 xchg 或其他东西获得)。获取/释放 https://preshing.com/20120913/acquire-and-release-semantics足以保护关键部分(因此得名)。


如果您使用原子标志位字段,您可以使用lock bts(测试和设置)相当于 xchg-with-1。你可以旋转bt or test。要解锁,您需要lock btr, 不只是btr,因为这将是字节的非原子读取-修改-写入,甚至是包含 32 位的字节。

使用通常使用的字节或整数大小的锁,您甚至不需要locked操作解锁;释放语义就足够了 https://stackoverflow.com/questions/36731166/spinlock-with-xchg/37246395#37246395。 glibc的pthread_spin_unlock http://repo.or.cz/glibc.git/blob/3f0eedddbe260aad3a7b88051d6aa2b205218aa9:/sysdeps/x86_64/nptl/pthread_spin_unlock.S它和我的解锁功能一样吗:一个简单的商店。

(lock bts没有必要;xchg or lock cmpxchg对于普通锁来说同样好。)


第一次访问应该是原子 RMW

参见讨论cmpxchg 是否会在失败时写入目标缓存行?如果不是,对于自旋锁来说它比 xchg 更好吗? https://stackoverflow.com/questions/63008857/does-cmpxchg-write-destination-cache-line-on-failure-if-not-is-it-better-than- 如果第一次访问是只读的,CPU 可能只发出对该高速缓存行的共享请求。然后,如果它看到该行已解锁(希望是常见的低争用情况),则必须发送 RFO(读取所有权)才能真正能够写入缓存行。因此,这是非核心事务的两倍。

缺点是这需要MESI https://en.wikipedia.org/wiki/MESI_protocol该缓存行的独占所有权,但真正重要的是拥有锁的线程可以有效地存储0这样我们就可以看到它已解锁。无论哪种方式,只读或 RMW,该核心都将失去该行的独占所有权,并且必须先进行 RFO,然后才能提交该解锁存储。

我认为,当多个线程排队等待已获取的锁时,只读首次访问只会优化内核之间稍微减少的流量。对此进行优化是一件愚蠢的事情。

(最快的内联组装自旋锁 https://stackoverflow.com/questions/11959374/fastest-inline-assembly-spinlock/12979828#12979828还测试了大规模竞争自旋锁的想法,其中多个线程除了尝试获取锁之外什么都不做,但结果很差。该链接的答案提出了一些不正确的主张xchg全局锁定总线 - 对齐lock不要这样做,只是一个缓存锁(在特定情况下递增 int 是否有效地原子? https://stackoverflow.com/questions/39393850/can-num-be-atomic-for-int-num),每个核心可以在 a 上执行单独的原子 RMW不同的同时缓存行 https://stackoverflow.com/questions/11959374/fastest-inline-assembly-spinlock/12979828#comment118186534_12979828.)


然而,如果最初的尝试发现它锁住了,我们不想继续用原子 RMW 来敲击缓存行。那就是我们回到只读状态的时候。 10 个线程全是垃圾邮件xchg因为相同的自旋锁会使内存仲裁硬件非常繁忙。它可能会延迟解锁的存储的可见性(因为该线程必须争夺该行的独占所有权),因此它会直接适得其反。它也可以是其他核心的一般存储器。

PAUSE也是必不可少的,以避免 CPU 对内存排序的错误推测。仅当您正在读取的内存时才退出循环was由另一个核心修改。然而,我们不想pause在无争议的情况下。在天湖上,PAUSE等待的时间要长得多,比如从 ~5 个周期增加到 ~100 个周期,因此您绝对应该将自旋循环与初始解锁检查分开。

我确信 Intel 和 AMD 的优化手册谈到了这一点,请参阅x86 /questions/tagged/x86标记 wiki 以及大量其他链接。


还不够好?例如,我应该使用 C 中的 register 关键字吗?

register在现代优化编译器中是毫无意义的提示,除了调试版本(gcc -O0).

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

通过内联汇编锁定内存操作 的相关文章

  • 如何在c++中读取pcap文件来获取数据包信息?

    我想用 C 编写一个程序来读取 pcap 文件并获取数据包的信息 例如 len sourc ip flags 等 现在我找到了如下代码 我认为它会帮助我获取信息 但是我有一些疑问 首先我想知道应该将哪个库添加到我的程序中 然后什么是 pca
  • 如何将 protobuf-net 与不可变值类型一起使用?

    假设我有一个像这样的不可变值类型 Serializable DataContract public struct MyValueType ISerializable private readonly int x private readon
  • 在 DataView 的 RowFilter 中选择 DISTINCT

    我试图根据与另一个表的关系缩小 DataView 中的行范围 我使用的 RowFilter 如下 dv new DataView myDS myTable id IN SELECT DISTINCT parentID FROM myOthe
  • MVC 在布局代码之前执行视图代码并破坏我的脚本顺序

    我正在尝试将所有 javascript 包含内容移至页面底部 我正在将 MVC 与 Razor 一起使用 我编写了一个辅助方法来注册脚本 它按注册顺序保留脚本 并排除重复的内容 Html RegisterScript scripts som
  • 复制 std::function 的成本有多高?

    While std function是可移动的 但在某些情况下不可能或不方便 复制它会受到重大处罚吗 它是否可能取决于捕获变量的大小 如果它是使用 lambda 表达式创建的 它依赖于实现吗 std function通常被实现为值语义 小缓
  • C中的malloc内存分配方案

    我在 C 中尝试使用 malloc 发现 malloc 在分配了一些内存后浪费了一些空间 下面是我用来测试 malloc 的一段代码 include
  • 使用 LINQ2SQL 在 ASP.NET MVC 中的各种模型存储库之间共享数据上下文

    我的应用程序中有 2 个存储库 每个存储库都有自己的数据上下文对象 最终结果是我尝试将从一个存储库检索到的对象附加到从另一个存储库检索到的对象 这会导致异常 Use 构造函数注入将 DataContext 注入每个存储库 public cl
  • 使用8086汇编语言画圆[关闭]

    Closed 这个问题是无法重现或由拼写错误引起 help closed questions 目前不接受答案 我试图使用 8086 汇编器画一个圆 我尝试利用中点圆算法 https en wikipedia org wiki Midpoin
  • 在一个平台上,对于所有数据类型,所有数据指针的大小是否相同? [复制]

    这个问题在这里已经有答案了 Are char int long 甚至long long 大小相同 在给定平台上 不能保证它们的大小相同 尽管在我有使用经验的平台上它们通常是相同的 C 2011 在线草稿 http www open std
  • 如何在 32 位或 64 位配置中以编程方式运行任何 CPU .NET 可执行文件?

    我有一个可在 32 位和 64 位处理器上运行的 C 应用程序 我试图枚举给定系统上所有进程的模块 当尝试从 64 位应用程序枚举 32 位进程模块时 这会出现问题 Windows 或 NET 禁止它 我认为如果我可以从应用程序内部重新启动
  • C# HashSet 只读解决方法

    这是示例代码 static class Store private static List
  • 等待进程释放文件

    我如何等待文件空闲以便ss Save 可以用新的覆盖它吗 如果我紧密地运行两次 左右 我会得到一个generic GDI error
  • 动态添加 ASP.Net 控件

    我有一个存储过程 它根据数据库中存储的记录数返回多行 现在我想有一种方法来创建 div 带有包含该行值的控件的标记 如果从数据库返回 10 行 则 10 div 必须创建标签 我有下面的代码来从数据库中获取结果 但我不知道如何从这里继续 S
  • 将 MQTTNet 服务器与 MQTT.js 客户端结合使用

    我已经启动了一个 MQTT 服务器 就像this https github com chkr1011 MQTTnet tree master例子 该代码托管在 ASP Net Core 2 0 应用程序中 但我尝试过控制台应用程序 但没有成
  • 使用 C# 读取 Soap 消息

  • C++ 函数重载类似转换

    我收到一个错误 指出两个重载具有相似的转换 我尝试了太多的事情 但没有任何帮助 这是那段代码 CString GetInput int numberOfInput BOOL clearBuffer FALSE UINT timeout IN
  • 调用堆栈中的“外部代码”是什么意思?

    我在 Visual Studio 中调用一个方法 并尝试通过检查调用堆栈来调试它 其中一些行标记为 外部代码 这到底是什么意思 方法来自 dll已被处决 外部代码 意味着该dll没有可用的调试信息 你能做的就是在Call Stack窗口中单
  • 方法优化 - C#

    我开发了一种方法 允许我通过参数传入表 字符串 列数组 字符串 和值数组 对象 然后使用这些参数创建参数化查询 虽然它工作得很好 但代码的长度以及多个 for 循环散发出一种代码味道 特别是我觉得我用来在列和值之间插入逗号的方法可以用不同的
  • 如何部署“SQL Server Express + EF”应用程序

    这是我第一次部署使用 SQL Server Express 数据库的应用程序 我首先使用实体 框架模型来联系数据库 我使用 Install Shield 创建了一个安装向导来安装应用程序 这些是我在目标计算机中安装应用程序所执行的步骤 安装
  • 无法接收 UDP Windows RT

    我正在为 Windows 8 RT 编写一个 Windows Store Metro Modern RT 应用程序 需要在端口 49030 上接收 UDP 数据包 但我似乎无法接收任何数据包 我已按照使用教程进行操作DatagramSock

随机推荐

  • 即使我使用 html_entity_decode ,html 实体也会传递到数据库中

    string susan 039 s string is scraped from website string html entity decode string sql INSERT INTO database SET name str
  • 读取csv文件ios

    我在读取 csv 文件时遇到问题 仅显示 csv 文件的最后一行 但是在我的 fetchedResultsController 中我有 2 行 这是代码 NSString writeString NSInteger i 0 for id o
  • 如何评估 Application Insights 请求“自己”的持续时间,而不考虑依赖项的持续时间?

    我正在尝试生成一个 Kusto 查询来测量请求的 自己 持续时间 减去依赖项的持续时间 但是 我无法真正弄清楚如何通过纯 Kusto 查询来解决这个问题 为了更好地理解预期的结果 下面是一个示例案例 高级视图 其中 R 是请求 Dx 是依赖
  • Python - re.findall 返回不需要的结果

    re findall 100 0 9 0 9 0 9 89 这仅返回结果 89 我需要退还全部 89 请问有什么想法怎么做吗 gt gt gt re findall 100 0 9 0 9 0 9 89 89 当有捕获组时findall仅返
  • mongodb第二个id字段自动递增

    我想在我的 mongodb 集合中有一个额外的 ID 字段 objectId 非常适合获取唯一 ID 但我需要更短的 ID 来进行用户管理 这些 ID 应该类似于100001 100002等等 是否可以通过自动增量获得这些 Thx Mong
  • 使用 Flex 和 Bison 编译时未定义对“_yyerror”的引用

    我正在尝试为迷你 Pascal 语言制作一个编译器 我为此使用了 Flex 和 Bison 并且出现了这个错误 我的 Flex 文件 include y tab h include
  • PyTorch ROCm 已推出 - 如何选择 Radeon GPU 作为设备

    由于 Pytorch 发布了 ROCm 版本 这使我能够使用 nvidias 之外的其他 GPU 我如何在 python 中选择我的 radeon gpu 作为设备 显然 像 device torch cuda is available 或
  • 将布尔属性编辑器转换为 MVC 视图中的下拉列表

    我目前已经搭建了一个视图 其中模型的布尔属性被传递给 Html EditorFor 帮助器 Html EditorFor model gt model EndCurrentDeal 一切都很好 但我真正想做的是将其按摩到下拉菜单中 例如
  • 在reactJS中下载文件的按钮

    我目前正在制作个人作品集 我正在尝试制作一个按钮 如果您单击它 则应下载简历 code
  • 需要详细说明未处理的延续参考

    我们的公司门户无法从 AD 中获取某个用户的组 在门户日志中 我们看到此错误 javax naming PartialResultException 未处理的继续引用剩余名称 我在 Google 上搜索了该错误 似乎描述此情况的最佳症状以及
  • C# 中的 Unix 时间转换 [重复]

    这个问题在这里已经有答案了 我正在尝试以unix时间获取GMT 我使用以下代码 public static long GetGMTInMS var unixTime DateTime Now ToUniversalTime new Date
  • CloudFormation 问题:无法删除堆栈

    我为我们的资源创建 CloudFormation 模板 它包括 Lambda 函数 API 网关 角色等 为了验证我们的模板 我使用它创建 CloudFormation 堆栈 检查我更新的一些资源 然后删除堆栈 但上次我尝试删除堆栈时收到这
  • Git 查找历史上所有的二进制文件

    抱歉 如果这与上一个问题重复 但我找不到我要找的东西 我正在将一个大型 cvs 代码集 20 多个具有 15 年历史的存储库 10 15 GB 大小 转换为 git 大部分大小是由于过去与代码一起提交的二进制文件造成的 虽然某些二进制文件是
  • 在 R 中绘制简单数据

    我有一个逗号分隔的文件 名为foo csv包含以下数据 scale serial spawn for worker 5 0 000178 0 000288 0 000292 0 000300 10 0 156986 0 297926 0 0
  • 在 Android Studio 中调试时证书验证路径错误

    我刚刚生成了示例应用程序https flutter dev docs get started codelab https flutter dev docs get started codelab 当我点击 Android Studio 中的
  • Objective-C:在应用程序上播放 Youtube 视频

    我正在尝试探索在 iOS 应用程序开发中我还能做些什么 现在我尝试在我的应用程序中包含一个视频 我下面有这段代码 旨在在视图加载时播放 YouTube 视频 但我得到的只是一个黑色的 webView NSString videoURL ht
  • R 中是否有 FoldLeft 函数?

    我想知道R中是否有foldLeft函数 和foldRight 的实现 该语言应该是 相当 面向功能的 因此我认为应该有类似的东西 但我在文档中找不到它 对我来说 foldLeft 函数适用于列表并具有以下签名 foldLeft B z B
  • 我应该分配或重置 unique_ptr 吗?

    考虑到所拥有对象的生命周期与其所有者相关联的常见情况 我可以通过以下两种方式之一使用唯一指针 它可以被赋值 class owner std unique ptr
  • iPhone开发:如何为UIActionSheet创建彩色或半透明背景?

    当您尝试在 iPhone 的 便笺 应用程序中删除便笺时 会弹出 UIActionSheet 该片材是半透明的 但不是黑色半透明的 这是如何实现的 是否可以将 UIActionSheet 的背景设置为某种颜色 我通常实现以下委托方法 voi
  • 通过内联汇编锁定内存操作

    我对低级的东西很陌生 所以我完全不知道你可能会遇到什么样的问题 我什至不确定我是否正确理解 原子 一词 现在我正在尝试通过扩展程序集围绕内存操作制作简单的原子锁 为什么 为了好奇心 我知道我正在重新发明轮子 并且可能过度简化了整个过程 问题