이 영역을 누르면 첫 페이지로 이동
Arc 블로그의 첫 페이지로 이동

Arc

페이지 맨 위로 올라가기

Arc

[Unix Programming] Semaphore

  • 2021.12.04 17:54
  • Computer Science/OS
글 작성자: SeoArc

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

댓글

이 글 공유하기

  • 구독하기

    구독하기

  • 카카오톡

    카카오톡

  • 라인

    라인

  • 트위터

    트위터

  • Facebook

    Facebook

  • 카카오스토리

    카카오스토리

  • 밴드

    밴드

  • 네이버 블로그

    네이버 블로그

  • Pocket

    Pocket

  • Evernote

    Evernote

다른 글

  • [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
다른 글 더 둘러보기

정보

Arc 블로그의 첫 페이지로 이동

Arc

  • Arc의 첫 페이지로 이동

검색

메뉴

  • 홈
  • 태그
  • 방명록

카테고리

  • 분류 전체보기 (106)
    • Language (28)
      • C++ (0)
      • C# (0)
      • Java (28)
    • Algorithm (47)
      • Algorithm (15)
      • Data Structure (6)
      • PS (26)
    • Computer Science (22)
      • Design Pattern (1)
      • Network (14)
      • OS (7)
    • Game (0)
      • Unity (0)
    • Backend (3)
      • Spring (1)
      • JPA (2)
    • DB (0)
      • SQL (0)
    • DevOps (2)
      • AWS (0)
      • Docker (2)
      • Jenkins (0)
      • Nginx (0)
    • Software Engineering (4)
      • OOP (4)
    • AI (0)
      • Machine Learning (0)
    • Others (0)

최근 글

인기 글

댓글

공지사항

아카이브

태그

  • graph
  • 그래프
  • algorithm
  • java
  • 알고리즘
  • 자바
  • network
  • 네트워크

나의 외부 링크

정보

SeoArc의 Arc

Arc

SeoArc

블로그 구독하기

  • 구독하기
  • RSS 피드

방문자

  • 전체 방문자
  • 오늘
  • 어제

티스토리

  • 티스토리 홈
  • 이 블로그 관리하기
  • 글쓰기
Powered by Tistory / Kakao. © SeoArc. Designed by Fraccino.

티스토리툴바