<semaphore.h>에는 semaphore를 다룰 수 있는 함수들이 다수 존재한다. <semaphore.h> 내에서 사용할 수 있는 semaphore는 Named Semaphore와 Unnamed Semaphore로 나뉘는데, Mac OS X에서는 Unnamed Semaphore를 Deprecated로 두어 이와 관련된 함수들을 허용하지 않는다. 물론 Linux 상에서는 <semaphore.h>에 존재하는 모든 함수들을 이용할 수 있지만, 여기서는 Mac OS X에서 사용할 수 있는 함수들만 다룬다는 것을 사전에 밝힌다.
또한 단순히 semaphore를 쓸 수 있고 없고에 대한 공부도 좋지만, Async-Signal-Safe, Thread-Safe, Reentratn와 같은 키워드를 접함으로써 이들에 대한 개념 정리를 해보는 것도 좋겠다는 생각이 들었다. 따라서 제시된 3가지 키워드에 대해서도 꼭 찾아보는 것을 권한다.
1. Semaphore
1) sem_open
함수 원형
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
C
복사
함수 인자
semaphore의 이름을 const char * 타입의 name으로 결정할 수 있다.
oflag는 sem_open을 수행 작업에 대한 설정 값으로 O_CREAT와 O_EXCL을 사용할 수 있다.
mode는 semaphore에 대한 권한 설정 값으로 8진수 값으로 기재하면 되는데, <stat.h>를 포함할 시에는 해당 헤더 내에 있는 매크로 값을 이용할 수도 있다.
value는 semaphore가 보유하고 있는 LOCK의 수를 설정할 때 이용된다.
반환 값
sem_open 작업을 성공했을 때는 semaphore에 대한 주소 값을 반환한다. 실패 시에는 SEM_FAILED라는 매크로 값을 반환한다.
sem_t는 int를 단순히 타입 정의한 타입에 불과하다. 이는 일반적으로 파일 디스크립터 값을 나타내기 때문에 sem_t *는 파일 디스크립터 값을 참조하고 있는 포인터에 불과하다. SEM_FAILED는 (sem_t *)-1 값이기 때문에 포인터 변수 내에 모든 비트가 1로 채워진 값을 의미한다.
참고
O_CREAT은 semaphore 생성에 대한 값이고, O_EXCL은 exclude를 의미하여 기존에 이미 name이라는 semaphore가 유지되고 있는지 확인하는 값이다.
함수 원형이 2개가 주어졌는데, oflag의 값으로 O_CREAT이 포함되는 경우에는 필수적으로 인자가 4개짜리 함수 원형을 이용해야 한다. 인자가 2개짜리인 함수 원형은 O_EXCL을 사용할 때만 이용한다.
oflag의 값은 생략할 수 없기 때문에 필수적으로 값을 채워야하므로 O_EXCL을 수행할 것이 아니라면 반드시 O_CREAT는 포함될 수 밖에 없다. 따라서 이미 존재하는 semaphore에 대해서 sem_open을 수행해야 한다면, O_CREAT이 포함됨에 따라 semaphore를 또 생성하게 되는 것 아닌가 라는 의문과 인자가 4개짜리인 함수를 이용해야 하니 mode와 value 값을 필수적으로 기재함에 따라 기존 값에 영향을 끼치는 것이 아닌가 하는 의문이 생길 수 있다.
우선 O_CREAT은 name에 대한 semaphore가 존재하지 않을 때만 semaphore를 생성한다. 그리고 name에 대한 semaphore가 이미 존재할 때 O_CREAT을 통해 sem_open이 수행되면, 이 때 인자로 사용한 mode와 value는 무시된다.
즉, mode와 value는 기존 값을 유지한다.
O_EXCL이 포함된 경우에 sem_open 함수는 name이라는 semaphore가 이미 존재할 경우에 문제 상황에 대한 errno를 반환하도록 동작한다.
oflag에 대해서 <fcntl.h>의 다른 매크로 값들을 사용하는 행위는 코드의 이식성을 저하하므로 사용하지 않도록 한다. 일부 시스템에서는 이들을 Undefined Behavior로 보기도 하고, 무시하기도 하기 때문이다.
2) sem_unlink
함수 원형
int sem_unlink(const char *name);
C
복사
함수 인자
semaphore를 삭제할 수 있도록 cosnt char * 타입의 name 인자를 사용한다.
반환 값
sem_unlink 작업을 성공했을 때는 0을 반환한다. 실패 시에는 -1을 반환한다.
참고
semaphore는 open 후 close를 한다고 해서 시스템 상에서 없어지지 않는다. 물론 시스템을 재부팅하게 되면 기존에 이용하고 있던 semaphore들은 삭제되지만, 그 전까지는 컴퓨팅 자원을 소모하고 있게 된다. 따라서 사용하지 않는 semaphore에 대해서는 반드시 sem_unlink를 통해 삭제해줘야 한다.
sem_unlink는 semaphore의 최초 sem_open 호출 직후에 바로 호출되기도 한다. 이에 따라 semaphore가 생성되자마자 삭제되는 것처럼 보이지만, 실제로는 그렇지 않다. 프로세스 혹은 쓰레드에 의해 참조되고 있지 않는 semaphore는 즉시 삭제되지만, 단 하나의 작업이라도 semaphore를 sem_open 하여 사용하고 있다면 sem_close를 통해 semaphore의 참조 값이 0이 되기 전까지는 삭제되지 않는다. 따라서 sem_unlink를 미리 호출해두어도 semaphore를 즉시 삭제하지 않는 것이다.
3) sem_close
함수 원형
int sem_close(sem_t *sem);
C
복사
함수 인자
대상이 되는 semaphore의 주소를 인자로 받는다.
반환 값
sem_unlink 작업을 성공했을 때는 0을 반환한다. 실패 시에는 -1을 반환한다.
참고
close 함수와 동일하게, 프로세스가 종료되면 sem_open된 semaphore에 대해 자동으로 sem_close를 수행한다. 이는 execve와 같은 exec 계열 함수에 대해서도 자동으로 sem_close가 수행된다.
4) sem_trywait
함수 원형
int sem_trywait(sem_t *sem);
C
복사
함수 인자
LOCK을 취득하고자 하는 semaphore의 주소를 인자로 받는다.
반환 값
sem_trywait 작업을 성공했을 때는 0을 반환한다. 실패 시에는 -1을 반환한다.
참고
LOCK을 취득할 수 있다면 LOCK을 취득한 뒤에 함수가 종료되고, 취득할 수 있는 LOCK이 없어도 함수가 종료된다. sem_trywait에 대해선 취득할 수 있는 LOCK이 없는 것도 함수 수행 실패로 간주되어 -1이 반환된다.
5) sem_wait
함수 원형
int sem_wait(sem_t *sem);
C
복사
함수 인자
LOCK을 취득하고자 하는 semaphore의 주소를 인자로 받는다.
반환 값
sem_wait 작업을 성공했을 때는 0을 반환한다. 실패 시에는 -1을 반환한다.
참고
LOCK을 취득할 수 있다면 LOCK을 취득한 뒤에 함수가 종료되고, 취득할 수 있는 LOCK이 없으면 sem_trywait과는 달리 Sleep 상태로 대기했다가 LOCK을 얻는다. LOCK을 당장 얻을 수 있는지 없는지 여부가 중요하지 않음에도 sem_trywait과 같이 int 값을 반환하는 이유는 문제 상황을 감별하기 위해서이다.
6) sem_post
함수 원형
int sem_post(sem_t *sem);
C
복사
함수 인자
LOCK을 반납할 수 있도록 LOCK을 갖고 있는 semaphore의 주소를 인자로 받는다.
반환 값
sem_unlink 작업을 성공했을 때는 0을 반환한다. 실패 시에는 -1을 반환한다.
참고
sem_post의 호출은 semaphore의 현재 LOCK 수를 증가 (increment)하는 작업을 유도한다. 이 때 결과적으로 semaphore의 현재 LOCK 수는 적어도 0보다 크게 되는데, sem_wait에 의해 Sleep 상태로 있는 다른 프로세스 혹은 쓰레드를 깨워서 LOCK을 잡을 수 있도록 해준다.