Используйте мембрану внутри контейнера docker - PullRequest
2 голосов
/ 02 марта 2020

У меня есть программа, которая должна уметь обрабатывать SIGTERM. Для этого мне нужен глобальный флаг sig_atomic_t, который установит обработчик sigterm. И для того, чтобы основной код мог надежно прочитать эту переменную, мне нужно использовать мембрану как в обработчике, так и в основном коде.

Как у меня сейчас, это выглядит так:

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?

Ответы [ 2 ]

2 голосов
/ 02 марта 2020

Системный вызов membarrier не включен Docker seccomp белый список по умолчанию, поэтому вам нужно передать измененный профиль в команду docker run, если вы хотите использовать it:

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

Я не уверен, почему не внесен в белый список, вы можете спросить у docker devs, если это ошибка или ожидаемая конфигурация.

1 голос
/ 02 марта 2020

И чтобы основной код мог надежно прочитать эту переменную, мне нужно использовать мембрану как в обработчике, так и в основном коде.

Нет, просто сделайте это volatile sig_atomic_t. ISO C гарантирует, что ваш код будет работать без необходимости писать какие-либо явные барьеры в исходном коде. (В основном, как без блокировки _Atomic с упорядочением mo_relaxed, за исключением упорядоченного по отношению к другим изменчивым доступам.)

И если вам нужен барьер памяти, вам не нужен системный вызов membarrier, просто asm("" ::: "memory"), чтобы заставить или хранилище происходить хотя бы раз в al oop.

membarrier(), может быть полезно, если у вас есть другой поток, выполняющий слабо упорядоченные загрузки из памяти, но который не может быть оптимизирован (поднят из oop). Тогда membarrier() может превратить эту расслабленную нагрузку на другое ядро ​​в фактически загрузочную загрузку, если вы делаете это между двумя хранилищами в потоке производителя.

Поскольку вы уже используете полный барьер во время компиляции в считыватель (чтобы не допустить подъема энергонезависимой нагрузки из l oop) и проверка флага exit_now или keep_running не имеет порядка упорядочения. другой код, вам это не нужно.


ISO C гарантирует только что-либо за volatile sig_atomic_t, а не просто sig_atomic_t (обычно это просто int). Единственная причина использования sig_atomic_t состоит в том, что вы используете его с volatile.

На практике volatile int будет даже виден другим потокам , а не только между обработчиком сигнала и потоком, который приостановил выполнение обработчика сигнала. (Поскольку настоящие C реализации работают на аппаратном обеспечении, согласованном с кешем, и не определяют аппаратное состязание, и т.д. *. См. Также https://electronics.stackexchange.com/questions/387181/mcu-programming-c-o2-optimization-breaks-while-loop/387478#387478 для атомарности между одним потоком и обработчиком прерывания (или сигнала).

И также смотрите Когда использовать volatile с многопоточностью? - в основном никогда, используйте C11 _Atomic из stdatomic.h. Мой ответ объясняет, почему это работает на практике, и что именно происходит.

...