156
•никакой другой процесс не может получить доступ к промежуточному сос-
тоянию набора семафоров, когда часть операций из массива уже выполнилась, а другая еще не успела.
Сама ОС выполняет операции из массива семафоров - по очереди, причем порядок их обработки - не оговаривается.
Если очередная операция не может быть выполнена, то:
•эффект предыдущих операций аннулируется;
•вызов функции semop(...) приостанавливается (операция с блокировкой, когда
sem_flg=0) или немедленно завершается неудачей, когда выполняется операция без блокировки.
Замечание
В случае неудачного завершения вызова semop(...) значения всех семафоров в наборе останутся неизменными.
Следующий пример демонстрирует задание значений элементам структуры struct sembuf sbuf[2]:
sbuf [0].sem_num = 1; |
// Относится к первому семафору |
sbuf [0].sem_flg = 0; |
// С блокировкой |
sbuf [0].sem_op = -2; |
// Операция D(S,2) |
sbuf [1].sem_num = 0; |
// Относится к нулевому семафору |
sbuf [1].sem_flg = IPC_NOWAIT; // Без блокировки |
|
sbuf [1].sem_op = 0; |
// Операция Z(S) |
С целью закрепления изученного материала, рассмотрим пример синхронизации двух асинхронно запускаемых программ, каждая из которых реализуется в виде одного процесса и использует набор из одного семафора.
Первая программа, текст которой представлен на листинге 4.1:
•создает ключ доступа для канала 1, используя, в качестве имени файла, директорию /home/upk;
•создает или подключается к набору из одного семафора;
•пытается уменьшить его значение на 1, используя флаг блокировки;
•когда операция успешно выполнится — заканчивает работу.
Вторая программа, текст которой представлен на листинге 4.2:
•создает ключ доступа для канала 1, используя, в качестве имени файла, директорию /home/upk;
•создает или подключается к набору из одного семафора;
•увеличивает значение семафора на 1, - операция без блокировки;
•когда операция успешно выполнится — заканчивает работу.
157
Листринг 4.1. Текст программы, уменьшающей значение семафора
/* Эта программа lab10.1 получает доступ к одному системному семафору и ждет, пока его значение не станет больше или равным 1, а затем уменьшает его на 1*/
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <stdio.h> #include <stdlib.h>
int main()
{
puts("Запущена программа: lab10.1");
/* IPC дескриптор для массива IPC семафоров */ int semid;
/* Имя файла, использующееся для генерации ключа.
*Файл с таким именем должен существовать где-либо */ char pathname[] = "/home/upk";
/* IPC ключ */ key_t key;
/* Структура для задания операции над семафором */ struct sembuf mybuf;
/* Генерируем IPC-ключ из имени файла
*и номера экземпляра массива семафоров 0 */
if((key = ftok(pathname, 1)) < 0){
perror("lab10.1 - Немогу сгенерировать ключ key:"); exit(-1);
}
/* Пытаемся получить доступ по ключу к массиву семафоров,
*если он существует, или создать его из одного семафора,
*если его еще не существует, с правами доступа
*read & write для всех пользователей */
if((semid = semget(key, 1, 0666 | IPC_CREAT)) < 0){ perror("lab10.1 - Не могу получит semid:"); exit(-1);
} |
|
system("ipcs -s"); |
// Вызов утилиты ipcs |
/* Выполним операцию D(semid1,1) для нашего массива семафоров.
*Для этого сначала заполним нашу структуру.
*Флаг, как обычно, полагаем равным 0.
*Наш массив семафоров состоит:
*из одного семафора с номером 0. Код операции -1.*/ mybuf.sem_op = -1;
mybuf.sem_flg = 0; mybuf.sem_num = 0;
puts("lab10.1 - Начинаю выполнять системный вызов: semop(...)..."); if(semop(semid, &mybuf, 1) < 0){
perror("lab10.1 - Не могу выполнить semop(...):"); exit(-1);
}
puts("lab10.1 - Выполнил операцию D(semid1,1)..."); puts("lab10.1 - завершила работу...");
return 0;
}
158
Листринг 4.2. Текст программы, увеличивающей значение семафора
/* Эта программа lab10.2 получает доступ к одному системному семафору и увеличивает его на 1*/
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <stdio.h> #include <stdlib.h>
int main()
{
puts("Запущена программа: lab10.2");
/* IPC дескриптор для массива IPC семафоров */ int semid;
/* Имя файла, использующееся для генерации ключа.
* Файл с таким именем должен существовать где-либо */ char pathname[] = "/home/upk";
/* IPC ключ */ key_t key;
/* Структура для задания операции над семафором */ struct sembuf mybuf;
/* Генерируем IPC-ключ из имени файла
*и номера экземпляра массива семафоров 0 */ if((key = ftok(pathname, 1)) < 0){
perror("lab10.2 - Немогу сгенерировать ключ key:"); exit(-1);
}
/* Пытаемся получить доступ по ключу к массиву семафоров,
*если он существует, или создать его из одного семафора,
*если его еще не существует, с правами доступа
*read & write для всех пользователей */
if((semid = semget(key, 1, 0666 | IPC_CREAT)) < 0){ perror("lab10.2 - Не могу получит semid:"); exit(-1);
}
/* Выполним операцию A(semid1,1) для нашего массива семафоров. Для этого сначала заполним нашу структуру. Флаг, как обычно, полагаем равным 0. Наш массив семафоров состоит из одного семафора с номером 0. Код операции 1.*/
mybuf.sem_op = 1; mybuf.sem_flg = 0; mybuf.sem_num = 0;
puts("lab10.2 - Начинаю выполнять системный вызов: semop(...)..."); if(semop(semid, &mybuf, 1) < 0){
perror("lab10.2 - Не могу выполнить semop(...):"); exit(-1);
}
puts("lab10.2: Выполнил операцию A(semid1,1)..."); puts("lab10.2 - завершила работу...");
return 0;
}
Замечание
Очевидно, результат работы этих программ зависит от порядка их запуска.
159
Последний, изучаемый в данной теме системный вызов semctl(...), предназначен для управления набором семафоров. Он может иметь три или четыре аргумен-та:
int semctl (int semid, int semnum, int cmd, [union semun arg]);
где semid - идентификатор набора семафоров;
semnum - номер семафора в наборе, определяющий объект, над которым выполняется управляющее действие, задаваемое значением третьего аргумента cmd; если объектом является набор, значение semnum — игнорируется;
cmd - набор команд выполняемых на семафорами; для некоторых команд используется четвертый аргумент arg, имеющий вид:
union semun { |
|
|
|
|
int |
val; |
// Значение |
для SETVAL |
|
struct semid_ds |
*buf; |
// |
Буфер |
для IPC_STAT, IPC_SET |
unsigned short |
*array; |
// |
Массив |
для GETALL, SETALL |
} arg; |
|
|
|
|
Полный перечень значений аргумента cmd следует изучать по руководству: man semctl.
Далее, рассмотрим наиболее важные значения cmd.
Одиночные операции над семафором:
GETVAL - получить значение семафора и выдать его в качестве результата; SETVAL - установить значение семафора равным arg.val.
val = semctl (semid, semnum, GETVAL);
arg.val = ...;
if (semctl (semid, semnum, SETVAL, arg) == -1) ...;
Групповые операции над набором семафоров:
•GETALL - прочитать значения всех семафоров набора и поместить их в массив arg.array;
•SETALL - установить значения всех семафоров набора равными значениям элементов массива.
arg.array = (unsigned short *) malloc (nsems * sizeof (unsigned short)); err = semctl (semid, 0, GETALL, arg);
for (i = 0; i < nsems; i++) arg.array [i] = ...; err = semctl (semid, 0, SETALL, arg);
Информационные операции:
•GETPID - узнать PID процесса, выполнившего последнюю операцию над семафором;
•GETNCNT/GETZCNT — узнать число процессов, ожидающих увеличения /обнуления значения семафора.
160
lpid = semctl (semid, semnum, GETPID); ncnt = semctl (semid, semnum, GETNCNT); zcnt = semctl (semid, semnum, GETZCNT);
Управляющие команды над семафорами:
•IPC_STAT - получить информацию о состоянии набора семафоров;
•IPC_SET - переустановить характеристики;
•IPC_RMID - удалить набор семафоров.
arg.buf = (struct semid_ds *) malloc (sizeof (struct semid_ds); err = semctl (semid, 0, IPC_STAT, arg);
arg.buf->sem_perm.mode = 0644;
err = semctl (semid, 0, IPC_SET, arg);
err = semctl (semid, 0, IPC_RMID);
В случае ошибки, semctl(...) вернет значение -1, а переменная errno будет иметь одно из значений:
•EACCES - вызывающий процесс не имеет права доступа, необходимого для запуска cmd;
•EFAULT - адрес, указанный arg.buf или arg.array , недоступен;
•EIDRM - набор семафоров был удален;
•EINVAL - неверное значение cmd или semid;
•EPERM - аргумент cmd имеет значение IPC_SET или IPC_RMID, но вызывающий процесс не имеет достаточных привилегий на выполнение команды;
•ERANGE - аргумент cmd имеет значение SETALL или SETVAL, или значение,
присваиваемое semval, для некоторых семафоров в наборе, меньше нуля или больше, чем стандартное значение SEMVMX.
Как уже было отмечено ранее, семафоры:
•обеспечивают программиста наддежными средствами синхронизации процессов;
•требуют от программиста разработки и реализации алгоритмов, обеспечивающих взаимное исключение процессов и устранение их взаимных блокировок.
Всложных реальных задачах, в которых взаимодействуют множество асинхронно выполняющихся процессов, разработка надежных алгоритмов синхронизации также превращается в самостоятельную проблему.
Втаких случаях, программисту требуется правильно провести декомпозицию алгоритма работы приложений и реализовать его по частям.