semop - операции с семафорами System V
semop(2)
операции с семафорами System V
Other Alias
semtimedop
ОБЗОР
#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.
СООТВЕТСТВИЕ СТАНДАРТАМ
POSIX.1-2001, POSIX.1-2008, SVr4.
ЗАМЕЧАНИЯ
Включение файлов
<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);
}