모각코 - 세마포어

김필모·2024년 3월 22일

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


Lock Mecahnism

세마 포어는 다익스트라가 고안한 상호배제의 개념에서 출발해 하나의 동기화 및 락 메커니즘을 구현한 도구인데요. linux의 lock mechanism은 다음과 같은 종류가 있습니다.

  • Counting semaphore (0, 1, 2, ...)
    • 복수 개의 자원 카운팅이 가능한 세마포어
  • Binary semaphore(0, 1)
    • 1개의 자원 카운팅이 가능한 세마포어
    • 있거나? 없거나
  • Mutex
    • 독점적인 획득을 가능하게 하는 특수한 형태의 락
  • Spin Lock
    • contex switching을 막기 위해서 사용함 -> cpu 안뺏기려구 ㅎ
    • 매우 빠른 특수한 형태의 lock
  • Reader/Writer lock
    • 읽기, 쓰기가 서로 다르게 적용되는 특수한 형태의 lock

그 중 이 글에서는 세마포어에 대해서 집중적으로 다룰건데요. 세마포어는 한 번에 한 프로세스만 작업을 수행하는 부분에 접근해 잠그거나, 다시 잠금을 해제하는 기능을 제공하는 정수형 변수에요.
위 사진에 보이는 다익스트라 라는 분이 사용한 용어에 따라 잠금함수는 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 값이 semval에 더해짐

sem_op가 0이라면?

  • semval 값이 0이면 semop는 즉시 리턴
  • semval 값이 0이 아니고 sem_flg에 IPC_NOWAIT가 설정되어 있으면 semop 함수는 즉시 리턴한다.
  • semval 값이 0이 아니고 sem_flg에 IPC_NOWAIT가 설정되어 있지 않으면 semop는 semzcnt를 증가시키고 semval 값이 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 지식을 공유하면서 복습하는 시간을 가졌는데 보람 있었다.

0개의 댓글