[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