안녕하세요. 이번에는 앞서다룬 IPC기법들 중 동기화가 필요한 통신에서의 동기화를 책임질 세마포어(Semaphore)에 대하여 알아보도록 하겠습니다.
● 세마포어(Semaphore)
-> 기본적으로 세마포어는 프로세스 사이의 동기를 맞추는 기능을 제공합니다.
-> 한번에 한 작업만을 허용하는 부분에 접근하여 잠금 또는 잠금해제를 제공하는 정수형 변수입니다.즉 A라는 프로세스가 특정 메모리에 접근을 하고있다면 B 및 C 프로세스들이 이 메모리에 접근을 못하도록 막아주는 기능을 하는 변수라고 생각하시면 편하겠습니다. 이 정수형 변수는 제공되는 함수를 통해 값을 변경할 수 있는데요. 잠금 함수는 p(), 해제 함수는 v()로 표시한다고 합니다.
● 세마포어 함수 (System V)
-> 기본적으로 세마포어 함수에는 세마포어를 생성하는 함수, 잠금 및 해제를 도와주는 연산 함수, 제어 함수가 있습니다.
1. 세마포어 생성 : semget(key,세마포어 개수, semflg);
2. 세마포어 제어 : semctl(식별자, 세마포어 번호, 제어명령); // 제어명령으로 주로 IPC_RMID를 씁니다.
3. 세마포어 연산 : semop(식별자, sembuf 구조체 주소, 구조체 크기);
sembuf 구조체는 아래와 같이 3개의 멤버변수가 존재합니다.
unsigned short sem_num; // 세마포어 번호
short sem_op; // 세마포어 연산
short sem_flg; // 연산을 위한 플래그
플래그 값으로는 IPC_NOWAIT, SEM_UNDO가 있는데 SEM_UNDO는 세마포어 반납 전 프로세스가 강제종료가 될 시에 반납을 도와주는 플래그로써 중요하게 볼 필요가 있습니다.
또한 세마포어는 System V방식 뿐만아니라 POSIX 방식이 있는데 이번 글에서는 POSIX에 대해서 좀 더 알아보도록 합시다.
● 세마포어(POSIX)
-> POSIX 규격을 따르는 새로운 세마포어 인터페이스로 전통적인 System V 인터페이스에 비해서 좀 더 명확합니다. 또한 세마포어 집합이란 개념이 없어 이해하기가 수월하다는 장점이 있습니다.
-> 또한 POSIX 세마포어에는 이름있는 세마포어, 이름없는 세마포어 방식이 있는데요. 이름없는 세마포어는 sem_init()으로 생성하며, 이름있는 세마포어는 sem_open을 통해 생성합니다. 이번 포스팅에서는 독립적인 프로세스간의 통신을 목표로 하기 때문에 이름있는 세마포어에 대해서 다뤄보려 합니다.
● 세마포어 함수 (POSIX)
1. 세마포어 식별자 생성 : sem_open( const char * sem_name, int oflags, ... );
식별자 생성의 인자로는 아래와 같습니다.
sem_name : 생성 또는 접근하고자 하는 세마포어의 이름
oflags : 세마포어 생성 시 플래그 // 보통 O_CREAT를 사용합니다.
mode_t mode : 세마포어 접근권한을 설정합니다. // 0666
unsigned int value : 세마포어 초기 값으로 0 보다 큰 양수여야 하며, unlock된 세마포어의 수를 의미합니다.
2. sem_wait(식별자) : 만약 세마포어 값이 0보다 크면 프로세스는 세마포어를 얻고 세마포어를 감소하고 즉시 반환합니다. 세마포어값이 0이라면 세마포어가 0보다 더 커지거나 시그널이 발생할 때까지 대기합니다.
3. sem_post(식별자) : 세마포어를 되돌려주며, 세마포어 값이 하나 증가합니다.
4. sem_unlink(식별자) : 세마포어를 삭제합니다. 이름있는 세마포어를 삭제하고자 할때 사용됩니다.
5. sem_close(식별자) : 가지고 있는 세마포어를 닫을 때 사용되는 함수입니다.
● 다음은 POSIX 세마포어를 이용한 예제입니다.
/* mom.c */ #include <stdio.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <sys/types.h> #include <stdlib.h> #include <semaphore.h> #include <pthread.h> int main(int argc, char* argv[]) { int fd; char *name = "semaphore"; int VALUE = 1; sem_t *semid; // 이름있는 세마포어 식별자를 생성 semid = sem_open(name, O_CREAT, 0666, VALUE); //fridge라는 파일을 생성 및 open fd=open("fridge", O_CREAT|O_RDWR|O_APPEND, 0777); printf("Mom comes home. \n"); sem_wait(semid); // 세마포어를 얻음 printf("Mom checks the fridge. \n"); if(lseek(fd, 0, SEEK_END)==0) { // fridge 파일에 아무 값도 없을 경우 printf("Mom goes to buy milk...\n"); sleep(10); //10초 대기 write(fd, "milk ", 5); // milk 포함 5글자를 write printf("Mom puts milk in fridge and leaves. \n"); if(lseek(fd,0,SEEK_END)>5) // 10초 사이 파일에 5글자 이상이 쓰여있다면 printf("What a waste of food! The fridge can not hold so much milk!\n"); } else{ // 파일에 값이 있을 경우 printf("Mom closes the fridge and leaves. \n"); } sem_post(semid); //세마포어를 반환 close(fd); sem_close(semid); // 세마포어 닫기 sem_unlink(name); // 세마포어 삭제 return 0; } |
/* dad.c */ #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <unistd.h> #include <semaphore.h> #include <pthread.h> int main(int argc, char* argv[]) { int fd; char *name = "semaphore"; sem_t *semid; int VALUE = 1; semid = sem_open(name, O_CREAT, 0666, VALUE); printf("dad comes home. \n"); sleep(3); // 3초 대기 printf("dad checks the fridge. \n"); while(1) { //무한루프 if(sem_wait(semid) == -1) sleep(1); // 세마포어를 못 얻을 경우 else { // 세마포어를 얻은 경우 fd=open("fridge", O_CREAT|O_RDWR|O_APPEND, 0777); if(lseek(fd, 0, SEEK_END)==0) { printf("dad goes to buy milk...\n"); sleep(2); write(fd, "milk ", 5); printf("dad puts milk in fridge and leaves. \n"); if(lseek(fd,0,SEEK_END)>5) printf("What a waste of food! The fridge can not hold so much milk!\n"); break; } else{ printf("dad closes the fridge and leaves. \n"); break; } } } close(fd); sem_post(semid); sem_close(semid); sem_unlink(name); return 0; } |
위 예제소스는 mom.c 와 dad.c 두 개의 프로그램이 각각 fridge 파일을 열어 안에 값이 없는 경우엔 write 하고, 있는 경우에는 파일을 닫는 내용입니다.
두 개의 독립 프로세스가 동기없이 fridge 파일을 동시에 열어 값을 쓰게된다면 아마 milk라는 값이 두번 쓰여지게 되겠지만, 세마포어를 사용하여 한쪽에서 세마포어를 얻으면 다른 한쪽에서는 파일 입출력 코드 부분에 접근하지 못하도록 설계하여 정상적으로 한 개의 milk만이 쓰여지는걸 확인할 수 있게됩니다.
아래는 결과 영상입니다.
이처럼 세마포어는 두 개의 독립 프로세스가 하나의 메모리 영역을 동시에 다루려고 할때 벌어지는 데이터 손상을 막기위해 동기를 맞춰주는 역할을 수행하며, System V 방식 및 POSIX 방식 두 가지 방식이 있다는걸 알아봤습니다.
본 포스팅에서는 다루기 쉬운 POSIX방식에 대한 함수 및 예제들에 대해 자세히 다루어 보았습니다.
'CS > 시스템 프로그래밍' 카테고리의 다른 글
시스템 V IPC (System V) 2 -Infinite (0) | 2022.06.21 |
---|---|
시스템 V IPC (System V) 1 -Infinite (0) | 2022.06.21 |
파이프(PIPE) -Infinite (0) | 2022.06.20 |
시그널(signal) -Infinite (0) | 2022.06.20 |
쓰레드(Thread) -Infinite (0) | 2022.06.20 |
개발 기술 블로그, Dev
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!