在 docker 容器内使用 membarrier
Use a membarrier inside a docker container
我有一个程序需要能够处理 SIGTERM。为此,我需要 sigterm 处理程序将设置的 sig_atomic_t
全局标志。为了让主代码能够可靠地读取该变量,我需要在处理程序和主代码中都使用一个 membarrier。
我现在的情况是这样的:
static int mb_cmd;
static sig_atomic_t sigterm;
static
int mb_init(void);
static
int sigterm_init(void);
static
void sigterm_handler(int sig);
inline
int membarrier(int cmd, int flags)
{
return syscall(__NR_membarrier, cmd, flags);
}
int main(void)
{
int status;
status = 1;
if (sigterm_init())
goto err;
do {
// do stuff
asm volatile ("" : : : "memory");
} while (!sigterm);
return 0;
err:
fprintf(stderr, "ERROR: main(): %i\n", status);
perrorx(NULL);
return status;
}
static
int mb_init(void)
{
static bool done = false;
int cmd;
int status;
if (done)
return 0;
status = 1;
cmd = membarrier(MEMBARRIER_CMD_QUERY, 0);
if (cmd < 0)
goto err;
if (cmd & MEMBARRIER_CMD_PRIVATE_EXPEDITED) {
status = 2;
mb_cmd = MEMBARRIER_CMD_PRIVATE_EXPEDITED;
if (membarrier(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED, 0))
goto err;
} else if (cmd & MEMBARRIER_CMD_GLOBAL_EXPEDITED) {
status = 3;
mb_cmd = MEMBARRIER_CMD_GLOBAL_EXPEDITED;
if (membarrier(MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED, 0))
goto err;
} else {
mb_cmd = MEMBARRIER_CMD_GLOBAL;
}
status = 4;
if (membarrier(mb_cmd, 0))
goto err;
done = true;
return 0;
err:
fprintf(stderr, "ERROR: mb_init(): %i\n", status);
return status;
}
static
int sigterm_init(void)
{
struct sigaction sa = {0};
int status;
status = 1;
if (mb_init())
goto err;
sigterm = false;
membarrier(mb_cmd, 0);
status++;
sigemptyset(&sa.sa_mask);
sa.sa_handler = &sigterm_handler;
if (sigaction(SIGTERM, &sa, NULL))
goto err;
return 0;
err:
fprintf(stderr, "ERROR: sigterm_init(): %i\n", status);
return status;
}
static
void sigterm_handler(int sig)
{
(void)sig;
sigterm = true;
membarrier(mb_cmd, 0);
}
当我 运行 我的计算机上的程序运行正常时,但在 docker 上它显示以下错误(errno
是 1):
ERROR: mb_init(): 1
ERROR: sigterm_init(): 1
ERROR: main(): 1
./rob:
rob.c:184:
main():
E1 - Operation not permitted
如何在应该 运行 on docker 的程序中使用内存屏障?
membarrier
系统调用不在 Docker seccomp whitelist by default so you need to pass a modified profile 到你的 docker run
命令,如果你想使用它:
docker run --security-opt seccomp=/path/to/seccomp/profile.json myimage
我不确定为什么没有列入白名单,您可以询问 docker 开发人员这是错误还是预期的配置。
And for the main code to be able to reliably read that variable, I need to use a membarrier both in the handler and in the main code.
不,只是让它成为 volatile sig_atomic_t
. ISO C 保证让您的代码工作而无需在源代码中编写任何明确的障碍。 (基本上像无锁 _Atomic
和 mo_relaxed 排序,除了有序的 wrt。其他易失性访问。)
如果您确实需要内存屏障,则不需要 membarrier
系统调用,只需 asm("" ::: "memory")
强制存储或加载在循环中至少发生一次。
membarrier()
如果您有另一个线程从内存中执行弱顺序加载,但无法优化掉(提升到循环之外),则
membarrier()
可能会有用。然后 membarrier()
可能会将另一个核心上的松弛负载转变为有效的获取负载,如果您在生产者线程中的两个存储之间进行的话。
因为您已经在 reader 中使用了编译时完全屏障(以阻止非易失性负载被提升到循环之外),并检查 exit_now 或keep_running flag 没有排序。其他代码,你不需要那个。
ISO C 一开始只保证 volatile sig_atomic_t
的任何内容,而不是普通的 sig_atomic_t
(通常只是 int
)。 使用 sig_atomic_t
的唯一原因是如果您将它与 volatile
一起使用。
实际上,volatile int
甚至对其他 线程 可见,而不仅仅是在信号处理程序和暂停到 运行 信号的线程之间处理程序。 (因为真正的 C 实现 运行 在缓存一致的硬件上,并且不进行硬件竞争检测等)但那时你只是在滚动你自己的无锁原子,应该使用 _Atomic int
.另请参阅 https://electronics.stackexchange.com/questions/387181/mcu-programming-c-o2-optimization-breaks-while-loop/387478#387478 以了解单个线程与中断(或信号)处理程序之间的原子性。
另见 When to use volatile with multi threading? - 基本上从不使用 stdatomic.h
中的 C11 _Atomic
。我的回答解释了为什么它在实践中有效,以及究竟发生了什么。
我有一个程序需要能够处理 SIGTERM。为此,我需要 sigterm 处理程序将设置的 sig_atomic_t
全局标志。为了让主代码能够可靠地读取该变量,我需要在处理程序和主代码中都使用一个 membarrier。
我现在的情况是这样的:
static int mb_cmd;
static sig_atomic_t sigterm;
static
int mb_init(void);
static
int sigterm_init(void);
static
void sigterm_handler(int sig);
inline
int membarrier(int cmd, int flags)
{
return syscall(__NR_membarrier, cmd, flags);
}
int main(void)
{
int status;
status = 1;
if (sigterm_init())
goto err;
do {
// do stuff
asm volatile ("" : : : "memory");
} while (!sigterm);
return 0;
err:
fprintf(stderr, "ERROR: main(): %i\n", status);
perrorx(NULL);
return status;
}
static
int mb_init(void)
{
static bool done = false;
int cmd;
int status;
if (done)
return 0;
status = 1;
cmd = membarrier(MEMBARRIER_CMD_QUERY, 0);
if (cmd < 0)
goto err;
if (cmd & MEMBARRIER_CMD_PRIVATE_EXPEDITED) {
status = 2;
mb_cmd = MEMBARRIER_CMD_PRIVATE_EXPEDITED;
if (membarrier(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED, 0))
goto err;
} else if (cmd & MEMBARRIER_CMD_GLOBAL_EXPEDITED) {
status = 3;
mb_cmd = MEMBARRIER_CMD_GLOBAL_EXPEDITED;
if (membarrier(MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED, 0))
goto err;
} else {
mb_cmd = MEMBARRIER_CMD_GLOBAL;
}
status = 4;
if (membarrier(mb_cmd, 0))
goto err;
done = true;
return 0;
err:
fprintf(stderr, "ERROR: mb_init(): %i\n", status);
return status;
}
static
int sigterm_init(void)
{
struct sigaction sa = {0};
int status;
status = 1;
if (mb_init())
goto err;
sigterm = false;
membarrier(mb_cmd, 0);
status++;
sigemptyset(&sa.sa_mask);
sa.sa_handler = &sigterm_handler;
if (sigaction(SIGTERM, &sa, NULL))
goto err;
return 0;
err:
fprintf(stderr, "ERROR: sigterm_init(): %i\n", status);
return status;
}
static
void sigterm_handler(int sig)
{
(void)sig;
sigterm = true;
membarrier(mb_cmd, 0);
}
当我 运行 我的计算机上的程序运行正常时,但在 docker 上它显示以下错误(errno
是 1):
ERROR: mb_init(): 1
ERROR: sigterm_init(): 1
ERROR: main(): 1
./rob:
rob.c:184:
main():
E1 - Operation not permitted
如何在应该 运行 on docker 的程序中使用内存屏障?
membarrier
系统调用不在 Docker seccomp whitelist by default so you need to pass a modified profile 到你的 docker run
命令,如果你想使用它:
docker run --security-opt seccomp=/path/to/seccomp/profile.json myimage
我不确定为什么没有列入白名单,您可以询问 docker 开发人员这是错误还是预期的配置。
And for the main code to be able to reliably read that variable, I need to use a membarrier both in the handler and in the main code.
不,只是让它成为 volatile sig_atomic_t
. ISO C 保证让您的代码工作而无需在源代码中编写任何明确的障碍。 (基本上像无锁 _Atomic
和 mo_relaxed 排序,除了有序的 wrt。其他易失性访问。)
如果您确实需要内存屏障,则不需要 membarrier
系统调用,只需 asm("" ::: "memory")
强制存储或加载在循环中至少发生一次。
membarrier()
如果您有另一个线程从内存中执行弱顺序加载,但无法优化掉(提升到循环之外),则
membarrier()
可能会有用。然后 membarrier()
可能会将另一个核心上的松弛负载转变为有效的获取负载,如果您在生产者线程中的两个存储之间进行的话。
因为您已经在 reader 中使用了编译时完全屏障(以阻止非易失性负载被提升到循环之外),并检查 exit_now 或keep_running flag 没有排序。其他代码,你不需要那个。
ISO C 一开始只保证 volatile sig_atomic_t
的任何内容,而不是普通的 sig_atomic_t
(通常只是 int
)。 使用 sig_atomic_t
的唯一原因是如果您将它与 volatile
一起使用。
实际上,volatile int
甚至对其他 线程 可见,而不仅仅是在信号处理程序和暂停到 运行 信号的线程之间处理程序。 (因为真正的 C 实现 运行 在缓存一致的硬件上,并且不进行硬件竞争检测等)但那时你只是在滚动你自己的无锁原子,应该使用 _Atomic int
.另请参阅 https://electronics.stackexchange.com/questions/387181/mcu-programming-c-o2-optimization-breaks-while-loop/387478#387478 以了解单个线程与中断(或信号)处理程序之间的原子性。
另见 When to use volatile with multi threading? - 基本上从不使用 stdatomic.h
中的 C11 _Atomic
。我的回答解释了为什么它在实践中有效,以及究竟发生了什么。