프로세스 간 통신-세마포

가장 먼저 떠오르는 질문은 왜 세마포어가 필요한가요? 여러 프로세스간에 공유되는 중요 / 공통 영역을 보호하기위한 간단한 대답입니다.

여러 프로세스가 동일한 코드 영역을 사용하고 있고 모두 병렬로 액세스하려는 경우 결과가 겹친다 고 가정 해 보겠습니다. 예를 들어 여러 사용자가 한 대의 프린터 만 사용하고 (공통 / 중요 섹션), 3 명의 사용자가 동시에 3 개의 작업을 수행한다고 가정 해 보겠습니다. 모든 작업이 병렬로 시작되면 한 사용자 출력이 다른 사용자 출력과 겹칩니다. 따라서 우리는 세마포어를 사용하여이를 보호해야합니다. 즉, 하나의 프로세스가 실행 중일 때 중요 섹션을 잠그고 완료되면 잠금을 해제합니다. 이것은 하나의 작업이 다른 작업과 겹치지 않도록 각 사용자 / 프로세스에 대해 반복됩니다.

기본적으로 세마포어는 두 가지 유형으로 분류됩니다.

Binary Semaphores − 0과 1의 두 가지 상태, 즉 잠김 / 잠금 해제 또는 사용 가능 / 사용 불가능, Mutex 구현.

Counting Semaphores − 임의의 자원 개수를 허용하는 세마포어를 카운팅 세마포라고합니다.

5 개의 프린터가 있고 (1 개의 프린터가 1 개의 작업 만 수락한다고 가정하기 위해) 인쇄 할 작업이 3 개 있다고 가정합니다. 이제 3 개의 프린터 (각각 1 개)에 대해 3 개의 작업이 제공됩니다. 이 작업이 진행되는 동안 다시 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 세마포어 세트를 생성하거나 할당합니다. 다음 인수를 전달해야합니다-

  • 첫 번째 인수 인 key는 메시지 큐를 인식합니다. 키는 임의의 값이거나 라이브러리 함수 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이면 호출 프로세스는 세마포어 값이 0이 될 때까지 대기하거나 휴면합니다.

  • sem_op이 + ve이면 리소스를 해제합니다.

예를 들면-

구조체 sembuf sem_lock = {0, -1, SEM_UNDO};

구조체 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 () 시스템 호출의 반환 값인 세마포어 식별자입니다.

  • 두 번째 인수 인 semnum은 세마포어의 수입니다. 세마포어는 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*/
};

sys / sem.h에 정의 된 semid_ds 데이터 구조는 다음과 같습니다.

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 − 다른 데이터 구조는 매뉴얼 페이지를 참조하십시오.

조합 semun arg; cmd의 유효한 값은 다음과 같습니다.

  • IPC_STAT− struct semid_ds의 각 멤버의 현재 값 정보를 arg.buf가 가리키는 전달 된 구조체에 복사합니다. 이 명령에는 세마포에 대한 읽기 권한이 필요합니다.

  • IPC_SET − semid_ds 구조가 가리키는 사용자 ID, 소유자 그룹 ID, 권한 등을 설정합니다.

  • IPC_RMID − 세마포어 세트를 제거합니다.

  • IPC_INFO − arg .__ buf가 가리키는 구조 semid_ds의 세마포어 제한 및 매개 변수에 대한 정보를 반환합니다.

  • SEM_INFO − 세마포어가 소비 한 시스템 리소스에 대한 정보를 포함하는 seminfo 구조를 반환합니다.

이 호출은 전달 된 명령에 따라 값 (음수가 아닌 값)을 반환합니다. 성공하면 IPC_INFO 및 SEM_INFO 또는 SEM_STAT는 Semaphore에 따라 가장 많이 사용 된 항목의 인덱스 또는 식별자 또는 GETNCNT의 경우 semncnt 값, GETPID의 경우 sempid 값, 성공한 다른 작업의 경우 GETVAL 0의 semval 값을 반환합니다. 실패의 경우 1입니다. 실패의 원인을 알려면 errno 변수 또는 perror () 함수로 확인하십시오.

코드를보기 전에 그 구현을 이해합시다.

  • 두 개의 프로세스 즉, 자식과 부모를 만듭니다.

  • 카운터 및 기타 플래그를 저장하는 데 주로 필요한 공유 메모리를 생성하여 공유 메모리에 대한 읽기 / 쓰기 프로세스의 끝을 나타냅니다.

  • 카운터는 부모 및 자식 프로세스 모두에 의해 개수만큼 증가합니다. 개수는 명령 줄 인수로 전달되거나 기본값으로 사용됩니다 (명령 줄 인수로 전달되지 않거나 값이 10000 미만인 경우). 부모와 자식이 동시에 즉 병렬로 공유 메모리에 액세스 할 수 있도록 특정 절전 시간과 함께 호출됩니다.

  • 카운터는 부모와 자식 모두 1 단계 씩 증가하므로 최종 값은 카운터의 두 배가되어야합니다. 동시에 작업을 수행하는 부모 및 자식 프로세스가 있기 때문에 카운터가 필요에 따라 증가하지 않습니다. 따라서 하나의 프로세스 완료 후 다른 프로세스의 완전성을 보장해야합니다.

  • 위의 모든 구현은 shm_write_cntr.c 파일에서 수행됩니다.

  • 카운터 값이 shm_read_cntr.c 파일에 구현되어 있는지 확인하십시오.

  • 완료를 보장하기 위해 세마포어 프로그램이 shm_write_cntr_with_sem.c 파일에 구현됩니다. 전체 프로세스 완료 후 세마포어 제거 (다른 프로그램에서 읽은 후)

  • 공유 메모리에서 counter 값을 읽을 수있는 별도의 파일이 있고 쓰기에 영향을받지 않기 때문에 읽기 프로그램은 동일하게 유지됩니다 (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