🔔 학교 강의를 바탕으로 개인적인 공부를 위해 정리한 글입니다. 혹여나 틀린 부분이 있다면 지적해주시면 감사드리겠습니다.
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
로 초기화