
🔔 학교 강의를 바탕으로 개인적인 공부를 위해 정리한 글입니다. 혹여나 틀린 부분이 있다면 지적해주시면 감사드리겠습니다.
semaphore의 p와 v를 의사 코드로 나타내면 아래와 같다.
p(sem) or wait(sem) {
if (sem != 0)
decrement sem by one
else
wait until sem becomes non-zero, then decrement
}
----------------------------------------------------
v(sem) or signal(sem) {
increment sem by one
if (queue of waiting processes not empty)
restart first process in wait queue
}
p는 sem이 0보다 크면 1을 감소시키며, 0이면 감소시킬 수 있을때까지 대기한다. v는 sem을 1 증가시킨다. sem의 값은 v와 p에 의한 증가 감소에 의해 0보다 작아지면 안된다.
System V의 semaphore은 semaphore 요소의 배열 형태로 구성되며, process는 단일 호출로 전체에 대한 작업 수행이 가능하다.
/* <sys/sem.h> */
struct semid_ds {
struct ipc_perm sem_perm; /* permission */
struct sem *sem_base; /* pointer to array of semaphores in set */
ushort_t sem_nsems /* # of semaphores in set */
time_t sem_otime; /* last semop() time */
time_t sem_ctime; /* last change time */
};
semid_ds
sem_perm: semaphore permission*sem_base:sem_nsems:sem_otime:sem_ctime:
semaphore는 아래와 같이 구성되어 있다.
struct sem {
ushort_t semval; /* semaphore value, unsigned */
short sempid; /* PID of last successful semop() */
ushort_t semncnt; /* # of awaiting (semval > current value) */
ushort_t smezcnt; /* # of awaiting (semval = 0) */
};
Semaphore element
semval: semaphore 값을 나타내는 양의 정수sempid: semaphore을 조작한 마지막 process의 pidsemncnt: semaphore가 증가하기를 기다리는 process 수semzcnt: semaphore가 0이 되기를 기다리는 process 수

위와 같이 semaphore element의 array로 이루어진다.
#include <sys/sem.h>
int semget(key_t key, int nsems, int permflag);
semget을 사용해 전달받은 key와 연결된 semaphore identifier을 얻을 수 있다.
Return
- 성공 시
return identifier- error 발생 시
return -1
Arguments
nsems: set의 semaphore element의 수 (nsems == 0일 경우 기존 set 참고)permflag
-IPC_CREAT:O_CREAT
-IPC_EXCL:OEXCL
이는 아래와 같이 사용하며, IPC_PRIVATE가 사용될 경우, IPC_CREAT가 있어야 한다. 또한 nsems == 0인 경우에는 기존의 것을 참고하는 의미이기 때문에 IPC_CREAT와 IPC_EXCL와 같이 쓰이면 오류가 발생하기 때문에 주의해야 한다.
Errors
EACCES:key에 대한 semaphore가 존재하나, 접근 권한 없음EEXIST:key에 대한 semaphore가 존재하나,IPC_CREAT혹은IPC_EXCL이 지정됨EINVAL
-nsems < 0이거나 system 제한보다 큰 경우
-key에 대한 semaphore가 존재하나nsems가 해당 semaphore보다 큰 경우ENOENT:key에 대한 semaphore가 존재하지 않으며IPC_CREAT가 지정되지 않았음ENOMEM: system 메모리 부족ENOSPC: semaphore 최대 개수 초과
#include <errno.h>
#include <sys/sem.h>
#include <sys/stat.h>
int main(int argc, char **argv) {
key_t skey;
int semid;
if (argc != 3) {
fprintf(stderr, “Usage: %s pathname id\n”, argv[0]);
return 1;
}
if ((skey = ftok(argv[1], atoi(argv[2]))) == (key_t)-1) {
fprintf(stderr, “failed to derive key from filename %s\n”, argv[1]);
return 2;
}
if ((semid = semget(skey, 2, 0660 | IPC_CREAT)) == -1) {
fprintf(stderr, “failed to create semaphore with key %d\n”, (int)skey);
return 3;
}
printf(“semid = %d\n”, semid);
return 0;
}
위 코드는 인자로 받은 path와 id를 통해 key를 받아, 해당 key를 이용하여 semaphore identifier를 받아 출력하는 코드이다.
#include <sys/sem.h>
int semctl(int semid, int sem_num, int cmd, union semun ctl_arg);
semctl은 cmd에 따라 semid와 sem_num에 대해 특정 기능을 수행한다. ctl_arg는 union이며, structure과는 달리 공간이 하나만 존재하며, 사용을 어떻게 하는지에 따라 값이 달라진다.
Return
cmd에 따라return이 다름
Argument
sem_num: semaphore set에서 몇 번째 element인지 의미 (like index)cmd: command typectl_arg:cmd에 따라 해당 값을 사용union semun { int val; /* for SETVAL */ struct semid_ds *buf; /* for IPC_STAT and IPC_SET */ unsigned short *array; /* for GETALL and SETALL */ };
cmd를 정리하면 아래와 같다.
Standard IPC functions
IPC_STAT semid_ds의 정보를ctl_arg.buf에 복사IPC_SET ctl_arg.buf로 semaphore set의 permission을 setIPC_RMID semid에 해당하는 semaphore을 삭제
Single semaphore operations
GETVAL 특정 semaphore의 value를 return SETVAL ctl_arg.val로 특정 semaphore의 value를 설정GETPID 마지막으로 semaphore element를 변경한 PID를 return GETNCNT semaphore element가 증가하기를 기다리는 process의 개수 return GETZCNT semaphore element가 0과 같아지기를 기다리는 process의 개수 return
All semaphore operations
GETALL ctl_arg.array에 semaphore의 value를 담아 returnSETALL ctl_arg.array로 semaphore의 value를 set
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int init_sem (key_t semkey) {
int status = 0, semid;
if ((semid = semget(semkey, 1, 0600 | IPC_CREAT)) == -1) {
perror (“init_sem failed”);
return -1;
}
else {
semun arg;
arg.val = 1;
status = semctl(semid, 0, SETVAL, arg);
}
return semid;
}
위의 예시코드와 같이 semaphore의 각 element는 사용되기 전에 semctl로 초기화해야 한다.
#include <sys/sem.h>
int semop(int semid, struct sembuf *op_array, size_t num_ops);
semop는 semid에 대한 semaphore set에 증가 혹은 감소 동작을 수행한다.
Return
- 성공 시
return 0- error 발생 시
return -1
Arguments
num_op:op_array에 있는sembuf수op_array: semaphore에 대한 연산 배열이며, 아래와 같은 형태를 요소로 가지는 배열struct sembuf { unsigned short sem_num; /* index number of semaphore in set */ short sem_op; /* see next page */ short sem_flg; /* IPC_NOWAIT, SEM_UNDO */ };
sem_op: 실질적인 동작을 의미하며 값에 따라 동작이 달라짐
-< 0:p(),lock,sem_op만큼 value를 감소시킴
- orsem_val이sem_op의 절댓값보다 크거나 같아질때까지 block
-> 0:v(),unlock,sem_op만큼 value를 증가시킴
-= 0: semaphore가 0인지를 테스트하며,semval = 0까지 blocksem_flag
-IPC_NOWAIT:sem_op <= 0일때까지 기다리지 않으며,errno = EAGAIN과 함께return -1
-SEM_UNDO: process가 죽었을 경우, semaphore을 원래대로 복구하며, 이는 비정상 종료를 방지하고,p이후v를 하지 않았을 경우를 해결함
-> 하나라도NO_WAIT이 존재하면 나머지도 동일하게NO_WAIT으로 작동함
header
/* pv.h -- header for semaphore */ #include <sys/types.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; ushore *array; } semun;
semaphore 생성 + 초기화
#include <sys/sem.h> #include <sys/ipc.h> #include <sys/sem.h>
int init_sem (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;
}
> #### p
```c
/* p.c -- p() or lock() */
#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
/* v.c -- v() or unlock() */ #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;
}
> #### main
```c
/* testsem */
#include “pv.h”
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); /* do something interesting */
printf(“process %d leaving critical section\n”, pid);
v(semid);
printf(“process %d exiting\n”, pid);
exit (0);
}
int main() {
key_t semkey = 0x200;
for (int i = 0; i < 3; i++) {
if (fork() == 0) handlesem(semkey);
}
return 0;
}
위 예시 코드는 semkey를 지정하고 fork를 3번 진행하여, 각 child process는 handlesem을 실행하는 코드이다. 아래의 실행 결과를 보면 확인할 수 있듯이, critical section에 동시에 2개의 process가 들어갈 수는 없음을 확인할 수 있다.
$ ./testsem
process 799 before critical section
process 799 in critical section
process 800 before critical section
process 801 before critical section
process 799 leaving critical section
process 801 in critical section
process 799 exiting
process 801 leaving critical section
process 801 exiting
process 800 in critical section
process 800 leaving critical section
process 800 exiting
shared memory는 process들이 동일한 memory segment에 접근하여 읽고 쓸 수 있도록 한다. client와 server 간에 data를 복사할 필요가 없기 때문에 가장 빠른 형태의 IPC이며, server가 data를 shared memory에 배치하는 경우, server가 완료될 때까지 client는 data에 접근을 시도할 수 없다.
semaphore가 shared memory access를 동기화하는 데 사용되기도 한다.
/* <sys/shm.h> */
struct shmid_ds {
struct ipc_perm shm_perm; /* permission */
size_t shm_segsz; /* size of segment in bytes */
pid_t shm_lpid; /* pid of last shmop() */
pid_t shm_cpid; /* pid of creator */
shmatt_t shm_nattch /* number of current attaches */
time_t shm_atime; /* last attach time */
time_t shm_dtime; /* last detach time */
time_t shm_ctime; /* last change time */
...
};
#include <sys/shm.h>
int shmget(key_t key, size_t size, int permflags);
shared memory segment는 shmget에 의해 생성된다.
Return
- 성공 시
return shared memory ID- error 발생 시
return -1
Errors
EACCES:key에 대한shmid가 있으나 권한이 없음EEXIST:key에 대한shmid가 있으나IPC_CREAT및IPC_EXCL가 지정됨EINVAL:shared memory segment가 생성되지 않았으나, 크기가 system 제한 혹은key의 segment 크기와 일치하지 않음ENOENT:key에 대한shmid가 존재하지 않으며,IPC_CREAT가 지정되지 않음ENOMEM: system memory 부족ENOSPC:shmid의 최대 개수 초과
#include <sys/shm.h>
void *shmat(int shmid, const void *daddr, int shmflags);
shmat은 shmid로 지정된 shared memory segment를 calling process의 address space에 연결하고, shmid에 대한 shm_nattch 값을 증가시킨다. 일반적으로 daddr은 0을 사용하는 것을 추천하며, 이는 heap이 해당 영역을 침범할 수 있기 때문이다.
Return
- 성공 시 shared memory segment에 대한 pointer return
- error 발생 시
return -1
Arguments
daddr
- 0일 경우, segment가 kernel이 선택한 사용 가능한 첫 번째 주소에 연결됨
- 0이 아니고SHM_RND가 지정되지 않은 경우,daddr의 주소에 연결됨
- 0이 아니고SHM_RND가 지정된 경우daddr을SHMLBA로 나눈 나머지의 주소에 연결됨

#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *shm_stat);
shmctl은 shmid에 의한 shared memory segment에 대해 cmd에 따른 특정 기능을 수행한다.
Returns
- 성공 시
return 0- error 발생 시
return -1
Arguments
shmid: shared memory segment IDcmd
-IPC_STAT
-IPC_SET
-IPC_RMID
-SHM_LOCK: shared memory segment를 lock
-SHM_UNLOCK: shared memory segment를 unlockshm_stat:shmid_ds구조체 포인터
/* share_ex.h */
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#define SHMKEY1 (key_t) 0x10
#define SHMKEY2 (key_t) 0x15
#define SEMKEY (key_t) 0x20
#define SIZ 5*BUFSIZ
struct databuf {
int d_nread;
char d_buf[SIZ];
};
typedef union _semun{
int val;
struct semid_ds *buf;
ushort *array;
} semun;
/* share_init.c */
#include “share_ex.h”
#define IFLAGS (IPC_CREAT | IPC_EXCL)
#define ERR ((struct databuf *) -1)
static int shmid1, shmid2, semid;
void getseg(struct databuf **p1, struct databuf **p2) {
/* create shared memory segment */
if ((shmid1 = shmget(SHMKEY1, sizeof(struct databuf),
0600 | IFLAGS)) == -1)
fatal(“shmget”);
if ((shmid2 = shmget(SHMKEY2, sizeof(struct databuf),
0600 | IFLAGS)) == -1)
fatal(“shmget”);
/* attach shared memory segment */
if ((*p1 = (struct databuf *) shmat(shmid1, 0, 0)) == ERR)
fatal(“shmat”);
if ((*p2 = (struct databuf *) shmat(shmid2, 0, 0)) == ERR)
fatal(“shmat”);
}
/* get semaphore set */
int getsem(void) {
semun x;
x.val = 0;
if ((semid = semget(SEMKEY, 2, 0600 | IFLAGS)) == -1)
fatal(“semget”);
if (semctl(semid, 0, SETVAL, x) == -1)
fatal(“semctl”);
if (semctl(semid, 1, SETVAL, x) == -1)
fatal(“semctl”);
return (semid);
}
/* remove shmids and semid */
void remobj (void) {
if (shmctl(shmid1, IPC_RMID, NULL) == -1)
fatal(“shmctl”);
if (shmctl(shmid2, IPC_RMID, NULL) == -1)
fatal(“shmctl”);
if (semctl(semid, IPC_RMID, NULL) == -1)
fatal(“semctl”);
}
/* reader.c */
#include “share_ex.h”
struct sembuf p1 = {0, -1, 0}, p2 = {1, -1, 0};
struct sembuf v1 = {0, 1, 0}, v2 = {1, 1, 0};
void reader (int semid, struct databuf *buf1, struct databuf *buf2) {
for(;;) {
buf1->d_nread = read(0, buf1->d_buf, SIZ);
semop(semid, &v1, 1);
semop(semid, &p2, 1);
if (buf1->d_nread <= 0)
return;
buf2->d_nread = read(0, buf2->d_buf, SIZ);
semop(semid, &v1, 1);
semop(semid, &p2, 1);
if (buf2->d_nread <= 0)
return;
}
}
/* writer.c */
#include “share_ex.h”
extern struct sembuf p1, p2;
extern struct sembuf v1, v2;
void writer (int semid, struct databuf *buf1, struct databuf *buf2) {
for(;;) {
semop(semid, &p1, 1);
semop(semid, &v2, 1);
if (buf1->d_nread <= 0)
return;
write(1, buf1->d_buf, buf1->d_nread);
semop(semid, &p1, 1);
semop(semid, &v2, 1);
if (buf2->d_nread <= 0)
return;
write(1, buf2->d_buf, buf2->d_nread);
}
}
/* shmcopy.c */
#include “share_ex.h”
int main() {
int semid;
pid_t pid;
struct databuf *buf1, *buf2;
semid = getsem();
getseg(&buf1, &buf2);
switch (pid=fork()) {
case -1:
fatal(“fork”);
case 0:
writer(semid, buf1, buf2);
remobj();
break;
default:
reader(semid, buf1, buf2);
break;
}
return 0;
}
위 코드는 semaphore의 값을 기준으로 fork 기준 child일 경우 write, parent인 경우 read를 반복하며, 실행 과정을 그림으로 나타내면 아래와 같다.

union semun arg;
arg.val = 1;
status = semctl(semid, 0, SETVAL, arg);
semid의 0번 semval을 arg.val로 초기화한다.
union semun arg;
unsigned short array[3] = {1, 3, 2};
arg.array = array;
status = semctl(semid, 0, SETALL, arg);
semid의 sem[0], sem[1], sem[2]를 arg.array로 초기화