세마포어를 사용하는 과제
방법 구상
- 프로세스, 세마포어 관련 개념 및 함수 숙지
- 세마포어로 임계구역을 보호
- 모니터링을 위한 별도의 스레드/프로세스 설정
구현 순서
- 인자 파싱
- 초기화
- 프로세스 생성
- 자원 해제
구현 방법
순서도
문제 해결 방법
Bonus 파트는 mandatory와는 조금 다르게 두 철학자 사이에 포크가 놓여 있는 것이 아닌, 한 곳에 모든 포크가 모여있는 구조이다. 따라서 교착상태와 기아상태를 mandatory와는 다른 방법으로 해결하였다.
- 경쟁 상태 (Race condition)
- 읽기와 쓰기가 동시에 일어날 수 있는 변수의 경우 세마포어를 통해 한 번에 한 스레드만이 접근하도록 하였다.
- 교착 상태 (Deadlock)
- 포크를 집는 과정 전체를 감싸는 세마포어를 설정하여 한 철학자가 2개의 포크를 연속으로 집는 것을 보장해주었다.
- 기아 상태 (Starvation)
- 위의 추가사항으로 인해 자연스럽게 철학자들이 순서대로 포크를 2개씩 집게 되므로 기아가 발생할 가능성도 제거하였다.
초기화
세마포어
- 세마포어의 종류
- 계수 세마포어 (Counting Semaphore)
- 세마포어 변수의 값을 포크의 전체 개수로 설정한다.
- 이진 세마포어 (Binary Semaphore)
- 0과 1을 오가며 mutex lock과 유사한 용도로 사용한다.
- 계수 세마포어 (Counting Semaphore)
- sem_open
- 세마포어를 열기 전에 우선 unlink를 하여 최초로 연다는 것을 보장해준다.
info->sem_print = sem_open("/print", O_CREAT, 0600, 1);
- 위와 같이 세마포어를 열어주었다. 두번째 매개변수인 oflag에 O_CREAT만 사용하면 단순히 세마포어를 생성하지만 O_EXCL과 비트연산자 or을 사용하여 함께 설정하게되면 이미 존재하는 세마포어인 경우 오류를 반환한다.
- 세번째 매개변수인 mode에 들어가는 값은 세마포어에 대한 접근권한이다. chmod와 사용방법이 유사하며 8진수의 각 자리수의 값은 차례대로 사용자(owner) 권한, 그룹 권한, 다른 사용자 권한을 의미한다. 과제에서는 세마포어에 접근하는 사용자에게만 읽기(r), 쓰기(w) 권한(rw-)을 주었다.
모니터링 스레드
- 스레드 생성
- pthread_create
- 스레드 해제
- pthread_detach
pthread_join
이 아닌pthread_detach
를 사용한 이유- mandatory에서는 메인 프로세스에서 한번에 스레드들을 생성한 후에 pthread_join을 통해 스레드들이 종료되는 것을 기다렸다.
- 하지만 bonus에서는 자식 프로세스 각각이 모니터 스레드를 생성하고 종료될 때는 exit을 통해 빠져나가기 때문에 join을 호출하는데에 어려움이 있다. 따라서 생성 후에 detach를 해주어 각 스레드들이 스스로 자원을 해제하도록 하였다.
결과물
- 인자가
4 210 100 100 15
인 경우 - 인자가
9 610 200 200 10
인 경우문제점
메모리 누수 발생
- 발생 원인 1
- 우선 아래와 같이 세마포어의 이름을 생성하는 함수 내부에서 메모리 할당 이후에 사용을 마친 변수를 제대로 해제해주지 않았다.
1 2 3 4 5 6 7 8 9
char *generate_sem_name(char *prefix, int i) { char *name; char *index; index = ft_itoa(i); name = ft_strjoin(prefix, index); return (name); }
- 해결 방법 1
- 세마포어 이름을 생성하는 함수 내부에서 할당하여 사용을 끝낸 변수는 free 해주었다.
발생 원인 2
- 위와 같이 문자열을 할당받은 메모리에 저장해서 unlink하는 식의 코드를 세마포어의 이름이 필요할 때마다 사용하고 해제하지 않는 오류를 범했다.
- 해결 방법 2
- 철학자 구조체의 세마포어명이 필요한 경우, 매 순간 이름을 생성하지 않고 최초 1회 생성 후 구조체에 저장하여 사용한 뒤, 종료 직전에 free하였다.
- 해당 부분을 수정하니 더이상의 메모리 누수는 발생하지 않았다.
- 발생 원인 1
철학자의 사망 메시지가 프로그램의 시작 직후 출력되는 문제
- 원인
- 철학자 프로세스가 시간 변수에 값을 저장하기 전에 모니터가 값에 접근해서 발생
- 해결 방법
- 시간 변수를 초기화하는 시점을 모니터 스레드가 생성되기 전으로 변경
- 원인
철학자의 사망 메시지를 출력한 이후에도 다른 철학자 프로세스가 동작하는 문제
원인
1 2 3 4 5 6
struct s_information { ... int is_die_printed; ... };
처음엔 위와 같이 info 구조체에 사망 출력 여부를 나타내는 변수를 설정하고 아래 함수로 변수를 체크해서 한 철학자 프로세스만 사망 메시지를 출력하도록 하려고 했다.
1 2 3 4 5 6 7 8 9 10 11
int is_die_printed(t_info *info) { sem_wait(info->sem_monitor); if (info->is_die_printed == TRUE) { sem_post(info->sem_monitor); return (TRUE); } sem_post(info->sem_monitor); return (FALSE); }
하지만 이렇게 작성해도 한 철학자의 사망을 다른 철학자 프로세스들이 알 수 없었다. fork된 이후에는 각 프로세스는 자신만의 주소 공간에 프로세스의 사본을 갖기 때문에 변수의 값이 공유되지 않는다는 개념을 놓쳤기 때문에 범한 실수였다.
해결 방법
- 각 철학자를 감시하는 모니터링 스레드가 사망을 인지하게되면 sem_wait을 통해 프로세스끼리 공유하는 세마포어를 block해두어 다른 프로세스가 출력하지 못하도록 막아주었다.
- 이후 자신의 프로세스를 status = 1로 종료하게되면 메인 프로세스에서 다른 철학자 프로세스들을 SIGKILL로 직접 종료해주었다.
Ref.
https://stackoverflow.com/questions/4298678/after-forking-are-global-variables-shared
https://stackoverflow.com/questions/71765047/why-we-unlink-semaphores-before-we-initializes-them