Уильям Стивенс - UNIX: взаимодействие процессов
Семафоры Posix могут быть именованными или неименованными (размещаемыми в памяти). Именованные семафоры всегда могут использоваться отдельными процессами, тогда как размещаемые в памяти должны для этого изначально планироваться как разделяемые между процессами. Эти типы семафоров также отличаются друг от друга по живучести: именованные семафоры обладают по меньшей мере живучестью ядра, тогда как размещаемые в памяти обладают живучестью процесса.
Задача производителей и потребителей является классическим примером для иллюстрации использования семафоров. В этой главе первое решение состояло из одного потока-производителя и одного потока-потребителя; второе решение имело нескольких производителей и одного потребителя, а последнее решение допускало одновременную работу и нескольких потребителей. Затем мы показали, что классическая задача двойной буферизации является частным случаем задачи производителей и потребителей с одним производителем и одним потребителем.
В этой главе было приведено три примера возможной реализации семафоров Posix. Первый пример был самым простым, в нем использовались каналы FIFO, а большая часть забот по синхронизации ложилась на ядро (функции read и write). Следующая реализация использовала отображение файлов в память (аналогично реализации очередей сообщений Posix из раздела 5.8), а также взаимное исключение и условную переменную (для синхронизации). Последняя реализация была основана на семафорах System V и представляла собой, по сути, удобный интерфейс для работы с ними.
Упражнения
1. Измените функции produce и consume из раздела 10.6 следующим образом. Поменяйте порядок двух вызовов Sem_wait в потребителе, чтобы возникла ситуация зависания (как описано в разделе 10.6). Затем добавьте вызов printf перед каждым Sem_wait, чтобы было ясно, какой из потоков ожидает изменения семафора. Добавьте еще один вызов printf после каждого Sem_wait, чтобы можно было определить, какой поток получил управление. Уменьшите количество буферов до двух, а затем откомпилируйте и выполните эту программу, чтобы убедиться, что она зависнет.
2. Предположим, что запущено четыре экземпляра программы, вызывающей функцию my_lock из листинга 10.10:
% lockpxsem & lockpxsem & lockpxsem & lockpxsem &
Каждый из четырех процессов запускается с значением initflag, равным 0, поэтому при вызове sem_open всегда указывается O_CREAT. Нормально ли это?
3. Что произойдет в предыдущем примере, если одна из четырех программ будет завершена после вызова my_lock, но перед вызовом my_unlock?
4. Что произошло бы с программой в листинге 10.22, если бы мы не инициализировали оба дескриптора значением –1?
5. Почему в листинге 10.22 мы сохраняем значение errno, а затем восстанавливаем его, вместо того чтобы написать просто:
if (sem->fd[0] >= 0) close(sem->fd[0]);
if (sem->fd[1] >= 0) close(sem->fd[1]);
6. Что произойдет, если два процесса вызовут нашу реализацию sem_open через FIFO (листинг 10.22) примерно одновременно, указывая флаг O_CREAT и начальное значение 5? Может ли канал быть инициализирован (неправильно) значением 10?
7. В связи с листингами 10.28 и 10.29 мы описали возможную ситуацию гонок в случае, если два процесса пытаются создать семафор примерно одновременно. Однако решение предыдущей задачи в листинге 10.22 не создавало ситуации гонок. Объясните это.
8. Стандарт Posix.1 указывает дополнительную возможность для функции semwait: она может прерываться перехватываемым сигналом и возвращать код EINTR. Напишите тестовую программу, которая определяла бы, есть ли такая возможность в вашей реализации.
Запустите эту тестовую программу с нашими реализациями, использующими FIFO (раздел 10.14), отображение в память (раздел 10.15) и семафоры System V (раздел 10.16).
9. Какая из трех реализаций sem_post этой главы является функцией типа async-signal-safe (табл. 5.1)?
10. Измените решение задачи о потребителе и производителе в разделе 10.6 так, чтобы для переменной mutex использовался тип pthread_mutex_t, а не семафор. Заметна ли разница в скорости работы программы?
11. Сравните быстродействие именованных семафоров (листинги 10.8 и 10.9) и размещаемых в памяти (листинг 10.11).
ГЛАВА 11
Семафоры System V
11.1.Введение
В главе 10 мы описывали различные виды семафоров, начав с:
■ бинарного семафора, который может принимать только два значения: 0 и 1. По своим свойствам такой семафор аналогичен взаимному исключению (глава 7), причем значение 0 для семафора соответствует блокированию ресурса, а 1 — освобождению.
Далее мы перешли к более сложному виду семафоров:
■ семафор-счетчик, значение которого лежит в диапазоне от 0 до некоторого ограничения, которое, согласно Posix, не должно быть меньше 32767. Они использовался для подсчета доступных ресурсов в задаче о производителях и потребителях, причем значение семафора соответствовало количеству доступных ресурсов.
Для обоих типов семафоров операция wait состояла в ожидании изменения значения семафора с нулевого на ненулевое и последующем уменьшении этого значения на 1. Операция post увеличивала значение семафора на 1, оповещая об этом все процессы, ожидавшие изменения значения семафора.
Для семафоров System V определен еще один уровень сложности:
■ набор семафоров-счетчиков — один или несколько семафоров, каждый из которых является счетчиком. На количество семафоров в наборе существует ограничение (обычно порядка 25 — раздел 11.7). Когда мы говорим о семафоре System V, мы подразумеваем именно набор семафоров-счетчиков, а когда говорим о семафоре Posix, подразумевается ровно один семафор-счетчик.
Для каждого набора семафоров ядро поддерживает следующую информационную структуру, определенную в файле <sys/sem.h>:
struct semid_ds {
struct ipc_perm sem_perm; /* разрешения на операции */
struct sem *sem_base; /*указатель на массив семафоров в наборе */
ushort sem_nsems; /* количество семафоров в наборе */
time_t sem_otime; /* время последнего вызова semop(); */
time_t sem_ctime; /* время создания последнего IPC_SET */
};
Структура ipc_perm была описана в разделе 3.3. Она содержит разрешения доступа для данного семафора.
Структура sem представляет собой внутреннюю структуру данных, используемую ядром для хранения набора значений семафора. Каждый элемент набора семафоров описывается так:
struct sem {
ushort_t semval; /* значение семафора, неотрицательно */
short sempid; /* PID последнего процесса, вызвавшего semop(), SETVAL, SETALL */
ushort_t semncnt; /* количество ожидающих того, что значение семафора превысит текущее */
ushort_t semzcnt; /* количество ожидающих того, что значение семафора станет равным 0*/
};
Обратите внимание, что sem_base представляет собой указатель на массив структур типа sem — по одному элементу массива на каждый семафор в наборе.
Помимо текущих значений всех семафоров набора в ядре хранятся еще три поля данных для каждого семафора: идентификатор процесса, изменившего значение семафора последним, количество процессов, ожидающих увеличения значения семафора, и количество процессов, ожидающих того, что значение семафора станет нулевым.
ПРИМЕЧАНИЕ
В стандарте Unix 98 данная структура не имеет имени. Приведенное выше имя (sem) взято из реализации System V.
Любой конкретный семафор в ядре мы можем воспринимать как структуру semid_ds, указывающую на массив структур sem. Если в наборе два элемента, мы получим картину, изображенную на рис. 11.1. На этом рисунке переменная sem_nsems имеет значение 2, а каждый из элементов набора идентифицируется индексом ([0] или [1]).
Рис. 11.1. Структуры данных ядра для набора семафоров из двух элементов
11.2. Функция semget
Функция semget создает набор семафоров или обеспечивает доступ к существующему.
#include <sys/sem.h>
int semget(key_t key, int nsems, int oflag);
/* Возвращает неотрицательный идентификатор в случае успешного завершения, –1 – в случае ошибки */
Эта функция возвращает целое значение, называемое идентификатором семафора, которое затем используется при вызове функций semop и semctl.
Аргумент nsems задает количество семафоров в наборе. Если мы не создаем новый набор, а устанавливаем доступ к существующему, этот аргумент может быть нулевым. Количество семафоров в уже созданном наборе изменить нельзя.
Аргумент oflag представляет собой комбинацию констант SEM_R и SEM_A из табл. 3.3. Здесь R обозначает Read (чтение), а А — Alter (изменение). К этим константам можно логически прибавить IPC_CREAT или IPC_CREAT | IPC_EXCL, о чем мы уже говорили в связи с рис. 3.2.
При создании нового семафора инициализируются следующие поля структуры semid_ds: