프로세스간 공유하는 락 중 하나인 세마포어에 대해 정리한 글입니다.

세마 포어는 다익스트라가 고안한 상호배제의 개념에서 출발해 하나의 동기화 및 락 메커니즘을 구현한 도구인데요. linux의 lock mechanism은 다음과 같은 종류가 있습니다.
그 중 이 글에서는 세마포어에 대해서 집중적으로 다룰건데요. 세마포어는 한 번에 한 프로세스만 작업을 수행하는 부분에 접근해 잠그거나, 다시 잠금을 해제하는 기능을 제공하는 정수형 변수에요.
위 사진에 보이는 다익스트라 라는 분이 사용한 용어에 따라 잠금함수는 p, 해제 함수는 v로 표시합니다. 다익스트라 알고리즘 그 사람 맞아여 ㅎㅎ
P -> Probeer te verlagen -> WAIT
V -> Verhogen -> SIGNAL/POST
N개의 자원이 있다고 가정했을 때 어떻게 동작한는 지 살펴볼게요.
자원을 가져가는 P연산을 하면 N-1이 될 것이고 만약 다 가져가서 자원이 없으면 대기를 합니다.
반대로 자원을 반납하는 V연산을 하면 N+1이 될것이고 대기하고 있는 프로세스가 있다면 시그널을 보내 깨우게 됩니다.
예시 코드를 볼까요?
p(sem) {
while sem=0 do wait;
sem--;
}
v(sem) {
sem++;
if (대기중인 프로세스가 있다면)
대기 중인 첫 번째 프로세스 동작시킴
}
다음으로 세마포어의 생성과 제어, 연산 방법을 알아볼게요.
c언어에서 세마포어 생성은 semget을 사용할 수 있습니다.
int smget(key_t key, int nsems, int semflg);
key: 세마포어(또는 다른 IPC 객체)를 생성하거나 접근할 때 사용되는 유니크한 식별자
nsems: 생성할 세마포어 개수
semflg: 세마포어 접근 속성(IPC_CREAT, IPC_EXCL)
semctl
int semctl(int semid, int semnum, int cmd, ...);
semnum: 기능을 제어할 세마포어 번호
cmd: 수행할 제어 명령
...: 제어 명령에 따라 필요시 사용할 세마포어
cmd에 지정할 수 있는 값은 다양하지만 몇가지만 살펴볼게요
GETVAL: 세마포어의 semval 값을 읽어온다.
SETVAL: 세마포어의 semval 값을 arg.val로 설정한다.
GETPID: 세마포어의 sempid 값을 읽어온다. -> 이 세마포어를 누가 쓰는 지 확인
int semop(int semid, struct sembuf *sops, size_t nsops);
sops: sembuf 구조체 주소
nsops: sops가 가리키는 구조체 크기
sembuf 구조체는 이렇게 생겼어요
struct sembuf {
ushort_t sem_num;
short sem_op;
short sem_flg;
sem_op가 음수이면 P연산에 해당해요.
세마포어 잠금 기능 수행
semval 값이 sem_op의 절댓값과 같거나 크면 semval 값에서 sem_op의 절댓값을 뺀다.
semval 값이 sem_op 값보다 작고 sem_flg에 IPC_NOWAIT가 설정되어 있으면 semop 함수는 즉시 리턴
semval 값이 sem_op 값보다 작은데 sem_flg에 IPC_NOWAIT가 설정되어 있지 않으면 semop 함수는 semcnt 값을 증가시키고 다음 상황 기다리기
semval >= |sem_op| -> semncnt 감소, semval - |sem_op|
시스템에서 semid가 제거된다면 errno가 EIDRM으로 설정되고 -1 리턴
semop 함수를 호출한 프로세스가 시그널을 받는다면 semncnt는 감소하고 시그널 처리
sem_op가 양수라면 V연산에 해당해요.
sem_op가 0이라면?
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
int initsem(key_t semkey) {
union semun semunarg;
int status = 0, semid;
semid = semget(semkey, 1, IPC_CREAT | IPC_EXCL | 0600);
if (semid == -1) {
if (errno == EEXIST)
semid = semget(semkey, 1, 0);
}
else {
semunarg.val = 1;
status = semctl(semid, 0, SETVAL, semunarg);
}
if (semid == -1 || status == -1) {
perror("initsem");
return (-1);
}
return semid;
}
int semlock(int semid) {
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1;
buf.sem_flg = SEM_UNDO;
if (semop(semid, &buf, 1) == -1) {
perror("semlock failed");
exit(1);
}
return 0;
}
int semunlock(int semid) {
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1;
buf.sem_flg = SEM_UNDO;
if (semop(semid, &buf, 1) == -1) {
perror("semunlock failed");
exit(1);
}
return 0;
}
void semhandle() {
int semid;
pid_t pid = getpid();
if ((semid = initsem(1)) < 0)
exit(1);
semlock(semid);
printf("--------------------------------\n");
printf("Lock : Process %d\n", (int)pid);
printf("** Lock Mode : Critical Section\n");
sleep(1);
printf("Unlock : Process %d\n", (int)pid);
semunlock(semid);
exit(0);
}
int main(void) {
int a, stat;
for (a = 0; a < 3; a++)
if (fork() == 0) semhandle();
for(a = 0; a < 3; a++) {
wait(&stat);
}
return 0;
}


팀원들과 OS 지식을 공유하면서 복습하는 시간을 가졌는데 보람 있었다.