在 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。我的回答解释了为什么它在实践中有效,以及究竟发生了什么。