membarrier(2) | System Calls Manual | membarrier(2) |
membarrier - задаёт барьеры памяти в наборе нитей
Standard C library (libc, -lc)
#include <linux/membarrier.h> /* определения констант MEMBARRIER_* */ #include <sys/syscall.h> /* определения констант SYS_* */ #include <unistd.h>
int syscall(SYS_membarrier, int cmd, unsigned int flags, int cpu_id);
Note: glibc provides no wrapper for membarrier(), necessitating the use of syscall(2).
Системный вызов membarrier() помогает сократить накладные расходы инструкций барьера памяти, которые требуются при доступе к памяти в многоядерных системах. Однако данный системный вызов затратнее чем барьер памяти, поэтому чтобы использовать его эффективно недостаточно просто заменить им барьеры памяти, требуется понимание описанного далее.
При использовании барьеров памяти нужно принимать во внимание, что барьер памяти всегда должен или иметь противоположную сторону барьера, или что модель памяти архитектуры этого не требует.
При работе бывает, что одна сторона барьера (которую будем называть «быстрой стороной») применяется чаще чем другая (которую будем называть «медленной стороной»). Это основная цель использования membarrier(). Основная идея в замене этих барьеров: быстрые стороны барьеров — простыми барьерами компилятора:
asm volatile ("" : : : "memory")
а медленные стороны барьеров — вызовами membarrier().
Это добавит накладных расходов к медленной стороне и удалит расходы у быстрой стороны, что в результате повысит общую производительность в зависимости от того, как часто используются вызовы membarrier() у медленной стороны, из-за которой возникают издержки, и не перевесит ли это увеличение производительности быстрой стороны.
Аргумент cmd может принимать одно из следующих значений:
The flags argument must be specified as 0 unless the command is MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ, in which case flags can be either 0 or MEMBARRIER_CMD_FLAG_CPU.
The cpu_id argument is ignored unless flags is MEMBARRIER_CMD_FLAG_CPU, in which case it must specify the CPU targeted by this membarrier command.
Все доступы к памяти, выполняемые в программном порядке из каждого целевого потока, гарантированно упорядочены в отношении membarrier().
Если использовать семантику barrier() для представления барьера компилятора, обеспечивающего доступа к памяти, выполняемого в программном порядке для пересечения барьера, и smp_mb() — для представления явных барьеров памяти, обеспечивающих полный упорядоченный доступ к памяти через барьер, то получится следующая упорядочивающая таблица для каждой пары barrier(), membarrier() и smp_mb(). Наличие порядка в паре показано знаком (O: упорядочено, X: не упорядочено):
barrier() | smp_mb() | membarrier() | |
barrier() | X | X | O |
smp_mb() | X | O | O |
membarrier() | O | O | O |
On success, the MEMBARRIER_CMD_QUERY operation returns a bit mask of supported commands, and the MEMBARRIER_CMD_GLOBAL, MEMBARRIER_CMD_GLOBAL_EXPEDITED, MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED, MEMBARRIER_CMD_PRIVATE_EXPEDITED, MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED, MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE, and MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE operations return zero. On error, -1 is returned, and errno is set to indicate the error.
Для данной команды, если флаг flags равен 0, то системный вызов всегда гарантирует возврат одного и того же значения до перезагрузки. Последующие вызовы с теми же аргументами всегда возвращают тот же результат. Поэтому, если flags равен 0, то обработка ошибок требуется только при первом вызове membarrier().
Системный вызов membarrier() впервые появился в Linux версии 4.3.
Before Linux 5.10, the prototype for membarrier() was:
int membarrier(int cmd, int flags);
Вызов membarrier() есть только в Linux.
Инструкция барьера памяти является частью системы команд архитектуры со слабо упорядоченными моделями памяти. Она упорядочивает доступ к памяти до барьера и после барьера в соответствии совпадающими барьерами на других ядрах. Например, загрузка барьера (fence) может упорядочить загрузку до и после этого барьера в соответствии порядком хранилища, установленного барьерами хранилища.
Программный порядок — это порядок, в котором инструкции упорядочены в ассемблерном коде программы.
Вызов membarrier() может быть полезным для реализации библиотек чтения-копирования-обновления или сборщиков мусора.
Предполагая, что в многонитевой программе «fast_path()» выполняется очень часто, а «slow_path()» — редко, следующий код (x86) можно преобразовать используя membarrier():
#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(void) { int read_a, read_b; /* * В реальных приложениях вызовы fast_path() и slow_path() * были бы в разных нитях. Их вызов из main() сделан только * для укорачивания данного примера. */ slow_path(&read_a); fast_path(&read_b); /* * read_b == 0 подразумевает read_a == 1 и * read_a == 0 подразумевает 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; /* Проверка поддержки в функции membarrier() */ ret = membarrier(MEMBARRIER_CMD_QUERY, 0, 0); if (ret < 0) { perror("membarrier"); return -1; } if (!(ret & MEMBARRIER_CMD_GLOBAL)) { fprintf(stderr, "membarrier не поддерживает 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); /* * В реальных приложениях вызовы fast_path() и slow_path() * были бы в разных нитях. Их вызов из main() сделан только * для укорачивания данного примера. */ slow_path(&read_a); fast_path(&read_b); /* * read_b == 0 подразумевает read_a == 1 и * read_a == 0 подразумевает read_b == 1. */ if (read_b == 0 && read_a == 0) abort(); exit(EXIT_SUCCESS); }
Русский перевод этой страницы руководства был сделан aereiae <aereiae@gmail.com>, Alexey <a.chepugov@gmail.com>, Azamat Hackimov <azamat.hackimov@gmail.com>, Dmitriy S. Seregin <dseregin@59.ru>, Dmitry Bolkhovskikh <d20052005@yandex.ru>, ITriskTI <ITriskTI@gmail.com>, Max Is <ismax799@gmail.com>, Yuri Kozlov <yuray@komyakino.ru>, Иван Павлов <pavia00@gmail.com> и Малянов Евгений Викторович <maljanow@outlook.com>
Этот перевод является бесплатной документацией; прочитайте Стандартную общественную лицензию GNU версии 3 или более позднюю, чтобы узнать об условиях авторского права. Мы не несем НИКАКОЙ ОТВЕТСТВЕННОСТИ.
Если вы обнаружите ошибки в переводе этой страницы руководства, пожалуйста, отправьте электронное письмо на man-pages-ru-talks@lists.sourceforge.net.
15 декабря 2022 г. | Linux man-pages 6.03 |