Уильям Стивенс - UNIX: взаимодействие процессов
Требования к аргументу пате приведены в разделе 2.2.
Аргумент oflag может принимать значения 0, O_CREAT, O_CREAT | O_EXCL, как описано в разделе 2.3. Если указано значение O_CREAT, третий и четвертый аргументы функции являются обязательными. Аргумент mode указывает биты разрешений доступа (табл. 2.3), a value указывает начальное значение семафора. Это значение не может превышать константу SEM_VALUE_MAX, которая, согласно Posix, должна быть не менее 32767. Бинарные семафоры обычно устанавливаются в 1, тогда как семафоры-счетчики чаще инициализируются большими величинами.
При указании флага O_CREAT (без O_EXCL) семафор инициализируется только в том случае, если он еще не существует. Если семафор существует, ошибки не возникнет. Ошибка будет возвращена только в том случае, если указаны флаги O_CREAT | O_EXCL.
Возвращаемое значение представляет собой указатель на тип sem_t. Этот указатель впоследствии передается в качестве аргумента функциям sem_close, sem_wait, sem_trywait, sem_post и sem_getvalue.
ПРИМЕЧАНИЕ
Кажется странным возвращать SEM_FAILED в случае ошибки — нулевой указатель был бы более уместен. В ранних версиях стандарта Posix указывалось возвращаемое значение –1, и во многих реализациях константа SEM_FAILED определена как
#define SEM_FAILED ((sem_t *)(-1))
В Posix.1 мало говорится о битах разрешений, связываемых с семафором при его создании и открытии. Вспомните, мы говорили в связи с табл. 2.2 о том, что для именованных семафоров не нужно даже указывать флаги O_RDONLY, O_WRONLY и O_RDWR. В системах, на которых мы тестируем все программы этой книги (Digital Unix 4.0B и Solaris 2.6), для работы с семафором (его открытия) необходимо иметь к нему доступ как на чтение, так и на запись. Причина, скорее всего, в том, что обе операции, выполняемые с семафором (post и wait), состоят из считывания текущего значения и последующего его изменения. Отсутствие доступа на чтение или запись в этих реализациях приводит к возвращению функцией sem_open ошибки EACCESS ("Permission denied").
Открыв семафор с помощью sem_open, можно потом закрыть его, вызвав sem_close:
#include <semaphore.h>
int sem_close(sem_t *sem);
/* Возвращает 0 в случае успешного завершения. –1 – в случае ошибки */
Операция закрытия выполняется автоматически при завершении процесса для всех семафоров, которые были им открыты. Автоматическое закрытие осуществляется как при добровольном завершении работы (вызове exit или _exit), так и при принудительном (с помощью сигнала).
Закрытие семафора не удаляет его из системы. Именованные семафоры Posix обладают по меньшей мере живучестью ядра. Значение семафора сохраняется, даже если ни один процесс не держит его открытым.
Именованный семафор удаляется из системы вызовом sem_unlink:
#include <semaphore.h>
int sem_unlink(const char *name);
/* Возвращает 0 в случае успешного завершения, –1 – в случае ошибки */
Для каждого семафора ведется подсчет процессов, в которых он является открытым (как и для файлов), и функция sem_unlink действует аналогично unlink для файлов: объект пате может быть удален из файловой системы, даже если он открыт какими-либо процессами, но реальное удаление семафора не будет осуществлено до тех пор, пока он не будет окончательно закрыт.
10.3. Функции sem_wait и sem_trywait
Функция sem_wait проверяет значение заданного семафора на положительность, уменьшает его на единицу и немедленно возвращает управление процессу. Если значение семафора при вызове функции равно нулю, процесс приостанавливается, до тех пор пока оно снова не станет больше нуля, после чего значение семафора будет уменьшено на единицу и произойдет возврат из функции. Ранее мы отметили, что операция «проверка и уменьшение» должна быть атомарной по отношению к другим потокам, работающим с этим семафором:
#include <semaphore.h>
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
/* Обе функции возвращают 0 в случае успешного завершения. –1 – в случае ошибки */
Разница между sem_wait и sem_trywait заключается в том, что последняя не приостанавливает выполнение процесса, если значение семафора равно нулю, а просто немедленно возвращает ошибку EAGAIN.
Возврат из функции sem_wait может произойти преждевременно, если будет получен сигнал. При этом возвращается ошибка с кодом EINTR.
10.4. Функции sem_post и sem_getvalue
После завершения работы с семафором поток вызывает sem_post. Как мы уже говорили в разделе 10.1, этот вызов увеличивает значение семафора на единицу и возобновляет выполнение любых потоков, ожидающих изменения значения семафора:
#include <semaphore.h>
int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem, int *valp);
/* Обе функции возвращают 0 в случае успешного завершения. –1 – в случае ошибки */
Функция sem_getvalue возвращает текущее значение семафора, помещая его в целочисленную переменную, на которую указывает valp. Если семафор заблокирован, возвращается либо 0, либо отрицательное число, модуль которого соответствует количеству потоков, ожидающих разблокирования семафора.
Теперь мы ясно видим отличия семафоров от взаимных исключений и условных переменных. Прежде всего взаимное исключение может быть разблокировано только заблокировавшим его потоком. Для семафоров такого ограничения нет: один из потоков может ожидать изменения значения семафора, чтобы потом уменьшить его с 1 до 0 (действие аналогично блокированию семафора), а другой поток может изменить значение семафора с 0 до 1, что аналогично разблокированию семафора.
Далее, поскольку любой семафор имеет некоторое значение, увеличиваемое операцией post и уменьшаемое операцией wait, поток может изменить его значение (например, увеличить с 0 до 1), даже если нет потоков, ожидающих его изменения. Если же поток вызывает pthread_cond_signal в отсутствие заблокированных при вызове pthread_cond_wait потоков, сигнал просто теряется.
Наконец, среди всех функций, работающих со средствами синхронизации — взаимными исключениями, условными переменными, блокировками чтения-записи и семафорами, только одна может быть вызвана из обработчика сигналов: sem_post.
ПРИМЕЧАНИЕ
Не следует рассматривать приведенный выше текст как доводы в пользу семафоров. Все средства синхронизации, обсуждаемые в этой книге — взаимные исключения, условные переменные, блокировки чтения-записи, семафоры и блокировка fcntl, обладают своими преимуществами и недостатками. Выбирать средства синхронизации для приложения следует с учетом их многочисленных особенностей. Из нашего сравнительного описания можно сделать вывод, что взаимные исключения больше приспособлены для блокировки, условные переменные — для ожидания, а семафоры — для того и другого, и последнее может привести к излишнему усложнению текста программы.
10.5. Простые примеры
В этом разделе мы напишем несколько простых программ, работающих с именованными семафорами Posix. Эти программы помогут нам узнать особенности функционирования и реализации семафоров. Поскольку именованные семафоры Posix обладают по крайней мере живучестью ядра, для работы с ними мы можем использовать отдельные программы.
Программа semcreate
В листинге 10.3 приведен текст программы, создающей именованный семафор. При вызове программы можно указать параметр –е, обеспечивающий исключающее создание (если семафор уже существует, будет выведено сообщение об ошибке), а параметр –i с числовым аргументом позволяет задать начальное значение семафора, отличное от 1.
Листинг 10.3.[1] Создание именованного семафора//pxsem/semcreate.c
1 #include "unpipc.h"
2 int
3 main(int argc, char **argv)
4 {
5 int с, flags;
6 sem_t *sem;
7 unsigned int value;
8 flags = O_RDWR | O_CREAT;
9 value = 1;
10 while ((c = Getopt(argc, argv, "ei:")) != –1) {
11 switch (c) {
12 case 'e':
13 flags |= O_EXCL;
14 break;
15 case 'i':
16 value = atoi(optarg);
17 break;
18 }
19 }
20 if (optind != argc – 1)
21 err_quit("usage: semcreate [ –e ] [ –i initialvalue ] <name>");
22 sem = Sem_open(argv[optind], flags, FILE_MODE, value);
23 Sem_close(sem);
24 exit(0);
25 }
Создание семафора22 Поскольку мы всегда указываем флаг O_CREAT, нам приходится вызывать sem_open с четырьмя аргументами. Последние два используются только в том случае, если семафор еще не существует.
Закрытие семафора23 Мы вызываем sem_close, хотя, если бы мы не сделали этот вызов, семафор все равно закрылся бы автоматически при завершении процесса и ресурсы системы были бы высвобождены.