Московский физико-технический институт
Опубликован: 16.09.2004 | Доступ: свободный | Студентов: 7351 / 1504 | Оценка: 4.44 / 4.28 | Длительность: 15:33:00
Лекция 5:

Семафоры в UNIX как средство синхронизации процессов

< Лекция 4 || Лекция 5: 123 || Лекция 6 >

Выполнение операций над семафорами. Системный вызов semop()

Для выполнения операций A, D и Z над семафорами из массива используется системный вызов semop() , обладающий довольно сложной семантикой. Разработчики System V IPC явно перегрузили этот вызов, применяя его не только для выполнения всех трех операций, но еще и для нескольких семафоров в массиве IPC-семафоров одновременно. Для правильного использования этого вызова необходимо выполнить следующие действия:

  1. Определиться, для каких семафоров из массива предстоит выполнить операции. Необходимо иметь в виду, что все операции реально совершаются только перед успешным возвращением из системного вызова, т.е. если вы хотите выполнить операции A(S1,5) и Z(S2) в одном вызове и оказалось, что S2 != 0, то значение семафора S1 не будет изменено до тех пор, пока значение S2 не станет равным 0. Порядок выполнения операций в случае, когда процесс не переходит в состояние ожидание, не определен. Так, например, при одновременном выполнении операций A(S1,1) и D(S2,1) в случае S2 > 1 неизвестно, что произойдет раньше – уменьшится значение семафора S2 или увеличится значение семафора S1. Если порядок для вас важен, лучше применить несколько вызовов вместо одного.
  2. После того как вы определились с количеством семафоров и совершаемыми операциями, необходимо завести в программе массив из элементов типа struct sembuf с размерностью, равной определенному количеству семафоров (если операция совершается только над одним семафором, можно, естественно, обойтись просто переменной). Каждый элемент этого массива будет соответствовать операции над одним семафором.
  3. Заполнить элементы массива. В поле sem_flg каждого элемента нужно занести значение 0 (другие значения флагов в семинарах мы рассматривать не будем). В поля sem_num и sem_op следует занести номера семафоров в массиве IPC семафоров и соответствующие коды операций. Семафоры нумеруются, начиная с 0. Если у вас в массиве всего один семафор, то он будет иметь номер 0. Операции кодируются так:
    • для выполнения операции A(S,n) значение поля sem_op должно быть равно n ;
    • для выполнения операции D(S,n) значение поля sem_op должно быть равно –n ;
    • для выполнения операции Z(S) значение поля sem_op должно быть равно 0.
  4. В качестве второго параметра системного вызова semop() указать адрес заполненного массива, а в качестве третьего параметра – ранее определенное количество семафоров, над которыми совершаются операции.

Системный вызов semop()

Прототип системного вызова

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, 
          int nsops);

Описание системного вызова

Системный вызов semop предназначен для выполнения операций A, D и Z (см. описание операций над семафорами из массива IPC семафоров – раздел "Создание массива семафоров или доступ к уже существующему. Системный вызов semget() " этого семинара). Данное описание не является полным описанием системного вызова, а ограничивается рамками текущего курса. Для полного описания обращайтесь к UNIX Manual.

Параметр semid является дескриптором System V IPC для набора семафоров, т. е. значением, которое вернул системный вызов semget() при создании набора семафоров или при его поиске по ключу.

Каждый из nsops элементов массива, на который указывает параметр sops, определяет операцию, которая должна быть совершена над каким-либо семафором из массива IPC семафоров, и имеет тип структуры struct sembuf, в которую входят следующие переменные:

  • short sem_num — номер семафора в массиве IPC семафоров (нумеруются, начиная с 0 );
  • short sem_op — выполняемая операция;
  • short sem_flg — флаги для выполнения операции. В нашем курсе всегда будем считать эту переменную равной 0.

Значение элемента структуры sem_op определяется следующим образом:

  • для выполнения операции A(S,n) значение должно быть равно n ;
  • для выполнения операции D(S,n) значение должно быть равно -n ;
  • для выполнения операции Z(S) значение должно быть равно 0.

Семантика системного вызова подразумевает, что все операции будут в реальности выполнены над семафорами только перед успешным возвращением из системного вызова. Если при выполнении операций D или Z процесс перешел в состояние ожидания, то он может быть выявен из этого состояния при возникновении следующих форс-мажорных ситуаций:

  • массив семафоров был удален из системы;
  • процесс получил сигнал, который должен быть обработан.

В этом случае происходит возврат из системного вызова с констатацией ошибочной ситуации.

Возвращаемое значение

Системный вызов возвращает значение 0 при нормальном завершении и значение -1 при возникновении ошибки.

Прогон примера с использованием семафора

Для иллюстрации сказанного рассмотрим простейшие программы, синхронизирующие свои действия с помощью семафоров

/* Программа 08-1a.c для иллюстрации работы с 
семафорами */ 
/* Эта программа получает доступ к одному системному семафору,
ждет, пока его значение не станет больше или равным 1 
после запусков программы 08-1b.c,а затем уменьшает его на 1*/ 
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
int main()
{
	int semid; /* IPC дескриптор для массива IPC 
семафоров */
	char pathname[] = "08-1a.c"; /* Имя файла, 
		использующееся для генерации ключа. Файл с таким 
		именем должен существовать в текущей директории */
	key_t key; /* IPC ключ */ 
	struct sembuf mybuf; /* Структура для задания 
		операции над семафором */
	/* Генерируем IPC-ключ из имени файла 08-1a.c в текущей
	директории и номера экземпляра массива семафоров 0 */
	if((key = ftok(pathname,0)) < 0){
		printf("Can\'t generate key\n");
		exit(-1);
	}
	/* Пытаемся получить доступ по ключу к массиву 
семафоров, если он существует, или создать его из одного 
семафора, если его еще не существует, с правами доступа 
read & write для всех пользователей */ 
	if((semid = semget(key, 1, 0666 | IPC_CREAT)) < 0){
		printf("Can\'t get semid\n");
		exit(-1);
	}
	/* Выполним операцию D(semid1,1) для нашего массива 
	семафоров. Для этого сначала заполним нашу структуру. 
	Флаг, как обычно, полагаем равным 0. Наш массив семафоров 
	состоит из одного семафора с номером 0. Код операции -1.*/
	mybuf.sem_op = -1;
	mybuf.sem_flg = 0;
	mybuf.sem_num = 0;
	if(semop(semid, &mybuf, 1) < 0){
		printf("Can\'t wait for condition\n");
		exit(-1);
	}
	printf("Condition is present\n");
	return 0;
}
Листинг 8.1. Программа 08-1a.c для иллюстрации работы с семафорами
/* Программа 08-1b.c для иллюстрации работы с 
семафорами */ 
/* Эта программа получает доступ к одному системному семафору
и увеличивает его на 1*/ 
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
int main()
{
	int semid; /* IPC дескриптор для массива IPC 
		семафоров */
	char pathname[] = "08-1a.c"; /* Имя файла, 
		использующееся для генерации ключа. Файл с таким 
		именем должен существовать в текущей директории */
	key_t key; /* IPC ключ */ 
	struct sembuf mybuf; /* Структура для задания операции 
	над семафором */
	/* Генерируем IPC ключ из имени файла 08-1a.c в текущей 
	директории и номера экземпляра массива семафоров 0 */
	if((key = ftok(pathname,0)) < 0){
		printf("Can\'t generate key\n");
		exit(-1);
	}
	/* Пытаемся получить доступ по ключу к массиву 
семафоров, если он существует, или создать его из 
одного семафора, если его еще не существует, с правами доступа
read & write для всех пользователей */ 
	if((semid = semget(key, 1, 0666 | IPC_CREAT)) < 0){
		printf("Can\'t get semid\n");
		exit(-1);
	}
	/* Выполним операцию A(semid1,1) для нашего массива 
	семафоров. Для этого сначала заполним нашу структуру. 
	Флаг, как обычно, 	полагаем равным 0. Наш массив 
	семафоров состоит из одного семафора с номером 0. 
	Код операции 1.*/
	mybuf.sem_op = 1;
	mybuf.sem_flg = 0;
	mybuf.sem_num = 0;
	if(semop(semid, &mybuf, 1) < 0){
		printf("Can\'t wait for condition\n");
		exit(-1);
	}
	printf("Condition is set\n");
	return 0;
}
Листинг 8.1b. Программа 08-1b.c для иллюстрации работы с семафорами

Первая программа выполняет над семафором S операцию D(S,1) , вторая программа выполняет над тем же семафором операцию A(S,1) . Если семафора в системе не существует, любая программа создает его перед выполнением операции. Поскольку при создании семафор всегда инициируется 0, то программа 1 может работать без блокировки только после запуска программы 2. Наберите программы, сохраните под именами 08-1а.с и 08-1b.c cоответственно, откомпилируйте и проверьте правильность их поведения.

Изменение предыдущего примера

Измените программы из предыдущего раздела так, чтобы первая программа могла работать без блокировки после не менее 5 запусков второй программы.

< Лекция 4 || Лекция 5: 123 || Лекция 6 >