Уильям Стивенс - UNIX: взаимодействие процессов
Что произойдет, если мы вызовем функцию sem_open, возвращающую указатель на тип sem_t, а затем вызовем fork? В описании функции fork в стандарте Posix.1 говорится, что «все открытые родительским процессом семафоры будут открыты и в дочернем процессе». Это означает, что нижеследующий код верен:
sem_t *mutex; /* глобальный указатель, копируемый, при вызове fork() */
…
/* родительский процесс создает именованный семафор */
mutex = Sem_open(Px_ipc_name(NAME), O_CREAT | O_EXCL, FILE_MODE, 0);
if ((childpid = Fork()) == 0) {
/* дочерний процесс */
…
Sem_wait(mutex);
…
}
/* родительский процесс */
…
Sem_post(mutex);
…
ПРИМЕЧАНИЕ
Причина, по которой следует аккуратно относиться к передаче семафоров при порождении процессов, заключается в том, что состояние семафора может храниться в переменной типа sem_t, но для его работы может требоваться и другая информация (например, дескрипторы файлов). В следующей главе мы увидим, что семафоры System V однозначно определяются их целочисленными идентификаторами, возвращаемыми функцией semget. Любой процесс, которому известен идентификатор, может получить доступ к семафору. Вся информация о семафоре System V хранится в ядре, а целочисленный идентификатор просто указывает номер семафора ядру.
10.13. Ограничения на семафоры
Стандартом Posix определены два ограничения на семафоры:
■ SEM_NSEMS_MAX — максимальное количество одновременно открытых семафоров для одного процесса (Posix требует, чтобы это значение было не менее 256);
■ SEM_VALUE_MAX — максимальное значение семафора (Posix требует, чтобы оно было не меньше 32767).
Две эти константы обычно определены в заголовочном файле <unistd.h> и могут быть получены во время выполнения вызовом sysconf, как мы показываем ниже.
Пример: программа semsysconf
Программа в листинге 10.20 вызывает sysconf и выводит два ограничения на семафоры, зависящие от конкретной реализации.
Листинг 10.20. Вызов sysconf для получения ограничений на семафоры//pxsem/semsysconf.с
1 #include "unpipc.h"
2 int
3 main(int argc, char **argv)
4 {
5 printf("SEM_NSEMS_MAX = %ld, SEM_VALUE_MAX = %ldn",
6 Sysconf(_SC_SEM_NSEMS_MAX), Sysconf(_SC_SEM_VALUE_MAX));
7 exit(0);
8 }
При запуске этой программы в наших двух тестовых системах получим следующий результат:
solaris % semsysconf
SEMS_NSEMS_MAX = 2147483647, SEM_VALUE_MAX = 2147483647
alpha % semsysconf
SEMS_NSEMS_MAX = 256, SEM_VALUE_MAX = 32767
10.14. Реализация с использованием FIFO
Займемся реализацией именованных семафоров Posix с помощью каналов FIFO. Именованный семафор реализуется как канал FIFO с конкретным именем. Неотрицательное количество байтов в канале соответствует текущему значению семафора. Функция sem_post помещает 1 байт в канал, a sem_wait считывает его оттуда (приостанавливая выполнение процесса, если канал пуст, а именно этого мы и хотим). Функция sem_open создает канал FIFO, если указан флаг O_CREAT; открывает его дважды (один раз на запись, другой — на чтение) и при создании нового канала FIFO помещает в него некоторое количество байтов, указанное в качестве начального значения.
ПРИМЕЧАНИЕ
Этот и последующие разделы данной главы содержат усложненный материал, который можно при первом чтении пропустить.
Приведем текст нашего заголовочного файла semaphore.h, определяющего фундаментальный тип sem_t (листинг 10.21).
Листинг 10.21. Заголовочный файл semaphore.h//my_pxsem_fifo/semaphore.h
1 /* фундаментальный тип */
2 typedef struct {
3 int sem_fd[2]; /* два дескриптора fd: [0] для чтения, [1] для записи */
4 int sem_magic; /* магическое число */
5 } mysem_t;
6 #define SEM_MAGIC 0x89674523
7 #ifdef SEM_FAILED
8 #undef SEM_FAILED
9 #define SEM_FAILED ((mysem_t *)(-1)) /* чтобы компилятор не выдавал предупреждений*/
10 #endif
Тип данных sem_t1-5 Новая структура данных содержит два дескриптора, один из которых предназначен для чтения из FIFO, а другой — для записи. Для единообразия мы храним оба дескриптора в массиве из двух элементов, в котором первый дескриптор всегда открыт на чтение, а второй — на запись.
Поле sem_magiс содержит значение SEM_MAGIC, если структура проинициализирована. Это значение проверяется всеми функциями, которым передается указатель на тип sem_t, чтобы гарантировать, что передан был действительно указатель на заранее инициализированную структуру, а не на произвольную область памяти. При закрытии семафора этому полю присваивается значение 0. Этот метод хотя и не совершенен, но дает возможность обнаружить некоторые ошибки при написании программ.
Функция sem_open
В листинге 10.22 приведен текст функции sem_open, которая создает новый семафор или открывает существующий.
Листинг 10.22. Функция sem_open//my_pxsem_fifo/sem_open.с
1 #include "unpipc.h"
2 #include "semaphore.h"
3 #include <stdarg.h> /* для произвольного списка аргументов */
4 mysem_t *
5 mysem_open(const char *pathname, int oflag, …)
6 {
7 int i, flags, save_errno;
8 char c;
9 mode_t mode;
10 va_list ap;
11 mysem_t *sem;
12 unsigned int value;
13 if (oflag & O_CREAT) {
14 va_start(ap, oflag); /* ар инициализируется последним аргументом */
15 mode = va_arg(ap, va_mode_t);
16 value = va_arg(ap, unsigned int);
17 va_end(ap);
18 if (mkfifo(pathname, mode) < 0) {
19 if (errno == EEXIST && (oflag & O_EXCL) == 0)
20 oflag &= ~O_CREAT; /* уже существует, OK */
21 else
22 return(SEM_FAILED);
23 }
24 }
25 if ((sem = malloc(sizeof(mysem_t))) == NULL)
26 return(SEM_FAILED);
27 sem->sem_fd[0] = sem->sem_fd[1] = –1;
28 if ((sem->sem_fd[0] = open(pathname, O_RDONLY | O_NONBLOCK)) < 0)
29 goto error;
30 if ((sem->sem_fd[1] = open(pathname, O_WRONLY | O_NONBLOCK)) < 0)
31 goto error;
32 /* отключение неблокируемого режима для sem_fd[0] */
33 if ((flags = fcntl(sem->sem_fd[0], F_GETFL, 0)) < 0)
34 goto error;
35 flags &= ~O_NONBLOCK;
36 if (fcntl(sem->sem_fd[0], F_SETFL, flags) < 0)
37 goto error;
38 if (oflag & O_CREAT) { /* инициализация семафора */
39 for (i = 0; i < value; i++)
40 if (write(sem->sem_fd[1], &c, 1) != 1)
41 goto error;
42 }
43 sem->sem_magic = SEM_MAGIC;
44 return(sem);
45 error:
46 save_errno = errno;
47 if (oflag & O_CREAT)
48 unlink(pathname); /* если мы создали FIFO */
49 close(sem->sem_fd[0]); /* игнорируем ошибку */
50 close(sem->sem_fd[1]); /* игнорируем ошибку */
51 free(sem);
52 errno = save_errno;
53 return(SEM_FAILED);
54 }
Создание нового sсемафора13-17 Если при вызове указан флаг O_CREAT, должно быть указано четыре аргумента, а не два. Мы вызываем va_start, после чего переменная ар указывает на последний явно указанный аргумент (oflag). Затем мы используем ар и функцию va_arg для получения значений третьего и четвертого аргументов. Работу со списком аргументов переменной длины и использование нашего типа va_mode_t мы обсуждали в связи с листингом 5.17.
Создание нового канала FIFO18-23 Создается новый канал FIFO, имя которого было указано при вызове функции. Как мы отмечали в разделе 4.6, эта функция возвращает ошибку EEXIST, если канал уже существует. Если при вызове sem_open флаг O_EXCL не был указан, мы пропускаем эту ошибку; но нам не нужно будет инициализировать этот канал, так что мы при этом сбрасываем флаг O_CREAT.
Выделение памяти под тип sem_t и открытие FIFO на чтение и запись25-37 Мы выделяем место для типа sem_t, который содержит два дескриптора. Затем мы дважды открываем канал FIFO: один раз только на чтение, а другой — только на запись. При этом мы не хотим блокирования при вызове open, поэтому указываем флаги O_NONBLOCK при открытии очереди только для чтения (вспомните табл. 4.1). Мы также указываем флаг O_NONBLOCK при открытии канала на запись, но это предназначено для обнаружения переполнения (на тот случай, если мы попытаемся записать больше, чем позволяет PIPE_BUF). После открытия канала мы отключаем неблокируемый режим для дескриптора, открытого на чтение.
Инициализация значения созданного семафора38-42 Если мы создали семафор, его нужно проинициализировать, записав в канал FIFO value байтов. Если указанное при вызове значение value превышает определенное реализацией ограничение PIPE_BUF, вызов write после переполнения FIFO вернет ошибку с кодом EAGAIN.