Системное программное обеспечение - Учебное пособие (Терехин А.Н.)

4.12операции над семафором

Используя полученный дескриптор, можно  производить изменять значения одного или нескольких семафоров в наборе, а также проверять их значения на равенство нулю,  для чего используется системный вызов semop():

#include <sys/types.h>

#include<sys/ipc.h>

#include<sys/sem.h>

int semop (int semid, struct sembuf *semop, size_t nops)

Этому вызову передаются следующие аргументы:

semid – дескриптор массива семафоров;

semop – массив из объектов типа struct sembuf, каждый из которых задает одну операцию над семафором;

nops – длина массива semop. Количество семафоров, над которыми процесс может одновременно производить операцию в одном вызове semop(), ограничено константой  SEMOPM, описанной в файле <sys/sem.h>. Если процесс попытается вызвать semop() с параметром nops, большим этого значения, этот вызов вернет неуспех.

Структура  имеет sembuf вид:

struct sembuf {

short sem_num;   /* номер семафора в векторе */

          short sem_op;      /* производимая операция */        

          short sem_flg;      /* флаги операции */

}

Поле операции в структуре интерпретируется следующим образом:

Пусть значение семафора с номером sem_num равно sem_val.

если значение операции не равно нулю:

оценивается значение суммы sem_val + sem_op.

если эта сумма больше либо равна нулю, то значение данного семафора устанавливается равным этой сумме: sem_val = sem_val + sem_op

если же эта сумма меньше нуля, то действие процесса будет приостановлено до тех пор, пока значение суммы  sem_val + sem_op не станет больше либо равно нулю, после чего значение семафора устанавливается равным этой сумме: sem_val = sem_val + sem_op

Если код операции sem_op равен нулю:

Если при этом значение семафора (sem_val) равно нулю, происходит немедленный возврат из вызова

Иначе происходит блокирование процесса до тех пор, пока значение семафора не обнулится, после чего происходит возврат из вызова

Таким образом, ненулевое значение поля sem_op обозначает необходимость прибавить к текущему значению семафора значение sem_op, а нулевое – дождаться обнуления семафора.

Поле sem_flg в структуре sembuf содержит комбинацию флагов, влияющих на выполнение операции с семафором. В этом поле может быть установлен флаг IPC_NOWAIT, который предписывает соответствующей операции над семафором не блокировать процесс, а сразу возвращать управление из вызова semop(). Вызов semop() в такой ситуации вернет –1. Кроме того, в этом поле может быть установлен флаг SEM_UNDO, в этом случае система запомнит изменение значения семафора, произведенные данным вызовом, и по завершении процесса автоматически ликвидирует это изменение. Это предохраняет от ситуации, когда процесс уменьшил значение семафора, начав работать с ресурсом, а потом, не увеличив значение семафора обратно, по какой-либо причине завершился. В этом случае остальные процессы, ждущие доступа к ресурсу, оказались бы заблокированы навечно.

Управление массивом семафоров.

#include <sys/types.h>

#include<sys/ipc.h>

#include<sys/sem.h>

int semctl (int semid, int num, int cmd, union semun arg)

С помощью этого системного вызова можно запрашивать и изменять управляющие параметры разделяемого ресурса, а также удалять его.

Первый параметр вызова – дескриптор массива семафоров. Параметр num представляет собой индекс семафора в массиве, параметр cmd задает операцию, которая должна быть выполнена над данным семафором. Последний аргумент имеет тип union semun и используется для считывания или задания управляющих параметров одного семафора или всего массива, в зависимости от значения аргумента cmd. Тип данных union semun определен в файле <sys/sem.h> и выглядит следующим образом:

union semun {

          int val;        // значение одного семафора

          struct semid_ds *buf;     /* параметры массива семафоров в целом */

          ushort         *array;                  /* массив значений семафоров */

}

где struct semid_ds – структура, описанная в том же файле, в полях которой хранится информация о всем наборе семафоров в целом, а именно, количество семафоров в наборе, права доступа к нему и статистика доступа к массиву семафоров.

Приведем некоторые наиболее часто используемые значения аргумента cmd:

IPC_STAT – скопировать управляющие параметры набора семафоров по адресу arg.buf

IPC_SET – заменить управляющие параметры набора семафоров на те, которые указаны в arg.buf. Чтобы выполнить эту операцию, процесс должен быть владельцем или создателем массива семафоров, либо обладать правами привилегированного пользователя, при этом процесс может изменить только владельца массива семафоров и права доступа к нему.

IPC_RMID – удалить массив семафоров. Чтобы выполнить эту операцию, процесс должен быть владельцем или создателем массива семафоров, либо обладать правами привилегированного пользователя

GETALL, SETALL – считать / установить значения всех семафоров в массив, на который указывает arg.array

GETVAL – возвратить значение семафора с номером num. Последний аргумент вызова игнорируется.

SETVAL – установить значение семафора с номером num равным arg.val

В случае успешного завершения вызов возвращает значение, соответствующее конкретной выполнявшейся операции (0, если не оговорено иное), в случае неудачи – -1.

 Работа с разделяемой памятью с синхронизацией семафорами.

Программа будет оперировать с разделяемой памятью.

1 процесс – создает ресурсы “разделяемая память” и “семафоры”, далее он начинает принимать строки со стандартного ввода и записывает их в разделяемую память.

2 процесс – читает строки из разделяемой памяти.

Таким образом мы имеем критический участок в момент, когда один процесс еще не дописал строку, а другой ее уже читает. Поэтому следует установить некоторые синхронизации и задержки. 

1й процесс:

#include <stdio.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

#include <string.h>

#define NMAX     256

int main(int argc, char **argv)

{       

key_t key;

          int semid, shmid;

          struct sembuf sops;

          char *shmaddr;

          char str[NMAX];

 

          key = ftok(“/usr/ter/exmpl”, ’S’);                  

/* создаем уникальный ключ */

          semid = semget(key, 1, 0666 | IPC_CREAT);

/* создаем один семафор с определенными правами доступа */

shmid = shmget(key, NMAX, 0666 | IPC_CREAT);

/* создаем разделяемую память на 256 элементов */

          shmaddr = shmat(shmid, NULL, 0);

/* подключаемся к разделу памяти, в shaddr –                                                                                  указатель на буфер с разделяемой памятью */

     semctl(semid,0,SETVAL, (int) 0);

/* инициализируем семафор значением 0 */

          sops.sem_num = 0;

sops.sem_flg = 0;

          do { /* запуск цикла */

printf(“Введите строку:”);

if (fgets(str, NMAX, stdin) == NULL)

{

/* окончание ввода */

/* пишем признак завершения – строку “Q” */

                             strcpy(str, “Q”);

                   }

/* в текущий момент семафор открыт для этого процесса */

strcpy(shmaddr, str); /* копируем строку в разд. память */

/* предоставляем второму процессу возможность войти */

sops.sem_op = 3; /* увеличение семафора на 3 */

          semop(semid, &sops, 1);

/* ждем, пока семафор будет открыт для 1го процесса -  для следующей итерации цикла */

sops.sem_op = 0; /* ожидание обнуления семафора */

                   semop(semid, &sops, 1);

} while (str[0] != ‘Q’);

/* в данный момент второй процесс уже дочитал из разделяемой памяти и отключился от нее – можно ее удалять*/

shmdt(shmaddr) ; /* отключаемся от разделяемой памяти */

          shmctl(shmid, IPC_RMID, NULL);

/* уничтожаем разделяемую память */

          semctl(semid, 0, IPC_RMID, (int) 0);        

/* уничтожаем семафор */

return 0;

}

         

 

2й процесс:

/* необходимо корректно определить существование ресурса, если он есть - подключиться */

#include <stdio.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

#include <string.h>

#define NMAX     256

 

int main(int argc, char **argv)

{       

key_t key;

int semid, shmid;

          struct sembuf sops;

          char *shmaddr;

          char str[NMAX];

 

          key = ftok(“/usr/ter/exmpl”,’S’);                   

/* создаем тот же самый ключ */

          semid = semget(key, 1, 0666 | IPC_CREAT);

          shmid = shmget(key, NMAX, 0666 | IPC_CREAT);

/* аналогично предыдущему процессу  - инициализации ресурсов */

          shmaddr = shmat(shmid, NULL, 0);

          sops.sem_num = 0;

sops.sem_flg = 0;

          /* запускаем цикл */

          do {

printf(“Waiting… ”); /* ожидание на семафоре */

sops.sem_op = -2;

/* будем ожидать, пока “значение семафора” + ”значение sem_op” не станет положительным, т.е. пока значение семафора не станет как минимум 3 (3-2=1 > 0) */

                   semop(semid, &sops, 1);

/* теперь значение семафора равно 1 */

strcpy(str, shmaddr); /* копируем строку из разд.памяти */

/*критическая секция - работа с разделяемой памятью - в этот момент  первый процесс к разделяемой памяти доступа не имеет*/

if (str[0] == ‘Q’)

{

          /*завершение работы - освобождаем разделяемую память */

          shmdt(shmaddr);

}

          /*после работы – обнулим семафор*/

          sops.sem_op=-1;

          semop(semid, &sops, 1);

printf(“Read from shared memory: \%s ”, str);

} while (str[0] != ‘Q’);

          return 0;

}

 

Отметим, что данный пример демонстрирует два разных приема использования семафоров для синхронизации: первый процесс блокируется в ожидании обнуления семафора, т.е. для того, чтобы он мог войти в критическую секцию, значение семафора должно стать нулевым;  второй процесс блокируется при попытке уменьшить значение семафора до отрицательной величины, для того, чтобы этот процесс мог войти в критическую секцию, значение семафора должно быть не менее 3. Обратите внимание, что в данном примере, помимо взаимного исключения процессов, достигается строгая последовательность действий двух процессов: они получают доступ к критической секции строго по очереди.

Взаимодействие процессов в сети.