【问题标题】:Use a membarrier inside a docker container在 docker 容器中使用 membarrier
【发布时间】:2020-06-15 02:35:00
【问题描述】:

我有一个程序需要能够处理 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

如何在应该在 docker 上运行的程序中使用内存屏障?

【问题讨论】:

  • 您总是可以使用普通类型的内存屏障(原子加载/存储)而不是内存屏障。

标签: c docker signals system-calls memory-barriers


【解决方案1】:

默认情况下,membarrier 系统调用不在 Docker seccomp whitelist 上,因此如果您想使用它,您需要将 modified profile 传递给您的 docker run 命令:

docker run --security-opt seccomp=/path/to/seccomp/profile.json myimage

我不确定为什么没有列入白名单,您可以询问 docker 开发人员这是错误还是预期配置。

【讨论】:

  • 更重要的是,对于这个用例,OP 不需要也不应该使用membarrier
【解决方案2】:

为了使主代码能够可靠地读取该变量,我需要在处理程序和主代码中都使用一个 membarrier。

不,只需将其设为volatile sig_atomic_t ISO C 保证无需在源代码中编写任何显式屏障即可使您的代码正常工作。 (基本上就像带 mo_relaxed 排序的无锁 _Atomic,除了有序 wrt。其他 volatile 访问。)

如果您确实需要内存屏障,则不需要membarrier 系统调用,只需asm("" ::: "memory") 即可强制存储或加载在循环中至少发生一次。

membarrier() 如果您有另一个线程从内存执行弱排序加载,但无法优化(从循环中提升),这可能会很有用。然后membarrier() 可能会将另一个内核上的轻松负载转换为有效的获取负载,如果您在生产者线程中的两个存储之间执行此操作。

由于您已经在阅读器中使用了编译时完全屏障(以阻止非易失性负载被提升出循环),并且检查 exit_now 或 keep_running 标志没有排序 wrt。其他代码,你不需要。


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

【讨论】:

    猜你喜欢
    • 2019-02-13
    • 2017-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-28
    相关资源
    最近更新 更多