Unix - статьи

       

Семафоры


Семафоры широко используются как средство синхронизации потоков и процессов. В Unix-системах реализованы три типа семафоров – семафоры System V, семафоры POSIX и семафоры в разделяемой памяти. Поскольку статья посвящена System V IPC, мы рассмотрим семафоры System V. Подробное описание всех трех типов семафоров можно найти в [].

Состояние семафора определяется значением некоторой внутренней переменной и переданным ему параметром. В зависимости от этих значений семафор либо приостанавливает выполнение обратившегося к нему потока (до тех пор, пока другой поток не переведет семафор в другое состояние), либо изменяет значение внутренней переменной, разрешив потоку дальнейшее выполнение. Следующая функция иллюстрирует логику работы семафора в зависимости от значения переменной состояния (semvalue) и управляющей переменной sem_op.

void semaphore (int sem_op) { static int semvalue; // Внутренняя переменная if (sem_op != 0) { if (sem_op < 0) while (semvalue < ABS(sem_op)); semvalue += sem_op; } else while (semvalue != 0); }

Отрицательное значение sem_op соответствует операции проверки доступности ресурса и вызывает приостановку потока, если доступ к ресурсу заблокирован. Положительное значение сигнализирует о высвобождении ресурса. Приведенная выше функция semaphore() описывает поведение многозначного семафора (general semaphore). Именно такие семафоры используются в System V IPC. Перепишем клиент и сервер из предыдущего примера, заменив спин- блокировки семафорами (на диске вы найдете исходный текст сервера в файле semserv.c, а исходный текст клиента – в файле semcli.c. Все, что касается семафоров, определено в файле <sys/sem.h>. Программа-сервер создает семафоры с помощью вызова semid = semget(key, 2, 0666|IPC_CREAT);

Функция semget(2) похожа на msgget() и shmget(), но у нее есть дополнительный параметр – количество создаваемых семафоров. Дело в том, что многим процессам, использующим семафоры, требуется более одного семафора (тогда как блоки разделяемой памяти и очереди сообщений обычно существуют в единственном экземпляре). Определение уникального ключа для каждого из нескольких семафоров затруднительно, поэтому функция semget() позволяет несколько семафоров сразу. Нашему приложению понадобится два семафора. Нам понадобилось бы три семафора, если бы... Впрочем, это уже совсем другая история. Первый семафор указывает, должен ли сервер читать запись, сделанную клиентом, второй - должен ли клиент читать запись, сделанную сервером. Таким образом, мы приказываем semget() создать сразу два семафора. В случае успешного завершения функция semget() возвращает идентификатор нового массива семафоров.

Для определения состояния семафора используется структура sembuf. В структуре sembuf определено много полей (конкретный набор зависит от версии Unix-системы), из которых обязательными являются три:




  • short sem_num – номер семафора (в массиве), над которым выполняется операция (нумерация начинается с нуля).
  • short sem_op – число, изменяющее состояние семафора.
  • short sem_flg – дополнительные флаги.


Как и в приведенном выше схематическом примере работы семафора, отрицательное значение sem_op соответствует операции проверки доступности ресурса и вызывает приостановку потока, если ресурс недоступен. Положительное значение заставляет семафор высвободить ресурс (или приблизиться к этому). Указатель на массив структур sembuf (по структуре на семафор) передается как второй параметр функции semop(2), которая либо изменяет состояние семафора, либо приостанавливает вызывавший поток. Первый параметр этой функции – идентификатор, возвращенный semget(). В третьем параметре передается число записей в массиве sembuf. Вот как, например, мы указываем, что клиент может записывать данные в разделяемую область:

buf[1].sem_op = 1; semop(semid, (struct sembuf*) &buf[1], 1);

А эти строки приостановят сервер, пока клиент не изменит значение первого семафора:

buf[0].sem_op = -1; semop(semid, (struct sembuf*) &buf, 1);

Получая разрешение на доступ к разделяемой области, процесс производит чтение/запись, разрешает доступ другому процессу, запрещает доступ себе и приостанавливается. Удаление семафора выполняет с помощью функции semctl(), в которой, кроме прочего, нужно указывать число семафоров: semctl(semid, 2, IPC_RMID);

Скомпилируйте сервер под именем semserv, а клиент под именем semcli, (или командуйте make semdemo) запустите клиент и сервер. Вы увидите, что обмен данными выполняется гораздо быстрее, а процессор загружается гораздо меньше, чем в случае использования спин-блокировок.

При всем богатстве выбора средств взаимодействия между процессами в Unix/Linux, самыми популярными средствами были и остаются сокеты. Ими мы и займемся в следующий раз.


Содержание раздела