SEMOP(2)

ИМЯ
semop, semtimedop - операции с семафорами System V

ОБЗОР
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, size_t nsops);

int semtimedop(int semid, struct sembuf *sops, size_t nsops,
               const struct timespec *timeout);

Требования макроса тестирования свойств для glibc (см. feature_test_macros(7)):

semtimedop(): _GNU_SOURCE

ОПИСАНИЕ
С каждым семафором в наборе семафоров System V связаны следующие значения:

unsigned short  semval;   /* значение семафора */
unsigned short  semzcnt;  /* # ожидает ноль */
unsigned short  semncnt;  /* # ожидает увеличения */
pid_t           sempid;   /* ID процесса, выполнявшего последнюю
                             операцию */

Вызов semop() производит операции над выбранными семафорами из набора семафоров semid. Каждый из элементов nsops в массиве, указанном в sops является структурой, которой задаётся операция, выполняемая над отдельным семафором. Элементы этой структуры имеют тип struct sembuf, который содержит поля:

unsigned short sem_num;  /* номер семафора */
short          sem_op;   /* операция над семафором */
short          sem_flg;  /* флаги операции */

Флаги в sem_flg могут иметь значения IPC_NOWAIT и SEM_UNDO. Если указан флаг SEM_UNDO, то при завершении процесса будет выполнена откат операции.

Набор операций из sops выполняется в порядке появления в массиве и является атомарным, то есть выполняются или все операции, или ни одной. Поведение системного вызова при обнаружении невозможности немедленного выполнения операций зависит от наличия флага IPC_NOWAIT в полях sem_flg отдельных операций, как это описано далее. Каждая операция выполняется над sem_num-тым семафором из набора, где первый семафор имеет номер 0. Есть три типа операций, различающихся значением sem_op.

Если значение sem_op — положительное целое число, то оно добавляется к значению семафора (semval). Если для операции стоит флаг SEM_UNDO, то система вычитает значение sem_op из значения регулировки (semadj) семафора. Эта операция выполняется всегда и не переводит нить в режим ожидания. Вызывающий процесс должен иметь права на изменение набора семафоров.

Если значение sem_op равно нулю, то процесс должен иметь права на чтение набора семафоров. Эта операция «ожидания нуля»: если semval равно нулю, то операция может выполнится сразу. Иначе, если в поле семафора sem_flg указан флаг IPC_NOWAIT, то semop() завершается с ошибкой и errno присваивается значение EAGAIN (и ни одна операция из sops не выполняется). Или же semzcnt (счётчик нитей, ожидающих пока значение семафора не сравнялось с нулём) увеличивается на единицу, а нить переходит в режим ожидания пока не случится одно из:

•   Значение semval станет равным 0, тогда значение semzcnt уменьшается.

•   Набор семафоров удалится: semop() завершается с ошибкой, а errno присваивается значение EIDRM.

•   Вызывающая нить получит сигнал: значение semncnt уменьшается и semop() завершается с ошибкой, а errno присваивается значение EINTR.

Если значение sem_op меньше нуля, то процесс должен иметь права на изменение набора семафоров. Если значение semval больше или равно абсолютному значению sem_op, то операция может выполнятся сразу: абсолютное значение sem_op вычитается из semval, и, если для этой операции установлен флаг SEM_UNDO, система добавляет абсолютное значение sem_op к значению регулировки (semadj) семафора. Если абсолютное значение sem_op больше semval, и в sem_flg указан IPC_NOWAIT, то semop() завершается с ошибкой, а errno присваивается значение EAGAIN (и ни одна операция из sops не выполняется). Иначе semncnt (счётчик нитей, ожидающих увеличения значения семафора) увеличивается на единицу, а нить переходит в режим ожидания пока не случится одно из:

•   semval становится больше или равно абсолютному значению sem_op: операция продолжается как описано выше.

•   Набор семафоров удалится из системы: semop() завершается с ошибкой, а errno присваивается значение EIDRM.

•   Вызывающая нить получит сигнал: значение semncnt уменьшается и semop() завершается с ошибкой, а errno присваивается значение EINTR.

При успешном выполнении значение sempid для каждого семафора, указанного в массиве, на который указывает sops, устанавливается равным идентификатору вызывающего процесса. Также sem_otime присваивается значение текущего времени.

  semtimedop()
Системный вызов semtimedop() ведёт себя идентично semop(), за исключением того, что в случаях, когда вызывающая нить будет спать, длительность этого сна ограничена количеством времени, определяемым структурой timespec, чей адрес передаётся в аргументе timeout. Данное значение интервала будет округлено до точности системных часов, а из-за задержки при планировании в ядре блокирующий интервал будет немного больше. Если достигнут указанный лимит времени, то semtimedop() завершится с ошибкой, а errno устанавливается в EAGAIN (и ни одна из операций в sops не выполняется). Если значение аргумента timeout равно NULL, то semtimedop() ведёт себя аналогично semop(). Заметим, что если semtimeop() прерывается сигналом, то вызов завершается с ошибкой EINTR, а содержимое timeout не изменяется.

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
При успешном выполнении semop() и semtimedop() возвращается 0; иначе возвращается -1, а переменной errno присваивается номер ошибки.

ОШИБКИ
В случае возникновения ошибки errno может принимать следующие значения:

E2BIG
    Значение аргумента nsops больше SEMOPM, максимального количества операций, которое может выполнить один системный вызов.

EACCES
    Вызывающий процесс не имеет прав, требуемых для выполнения указанных операций над семафорами, и не имеет мандата CAP_IPC_OWNER.

EAGAIN
    Операция не может быть выполнена немедленно и, либо IPC_NOWAIT был указан в sem_flg, либо истекло время лимита, определённое в timeout.

EFAULT
    Адрес, указанный в sops или timeout, не доступен.

EFBIG
    Для некоторых операций значение sem_num меньше нуля или больше или равно количеству семафоров в наборе.

EIDRM
    Набор семафоров был удалён.

EINTR
    Нить, находясь в режиме ожидания, получила сигнал; смотрите signal(7).

EINVAL
    Набор семафоров не существует, или значение semid меньше нуля, или nsops имеет не положительное значение.

ENOMEM
    Для некоторых операций в поле sem_flg задан флаг SEM_UNDO, и система не может выделить достаточно памяти для структуры откатов.

ERANGE
    Для некоторых операций sem_op+semval больше чем SEMVMX, максимального значения semval (зависит от реализации).

ВЕРСИИ
Вызов semtimedop() впервые появился в Linux 2.5.52, а затем был перенесён в ядро версии 2.4.22. Поддержка в glibc для semtimedop() впервые появилась в версии 2.3.3.

СООТВЕТСТВИЕ СТАНДАРТАМ
SVr4, POSIX.1-2001.

ЗАМЕЧАНИЯ
Включение файлов <sys/types.h> и <sys/ipc.h> не требуется в Linux или любых версий POSIX. Однако, некоторые старые реализации требуют включения данных заголовочных файлов, и это также требуется по SVID. В приложениях, которые нужно перенести на такие старые системы, может потребоваться включить данных заголовочные файлы. Структуры процесса sem_undo не наследуются потомками, созданными через fork(2), но они наследуются при выполнении системного вызова execve(2).

Вызов semop() никогда автоматически не перезапускается после прерывания обработчиком сигнала, независимо от установки флага SA_RESTART при настройке обработчика сигнала. Значение регулировки семафора (semadj) есть в каждом процессе. Это целое число — простой (отрицательный) счётчик всех операций над семафорами, для которых установлен флаг SEM_UNDO. В каждом процессе есть список значений semadj — по одному значению на каждый семафор, у которых установлен флаг SEM_UNDO. При завершении процесса. каждое из значений semadj семафора добавляется к соответствующему семафору, достигая таким образом эффекта выполнения операций процесса над семафорами (но смотрите раздел ДЕФЕКТЫ). Когда значение семафора явно устанавливается с помощью запроса SETVAL или SETALL вызовом semctl(2), то соответствующие значения semadj во всех процессах очищаются. Флаг CLONE_SYSVSEM clone(2) позволяет нескольким процессам совместно использовать список semadj; подробности смотрите в clone(2). Значения semval, sempid, semzcnt и semnct семафора можно получить с помощью соответствующих вызовов semctl(2).

  Ограничения семафоров
Ниже приведены лимиты ресурсов наборов семафоров, влияющие на вызов semop():

SEMOPM
    Максимальное количество операций, разрешённых для одного вызова semop(). До версии Linux 3.19, значение по умолчанию было 32. Начиная с Linux 3.19, значение по умолчанию равно 500. В Linux это ограничение можно прочитать и изменить через третье поле /proc/sys/kernel/sem. Замечание: это ограничение не должно превышать 1000, так как есть риск, что semop(2) завершится с ошибкой из-за фрагментации памяти ядра при выделении памяти при копировании массива sops.

SEMVMX
    Максимально допустимое значение semval: зависит от реализации (32767).

Реализация не накладывает существенных ограничений на максимальное значение (SEMAEM), на которое можно изменить значение семафора при выходе, максимальное количество системных структур откатываемых операций (SEMMNU) и максимальное количество элементов отката системных параметров на процесс.

ДЕФЕКТЫ
При завершении процесса его набор связанных структур semadj используется для отката выполненных действий над семафорами, для которых был установлен флаг SEM_UNDO. Это повышает сложность: если одно (или более) этих изменений семафоров привело бы в результате к попытке уменьшить значение семафора ниже нуля, что должно быть сделано в реализации? Одним из возможных решений была бы блокировка до тех пор, пока не выполнятся все изменения семафоров. Однако это нежелательно, так как это привело бы к блокированию процесса на неопределённый срок при его завершении. Другим вариантом является игнорирование сразу всех изменений семафоров (в некоторой степени, аналогично завершению с ошибкой, когда для операции с семафором указан IPC_NOWAIT). В Linux используется третий вариант: уменьшение значения семафора до тех пор, пока это возможно ( т.е. до нуля) и разрешение немедленного завершения процесса. В ядрах 2.6.x, где x <= 10, есть дефект, из-за которого при определённых обстоятельствах нить, ожидающая установления значения семафора равного нулю, не будет разбужен когда значение станет равным нулю. Этот дефект исправлен в ядре 2.6.11.

ПРИМЕР
В следующем фрагменте кода используется semop() для атомарного ожидания момента, когда значение семафора 0 станет равным нулю и последующего увеличения значения семафора на единицу.
    struct sembuf sops[2];
    int semid;
    /* код для установки semid не показан */
    sops[0].sem_num = 0;        /* применяем к семафору 0 */
    sops[0].sem_op = 0;         /* ждём значения, равного 0 */
    sops[0].sem_flg = 0;
    sops[1].sem_num = 0;        /* применяем к семафору 0 */
    sops[1].sem_op = 1;         /* увеличиваем значение на 1 */
    sops[1].sem_flg = 0;
    if (semop(semid, sops, 2) == -1) {
        perror("semop");
        exit(EXIT_FAILURE);
    }

СМОТРИТЕ ТАКЖЕ
clone(2), semctl(2), semget(2), sigaction(2), capabilities(7), sem_overview(7), svipc(7), time(7)