[Unix Programming] Semaphore
synchronization 기법 중 하나인 semaphore에 대해 알아본다.
Semaphore는 공유된 자원에 대한 접근을 제어 하는 방식으로 1개의 공유되는 자원에 제한된 개수의 process나 thread가 접근할 수 있도록 한다. 다시 말해서, process간의 message 전송 또는 shared memory를 통해 특정 데이터를 공유하게 되는 경우 여러 개의 process가 동시에 접근하면서 문제가 발생할 수 있는데 이 접근을 제어하는 것이 semaphore이다.
이는 mutex와 유사한 기능을 수행하지만, 다음과 같은 차이가 있다.
하나의 process/thread의 접근을 제어하는 mutex와 달리, semaphore는 공유자원에 접근할 수 있는 process/thread의 수를 나타내는 값을 두어 여러 개의 process/thread의 접근을 제어할 수 있다.
또, semaphore의 종류에는 2가지가 있는데,
첫번째로 binary semaphore가 있다. 이는 integer값이 1인 semaphore로 상호배제와 거의 동일한 기능을 수행한다.
두번째로는 counting semaphore가 있다. 이는 integer 값 만큼 진입이 가능한데, 예를들어 값이 3이면 공유자원에 동시 접근이 가능한 process/thread를 3개까지 허용한다는 뜻이다.
마지막으로, semaphore의 속성에는 integer value가 있다. 이는 해당 semaphore가 공유할 수 있는 process/thread의 숫자를 의미한다.
이제 semaphore의 방식에 대해 간략히 살펴보도록 하자.
여기서 sem은 integer variable이다.
/* p(sem) or wait(sem) */ if (sem != 0) sem-- else /* sem의 값이 0이 아닐 때 까지 기다린다. */
p는 lock을 거는 연산이다.
/* v(sem) or signal(sem) */ sem++ if (/* 대기하는 process가 있다면 */) /* 대기 queue에 있는 첫 번째 프로세스를 재실행 */
v는 lock을 해제하는 연산이다.
/* critical section */ p(sem); /* something interesting */ v(sem);
임계영역(critical section) 진입과 종료 시점에서 p와 v 연산을 수행한다.
여기선 POSIX Programming과 달리 여러 개의 semaphore를 한꺼번에 정의해서 사용할 수 있다.
다음은 semaphore variable을 초기화하는 semget 함수이다.
#include <sys/sem.h> int semget(key_t key, int nsems, int permflags);
여기서 key 값은 전에 봤던 msgget과 마찬가지로 unique 해야되며, key값으로 서로 다른 process들이 동일한 semaphore를 사용하고 있다는 것을 지정해 줄 수 있다.
nsems는 사용할 semaphore의 개수이다.
permflags는 접근권한 flag들이 들어간다.
semaphore를 생성을 하고 나면 이제 semaphore 연산이 필요하다.
다음은 semaphore 제어를 위한 semctl 함수이다.
#include <sys/sem.h> int semctl(int semid, int sem_num, int command, union semun ctl_arg);
semid에는 semget을 통해 return 받은 id값이 들어간다.
그 다음 sem_num은 특정 semaphore를 식별하기 위한 값이다. 예를 들어, 위 semget 함수를 통해 4개의 semaphore를 생성했다면 그 중 어떤 semaphore variable인지를 지정하는 값이다.
command는 semctl이 어떤 기능을 수행할 것인지 정하는 값이다.
마지막으로 semun 집합인 ctl_arg는 semaphore 연산에 관련된 값이 들어가며 구조는 다음과 같다.
union semun { int val; struct semid_ds *stat; unsigned short *array; };
command
- 표준 IPC 기능
- IPC_STAT: 상태 정보를 ctl_arg.stat에 저장한다.
- IPC_SET: ctl_arg.stat에 저장된 형태로 소유권과 허가를 지정한다.
- IPC_RMID: 시스템에서 해당 semaphore의 집합을 삭제한다.
- 단일 세마포 연산
- GETVAL: semaphore 값 semval을 return 한다.
- SETVAL: semaphore 값을 ctl_arg.val로 지정한다.
- GETPID: sempid(semaphore에 가장 최근에 접근한 pid) 값을 return 한다.
- GETNCNT: semncnt(semaphore 값이 현재의 값보다 더 큰 값을 갖기를 기다리는 process의 수)를 return 한다.
- GETZCNT: semxcent(semaphore의 값이 0이 되기를 기다리는 process의 수)를 return 한다.
- 전체 세마포 연산
- GETALL: 모든 semvals의 값을 ctl_arg.array로 저장한다.
- SETALL: ctl_arg.array의 값을 이용하여 모든 semvals 값을 지정한다. 예를 들어, 2, 4, 1, 3의 배열을 정의하여 SETALL을 수행하면 아래의 그림과 같이 각각의 semaphore의 semval에 값이 들어간다.

다음은 semctl 수행 예시이다.
/* pv.h -- 예시코드 header file */ #include <sys/type.h> #include <sys/ipc.h> #include <sys/sem.h> #include <errno.h> #define SEMPERM 0600 #define TRUE 1 #define FALSE 0 typedef union semun { int val; struct semid_ds *buf; ushort *array; } semun;
/* initsem -- semaphore 초기화 */ #include "pv.h" int initsem(key_t semkey) { int status = 0, semid; if ((semid = semget(semkey, 1, SEMPERM|IPC_CREAT|IPC_EXCL)) == -1) { if (errno == EEXIST) { /* 이미 존재한다면 */ semid = semget(semkey, 1, 0); } } else { /* 만일 생성되었으면 */ semun arg; arg.val = 1; status = semctl(semid, 0, SETVAL, arg); } if (semid == -1 || status == -1) { perror("initsem failed"); return (-1); } /* 모든 것이 잘되었음*/ return (semid); }
이제 semaphore 연산을 수행하는 semop 함수에 대해 알아보자. semop는 POSIX에서 사용했던 sem_wait, sem_post와 동일한 기능을 수행한다.
#include <sys/sem.h> int semop(int semid, struct sembuf *op_array, size_t num_ops);
semid는 위와 마찬가지로 id 값이다.
sembuf 구조체는 다음과 같이 정의되어 있다.
struct sembuf { short sem_num; /* 세마포 집합 내의 index */ short sem_op; /* semop이 수행하는 기능을 나타냄 */ short sem_flg; };
sem_op
- 음수일 때: p() -> lock을 건다(=sem_wait)
- 양수일 때: v() -> lock을 푼다(=sem_post)
- 0일 때: semval의 값이 0이 될 때 까지 기다림
sem_flg
- IPC_NOWAIT: non-blocking으로 동작한다.
- SEM_UNDO: 해당되는 process가 종료되도 semaphore 값을 kernel이 적절한 값으로 변경해주도록 한다.
다음은 semaphore의 예시 코드이다.
/* p.c -- 세마포 p 연산 */ #include "pv.h" int p(int semid) { struct sembuf p_buf; p_buf.sem_num = 0; p_buf.sem_op = -1; p_buf.sem_flg = SEM_UNDO; if (semop(semid, &p_buf, 1) == -1) { perror("p(semid) failed"); exit(1); } return (0); }
/* v.c -- 세마포 v 연산 */ #include "pv.h" int v(int semid) { struct sembuf v_buf; v_buf.sem_num = 0; v_buf.sem_op = 1; v_buf.sem_flg = SEM_UNDO; if (semop(semid, &v_buf, 1) == -1) { perror("v(semid) failed"); exit(1); } return (0); }
/* testsem -- 세마포 루틴을 테스트한다. */ #include "pv.h" void handlesem(key_t skey); main() { key_t semkey = 0x200; int i; for (i = 0; i < 3; i++) { if (fork() == 0) { handlesem(semkey); } } } void handlesem(key_t skey) { int semid; pid_t pid = getpid(); if ((semid = initsem(skey)) < 0) { exit(1); } printf("\nprocess %d before critical section\n", pid); p(semid); printf("process %d in critical section\n", pid); sleep(10); /* 다른 child process들이 실행되는 과정을 볼 수 있음 */ printf("process %d leaving critical section\n", pid); v(semid); printf("process %d exiting\n", pid); exit(0); }
여기서 initsem()은 semctl의 예시 코드에 작성되어 있다.
'Computer Science > OS' 카테고리의 다른 글
[Unix Programming] ipcs & ipcrm (0) | 2021.12.13 |
---|---|
[Unix Programming] Shared Memory (0) | 2021.12.13 |
[Unix Programming] Message Passing (0) | 2021.12.04 |
[Unix Programming] IPC 기본 개념 (0) | 2021.12.03 |
[Unix Programming] Record Locking (0) | 2021.12.02 |
댓글
이 글 공유하기
다른 글
-
[Unix Programming] ipcs & ipcrm
[Unix Programming] ipcs & ipcrm
2021.12.13 -
[Unix Programming] Shared Memory
[Unix Programming] Shared Memory
2021.12.13 -
[Unix Programming] Message Passing
[Unix Programming] Message Passing
2021.12.04 -
[Unix Programming] IPC 기본 개념
[Unix Programming] IPC 기본 개념
2021.12.03
댓글을 사용할 수 없습니다.