c - 理解 linux 中的 membarier 函数
问题描述
linux手册中使用membarrier函数的示例:https ://man7.org/linux/man-pages/man2/membarrier.2.html
#include <stdlib.h>
static volatile int a, b;
static void
fast_path(int *read_b)
{
a = 1;
asm volatile ("mfence" : : : "memory");
*read_b = b;
}
static void
slow_path(int *read_a)
{
b = 1;
asm volatile ("mfence" : : : "memory");
*read_a = a;
}
int
main(int argc, char **argv)
{
int read_a, read_b;
/*
* Real applications would call fast_path() and slow_path()
* from different threads. Call those from main() to keep
* this example short.
*/
slow_path(&read_a);
fast_path(&read_b);
/*
* read_b == 0 implies read_a == 1 and
* read_a == 0 implies read_b == 1.
*/
if (read_b == 0 && read_a == 0)
abort();
exit(EXIT_SUCCESS);
}
上面转换为使用 membarrier() 的代码变为:
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/membarrier.h>
static volatile int a, b;
static int
membarrier(int cmd, unsigned int flags, int cpu_id)
{
return syscall(__NR_membarrier, cmd, flags, cpu_id);
}
static int
init_membarrier(void)
{
int ret;
/* Check that membarrier() is supported. */
ret = membarrier(MEMBARRIER_CMD_QUERY, 0, 0);
if (ret < 0) {
perror("membarrier");
return -1;
}
if (!(ret & MEMBARRIER_CMD_GLOBAL)) {
fprintf(stderr,
"membarrier does not support MEMBARRIER_CMD_GLOBAL\n");
return -1;
}
return 0;
}
static void
fast_path(int *read_b)
{
a = 1;
asm volatile ("" : : : "memory");
*read_b = b;
}
static void
slow_path(int *read_a)
{
b = 1;
membarrier(MEMBARRIER_CMD_GLOBAL, 0, 0);
*read_a = a;
}
int
main(int argc, char **argv)
{
int read_a, read_b;
if (init_membarrier())
exit(EXIT_FAILURE);
/*
* Real applications would call fast_path() and slow_path()
* from different threads. Call those from main() to keep
* this example short.
*/
slow_path(&read_a);
fast_path(&read_b);
/*
* read_b == 0 implies read_a == 1 and
* read_a == 0 implies read_b == 1.
*/
if (read_b == 0 && read_a == 0)
abort();
exit(EXIT_SUCCESS);
}
这个“成员”描述取自 Linux 手册。我仍然对“成员”功能如何将开销添加到慢速端并从快速端移除开销感到困惑,因此只要慢速端不频繁以至于成员的开销( ) 调用不会超过快速方面的性能提升。
你能帮我更详细地描述一下吗?
谢谢!
解决方案
这对 writes-then-read-the-other-var 是https://preshing.com/20120515/memory-reordering-caught-in-the-act/,StoreLoad重新排序的演示(x86 唯一允许的一种,给定它的程序顺序 +具有存储转发内存模型的存储缓冲区)。
只有一个本地 MFENCE 仍然可以重新排序:
FAST using just mfence, not membarrier
a = 1 exec
read_b = b; // 0
b = 1;
mfence (force b=1 to be visible before reading a)
read_a = a; // 0
a = 1 visible (global vis. delayed by store buffer)
但是考虑一下,如果在慢速路径的存储和重新加载之间,每个可能的顺序都必须包含 mfence-on-every-core 会发生什么。
这种排序将不再可能。如果read_b=b
已经读取了 0,那么a=1
已经是挂起的1(如果它还不可见)。它不可能保持私有直到之后,read_a = a
因为 membarrier() 确保在每个核心上运行一个完整的屏障,并且 SLOW 在读取之前等待这种情况发生(成员返回)a
。
并且没有办法先0,0
执行 SLOW ;它自己运行 membarrier,因此它的存储在读取之前对其他线程绝对a
可见。
脚注 1:等待执行,或在存储缓冲区中等待提交到 L1d 缓存。确保了这asm("":::"memory")
一点,但实际上是多余的,因为volatile
它本身保证了访问按程序顺序在 asm 中发生。在手动滚动原子而不是使用 C11 时,我们基本上需要 volatile 其他原因_Atomic
。(但通常不要这样做,除非您实际上正在编写内核代码。使用atomic_store_explicit(&a, 1, memory_order_release);
)。
请注意,它实际上是创建 StoreLoad 重新排序的存储缓冲区(x86 允许的唯一类型),而不是 OoO exec。实际上,存储缓冲区还允许 x86 乱序执行存储,然后使它们按程序顺序全局可见(如果事实证明它们不是错误推测或其他原因的结果!)。
另请注意,有序 CPU 可以无序地访问内存。它们按顺序开始指令(包括加载),但可以让它们不按顺序完成,例如通过记分板加载以允许击中未命中。另请参阅如何使用按序提交进行加载->存储重新排序?
推荐阅读
- javascript - 服务器响应后如何再次触发指令
- php - PHP登录页面不适用于password_verify
- c# - 如何将列表从控制器/视图发送到 JS 脚本
- c# - 如何使用 C# 中的方法在列表中添加数据?我正在尝试添加,但它不能保存数据并且只显示最后一个条目
- java - 带有嵌入式 id 的实体的带有 Spring Boot DDL 查询错误的 Wix 嵌入式 mysql
- jenkins - 基于 git commit 的 Jenkinsfile 管道触发阶段
- android - Android Studio 中导航抽屉预览中的渲染问题
- firebase-realtime-database - 颤动如何使gridview显示像按钮这样的firebase数据
- applescript - 错误:命令失败:osascript -e 告诉应用程序“系统事件”计算名称为“模拟器”的进程 (-1743)
- javascript - 当我按下滚动按钮时,如何限制骰子(3-d 类型)仅滚动三次,并且每次滚动时还需要骰子的值