Межпроцессное взаимодействие - семафоры

Первый вопрос, который приходит на ум: зачем нам семафоры? Простой ответ, чтобы защитить критическую / общую область, общую для нескольких процессов.

Предположим, что несколько процессов используют одну и ту же область кода, и если все хотят получить параллельный доступ, результат перекрывается. Скажем, например, что несколько пользователей используют только один принтер (общий / критический раздел), скажем, 3 пользователя при одновременном запуске 3 заданий, если все задания запускаются параллельно, тогда один вывод пользователя перекрывается с другим. Таким образом, мы должны защитить это с помощью семафоров, т. Е. Блокирования критической секции, когда выполняется один процесс, и разблокировки, когда он завершен. Это будет повторяться для каждого пользователя / процесса, чтобы одно задание не перекрывалось с другим заданием.

В основном семафоры делятся на два типа -

Двоичные семафоры - только два состояния 0 и 1, т. Е. Заблокировано / разблокировано или доступно / недоступно, реализация Mutex.

Подсчет семафоров - Семафоры, которые допускают произвольный подсчет ресурсов, называются подсчетами семафоров.

Предположим, что у нас есть 5 принтеров (чтобы понять, предположим, что 1 принтер принимает только 1 задание), и мы получили 3 задания на печать. Теперь 3 задания будут отданы на 3 принтера (по 1 на каждого). Снова 4 рабочих места пришли, пока это продолжается. Теперь из 2 доступных принтеров запланировано 2 задания, и у нас осталось еще 2 задания, которые будут выполнены только после того, как один из ресурсов / принтеров станет доступен. Этот вид планирования в соответствии с доступностью ресурса может рассматриваться как подсчет семафоров.

Чтобы выполнить синхронизацию с использованием семафоров, выполните следующие действия:

Шаг 1 - Создайте семафор или подключитесь к уже существующему семафору (semget ())

Шаг 2 - Выполните операции с семафором, т. Е. Выделите или освободите или дождитесь ресурсов (semop ())

Шаг 3 - Выполнение операций управления в очереди сообщений (semctl ())

Теперь давайте проверим это с помощью системных вызовов, которые у нас есть.

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

int semget(key_t key, int nsems, int semflg)

Этот системный вызов создает или выделяет набор семафоров System V. Следующие аргументы должны быть переданы -

  • Первый аргумент, ключ, распознает очередь сообщений. Ключ может быть либо произвольным значением, либо ключом, который можно получить из библиотечной функции ftok ().

  • Второй аргумент, nsems, определяет количество семафоров. Если двоичный код, то он равен 1, что подразумевает необходимость набора 1 семафора, в противном случае согласно требуемому количеству наборов семафоров.

  • Третий аргумент, semflg, указывает требуемые флаги семафора, такие как IPC_CREAT (создание семафора, если он не существует) или IPC_EXCL (используется с IPC_CREAT для создания семафора, и вызов завершается неудачей, если семафор уже существует). Нужно также передать разрешения.

Примечание. Подробнее о разрешениях см. В предыдущих разделах.

Этот вызов вернул бы действительный идентификатор семафора (используемый для дальнейших вызовов семафоров) в случае успеха и -1 в случае сбоя. Чтобы узнать причину ошибки, проверьте с помощью переменной errno или функции perror ().

Различные ошибки в отношении этого вызова: EACCESS (разрешение отклонено), EEXIST (очередь уже существует, не может создать), ENOENT (очередь не существует), ENOMEM (недостаточно памяти для создания очереди), ENOSPC (ограничение максимального количества установок) превышен) и т. д.

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

int semop(int semid, struct sembuf *semops, size_t nsemops)

Этот системный вызов выполняет операции над наборами семафоров System V, а именно: выделение ресурсов, ожидание ресурсов или освобождение ресурсов. Следующие аргументы должны быть переданы -

  • Первый аргумент, semid, указывает идентификатор набора семафоров, созданный semget ().

  • Второй аргумент, semops, является указателем на массив операций, которые должны быть выполнены с набором семафоров. Структура выглядит следующим образом -

struct sembuf {
   unsigned short sem_num; /* Semaphore set num */
   short sem_op; /* Semaphore operation */
   short sem_flg; /* Operation flags, IPC_NOWAIT, SEM_UNDO */
};

Элемент sem_op в приведенной выше структуре указывает на операцию, которую необходимо выполнить:

  • Если sem_op равен –ve, выделите или получите ресурсы. Блокирует вызывающий процесс до тех пор, пока другие процессы не освободят достаточно ресурсов, чтобы этот процесс мог выделить его.

  • Если sem_op равен нулю, вызывающий процесс ждет или спит, пока значение семафора не достигнет 0.

  • Если sem_op равен + ve, освободить ресурсы.

Например -

struct sembuf sem_lock = {0, -1, SEM_UNDO};

struct sembuf sem_unlock = {0, 1, SEM_UNDO};

  • Третий аргумент, nsemops, это количество операций в этом массиве.

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

int semctl(int semid, int semnum, int cmd, …)

Этот системный вызов выполняет операцию управления для семафора System V. Следующие аргументы должны быть переданы -

  • Первый аргумент, semid, является идентификатором семафора. Этот идентификатор является идентификатором семафора, который является возвращаемым значением системного вызова semget ().

  • Второй аргумент, semnum, это номер семафора. Семафоры пронумерованы от 0.

  • Третий аргумент, cmd, является командой для выполнения требуемой операции управления на семафоре.

  • Четвертый аргумент типа union semun зависит от cmd. В некоторых случаях четвертый аргумент не применим.

Давайте проверим союз semun -

union semun {
   int val; /* val for SETVAL */
   struct semid_ds *buf; /* Buffer for IPC_STAT and IPC_SET */
   unsigned short *array; /* Buffer for GETALL and SETALL */
   struct seminfo *__buf; /* Buffer for IPC_INFO and SEM_INFO*/
};

Структура данных semid_ds, которая определена в sys / sem.h, выглядит следующим образом:

struct semid_ds {
   struct ipc_perm sem_perm; /* Permissions */
   time_t sem_otime; /* Last semop time */
   time_t sem_ctime; /* Last change time */
   unsigned long sem_nsems; /* Number of semaphores in the set */
};

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

Union Semun Arg; Допустимые значения для cmd -

  • IPC_STAT - копирует информацию о текущих значениях каждого члена struct semid_ds в переданную структуру, указанную arg.buf. Эта команда требует разрешения на чтение для семафора.

  • IPC_SET - устанавливает идентификатор пользователя, идентификатор группы владельца, разрешения и т. Д., На которые указывает структура semid_ds.

  • IPC_RMID - удаляет набор семафоров.

  • IPC_INFO - возвращает информацию о границах и параметрах семафора в структуре semid_ds, на которую указывает arg .__ buf.

  • SEM_INFO - возвращает структуру seminfo, содержащую информацию о потребляемых системных ресурсах семафором.

Этот вызов вернет значение (неотрицательное значение) в зависимости от переданной команды. В случае успеха IPC_INFO и SEM_INFO или SEM_STAT возвращают индекс или идентификатор самой высокой используемой записи в соответствии с семафором или значение semncnt для GETNCNT или значение sempid для GETPID или значение semval для GETVAL 0 для других операций в случае успеха и - 1 в случае неудачи. Чтобы узнать причину ошибки, проверьте с помощью переменной errno или функции perror ().

Прежде чем смотреть на код, давайте разберемся с его реализацией -

  • Создайте два процесса, скажем, дочерний и родительский.

  • Создать разделяемую память, в основном необходимую для хранения счетчика и других флагов, указывающих конец процесса чтения / записи в разделяемую память.

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

  • Поскольку счетчик увеличивается с шагом 1 как родительским, так и дочерним, окончательное значение должно быть в два раза больше счетчика. Поскольку родительские и дочерние процессы выполняют операции одновременно, счетчик не увеличивается на единицу, как требуется. Следовательно, мы должны обеспечить полноту завершения одного процесса, за которым следует другой процесс.

  • Все вышеперечисленные реализации выполняются в файле shm_write_cntr.c

  • Проверьте, реализовано ли значение счетчика в файле shm_read_cntr.c

  • Для обеспечения завершения программа семафора реализована в файле shm_write_cntr_with_sem.c. Удалить семафор после завершения всего процесса (после чтения из другой программы)

  • Так как у нас есть отдельные файлы для чтения значения счетчика в общей памяти и мы не имеем никакого эффекта от записи, программа чтения остается прежней (shm_read_cntr.c)

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

Программы без семафоров.

/* Filename: shm_write_cntr.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>

#define SHM_KEY 0x12345
struct shmseg {
   int cntr;
   int write_complete;
   int read_complete;
};
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count);

int main(int argc, char *argv[]) {
   int shmid;
   struct shmseg *shmp;
   char *bufptr;
   int total_count;
   int sleep_time;
   pid_t pid;
   if (argc != 2)
   total_count = 10000;
   else {
      total_count = atoi(argv[1]);
      if (total_count < 10000)
      total_count = 10000;
   }
   printf("Total Count is %d\n", total_count);
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);

   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }

   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL , 0);
   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }
   shmp->cntr = 0;
   pid = fork();

   /* Parent Process - Writing Once */
   if (pid > 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
   } else if (pid == 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
      return 0;
   } else {
      perror("Fork Failure\n");
      return 1;
   }
   while (shmp->read_complete != 1)
   sleep(1);

   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }

   if (shmctl(shmid, IPC_RMID, 0) == -1) {
      perror("shmctl");
      return 1;
   }
   printf("Writing Process: Complete\n");
   return 0;
}

/* Increment the counter of shared memory by total_count in steps of 1 */
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count) {
   int cntr;
   int numtimes;
   int sleep_time;
   cntr = shmp->cntr;
   shmp->write_complete = 0;
   if (pid == 0)
   printf("SHM_WRITE: CHILD: Now writing\n");
   else if (pid > 0)
   printf("SHM_WRITE: PARENT: Now writing\n");
   //printf("SHM_CNTR is %d\n", shmp->cntr);
   
   /* Increment the counter in shared memory by total_count in steps of 1 */
   for (numtimes = 0; numtimes < total_count; numtimes++) {
      cntr += 1;
      shmp->cntr = cntr;
      
      /* Sleeping for a second for every thousand */
      sleep_time = cntr % 1000;
      if (sleep_time == 0)
      sleep(1);
   }
   
   shmp->write_complete = 1;
   if (pid == 0)
   printf("SHM_WRITE: CHILD: Writing Done\n");
   else if (pid > 0)
   printf("SHM_WRITE: PARENT: Writing Done\n");
   return;
}

Шаги компиляции и выполнения

Total Count is 10000
SHM_WRITE: PARENT: Now writing
SHM_WRITE: CHILD: Now writing
SHM_WRITE: PARENT: Writing Done
SHM_WRITE: CHILD: Writing Done
Writing Process: Complete

Теперь давайте проверим программу чтения общей памяти.

/* Filename: shm_read_cntr.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>

#define SHM_KEY 0x12345
struct shmseg {
   int cntr;
   int write_complete;
   int read_complete;
};

int main(int argc, char *argv[]) {
   int shmid, numtimes;
   struct shmseg *shmp;
   int total_count;
   int cntr;
   int sleep_time;
   if (argc != 2)
   total_count = 10000;
   
   else {
      total_count = atoi(argv[1]);
      if (total_count < 10000)
      total_count = 10000;
   }
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
   
   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }
   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL , 0);
   
   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }
   
   /* Read the shared memory cntr and print it on standard output */
   while (shmp->write_complete != 1) {
      if (shmp->cntr == -1) {
         perror("read");
         return 1;
      }
      sleep(3);
   }
   printf("Reading Process: Shared Memory: Counter is %d\n", shmp->cntr);
   printf("Reading Process: Reading Done, Detaching Shared Memory\n");
   shmp->read_complete = 1;
   
   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }
   printf("Reading Process: Complete\n");
   return 0;
}

Шаги компиляции и выполнения

Reading Process: Shared Memory: Counter is 11000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete

Если вы наблюдаете вышеприведенные выходные данные, счетчик должен быть 20000, однако, поскольку до завершения одной задачи процесса другой процесс также обрабатывает параллельно, значение счетчика не соответствует ожидаемому. Вывод будет варьироваться от системы к системе, а также будет зависеть от каждого выполнения. Чтобы гарантировать, что два процесса выполняют задачу после завершения одной задачи, она должна быть реализована с использованием механизмов синхронизации.

Теперь давайте проверим то же приложение, используя семафоры.

Примечание. Программа чтения остается прежней.

/* Filename: shm_write_cntr_with_sem.c */
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>

#define SHM_KEY 0x12345
#define SEM_KEY 0x54321
#define MAX_TRIES 20

struct shmseg {
   int cntr;
   int write_complete;
   int read_complete;
};
void shared_memory_cntr_increment(int, struct shmseg*, int);
void remove_semaphore();

int main(int argc, char *argv[]) {
   int shmid;
   struct shmseg *shmp;
   char *bufptr;
   int total_count;
   int sleep_time;
   pid_t pid;
   if (argc != 2)
   total_count = 10000;
   else {
      total_count = atoi(argv[1]);
      if (total_count < 10000)
      total_count = 10000;
   }
   printf("Total Count is %d\n", total_count);
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
   
   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }
   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL , 0);
   
   if (shmp == (void *) -1) {
      perror("Shared memory attach: ");
      return 1;
   }
   shmp->cntr = 0;
   pid = fork();
   
   /* Parent Process - Writing Once */
   if (pid > 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
   } else if (pid == 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
      return 0;
   } else {
      perror("Fork Failure\n");
      return 1;
   }
   while (shmp->read_complete != 1)
   sleep(1);
   
   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }
   
   if (shmctl(shmid, IPC_RMID, 0) == -1) {
      perror("shmctl");
      return 1;
   }
   printf("Writing Process: Complete\n");
   remove_semaphore();
   return 0;
}

/* Increment the counter of shared memory by total_count in steps of 1 */
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count) {
   int cntr;
   int numtimes;
   int sleep_time;
   int semid;
   struct sembuf sem_buf;
   struct semid_ds buf;
   int tries;
   int retval;
   semid = semget(SEM_KEY, 1, IPC_CREAT | IPC_EXCL | 0666);
   //printf("errno is %d and semid is %d\n", errno, semid);
   
   /* Got the semaphore */
   if (semid >= 0) {
      printf("First Process\n");
      sem_buf.sem_op = 1;
      sem_buf.sem_flg = 0;
      sem_buf.sem_num = 0;
      retval = semop(semid, &sem_buf, 1);
      if (retval == -1) {
         perror("Semaphore Operation: ");
         return;
      }
   } else if (errno == EEXIST) { // Already other process got it
      int ready = 0;
      printf("Second Process\n");
      semid = semget(SEM_KEY, 1, 0);
      if (semid < 0) {
         perror("Semaphore GET: ");
         return;
      }
      
      /* Waiting for the resource */
      sem_buf.sem_num = 0;
      sem_buf.sem_op = 0;
      sem_buf.sem_flg = SEM_UNDO;
      retval = semop(semid, &sem_buf, 1);
      if (retval == -1) {
         perror("Semaphore Locked: ");
         return;
      }
   }
   sem_buf.sem_num = 0;
   sem_buf.sem_op = -1; /* Allocating the resources */
   sem_buf.sem_flg = SEM_UNDO;
   retval = semop(semid, &sem_buf, 1);
   
   if (retval == -1) {
      perror("Semaphore Locked: ");
      return;
   }
   cntr = shmp->cntr;
   shmp->write_complete = 0;
   if (pid == 0)
   printf("SHM_WRITE: CHILD: Now writing\n");
   else if (pid > 0)
   printf("SHM_WRITE: PARENT: Now writing\n");
   //printf("SHM_CNTR is %d\n", shmp->cntr);
   
   /* Increment the counter in shared memory by total_count in steps of 1 */
   for (numtimes = 0; numtimes < total_count; numtimes++) {
      cntr += 1;
      shmp->cntr = cntr;
      /* Sleeping for a second for every thousand */
      sleep_time = cntr % 1000;
      if (sleep_time == 0)
      sleep(1);
   }
   shmp->write_complete = 1;
   sem_buf.sem_op = 1; /* Releasing the resource */
   retval = semop(semid, &sem_buf, 1);
   
   if (retval == -1) {
      perror("Semaphore Locked\n");
      return;
   }
   
   if (pid == 0)
      printf("SHM_WRITE: CHILD: Writing Done\n");
      else if (pid > 0)
      printf("SHM_WRITE: PARENT: Writing Done\n");
      return;
}
   
void remove_semaphore() {
   int semid;
   int retval;
   semid = semget(SEM_KEY, 1, 0);
      if (semid < 0) {
         perror("Remove Semaphore: Semaphore GET: ");
         return;
      }
   retval = semctl(semid, 0, IPC_RMID);
   if (retval == -1) {
      perror("Remove Semaphore: Semaphore CTL: ");
      return;
   }
   return;
}

Шаги компиляции и выполнения

Total Count is 10000
First Process
SHM_WRITE: PARENT: Now writing
Second Process
SHM_WRITE: PARENT: Writing Done
SHM_WRITE: CHILD: Now writing
SHM_WRITE: CHILD: Writing Done
Writing Process: Complete

Теперь мы проверим значение счетчика в процессе чтения.

Шаги выполнения

Reading Process: Shared Memory: Counter is 20000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete