Межпроцессное взаимодействие - семафоры
Первый вопрос, который приходит в голову, - зачем нам семафоры? Простой ответ, чтобы защитить критическую / общую область, совместно используемую несколькими процессами.
Предположим, несколько процессов используют одну и ту же область кода, и если все хотят иметь доступ параллельно, то результат перекрывается. Скажем, например, несколько пользователей используют только один принтер (общий / критический раздел), скажем, 3 пользователя получают 3 задания одновременно, если все задания запускаются параллельно, то вывод одного пользователя перекрывается с другим. Итак, нам нужно защитить это с помощью семафоров, то есть блокировать критическую секцию, когда один процесс запущен, и разблокировать, когда это делается. Это будет повторяться для каждого пользователя / процесса, чтобы одно задание не перекрывалось другим.
В основном семафоры делятся на два типа -
Binary Semaphores - Только два состояния 0 и 1, т.е. заблокировано / разблокировано или доступно / недоступно, реализация Mutex.
Counting Semaphores - Семафоры, которые позволяют произвольно подсчитывать ресурсы, называются семафорами подсчета.
Предположим, что у нас есть 5 принтеров (чтобы понять, что 1 принтер принимает только 1 задание) и у нас есть 3 задания для печати. Теперь на 3 принтера будет отдано 3 задания (по 1). Снова 4 работы пришли, пока это продолжается. Теперь из 2 доступных принтеров 2 задания были запланированы, и у нас осталось еще 2 задания, которые будут выполнены только после того, как один из ресурсов / принтеров станет доступен. Этот вид планирования по доступности ресурсов можно рассматривать как подсчет семафоров.
Чтобы выполнить синхронизацию с использованием семафоров, выполните следующие действия:
Step 1 - Создайте семафор или подключитесь к уже существующему семафору (semget ())
Step 2 - Выполнять операции с семафором, т. Е. Выделять, освобождать или ждать ресурсов (semop ())
Step 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 для создания семафора, и вызов завершается ошибкой, если семафор уже существует). Также необходимо передать разрешения.
Note - Подробную информацию о разрешениях см. В предыдущих разделах.
Этот вызов вернет действительный идентификатор семафора (используемый для дальнейших вызовов семафоров) в случае успеха и -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, является идентификатором семафора. Этот id является идентификатором семафора, который является возвращаемым значением системного вызова semget ().
Второй аргумент, семнум, - это номер семафора. Семафоры нумеруются от 0.
Третий аргумент, cmd, - это команда для выполнения требуемой операции управления семафором.
Четвертый аргумент типа union semun зависит от cmd. В некоторых случаях четвертый аргумент неприменим.
Проверим союз семун -
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 */
};
Note - Пожалуйста, обратитесь к страницам руководства для получения информации о других структурах данных.
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 возвращает индекс или идентификатор наиболее используемой записи согласно Semaphore или значение 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, однако, поскольку до завершения одной задачи процесса другой процесс также обрабатывается параллельно, значение счетчика не такое, как ожидалось. Результат будет отличаться от системы к системе, а также будет меняться при каждом выполнении. Чтобы два процесса выполняли задачу после завершения одной задачи, ее следует реализовать с использованием механизмов синхронизации.
Теперь давайте проверим то же приложение с помощью семафоров.
Note - Программа чтения осталась прежней.
/* 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