[42서울] Philosophers(3)

tamagoyakii·2022년 9월 6일
0

42seoul

목록 보기
12/19
post-thumbnail

필로소퍼의 Mandatory 파트가 스레드와 뮤텍스를 사용하는 과제였다면, Bonus 파트는 프로세스와 세마포어를 사용하는 과제다. 때문에 허용하고 있는 함수가 다르다!

✅ External Functs.

memset, printf, malloc, free, write, fork, kill, exit, pthread_create, pthread_detach, pthread_join, usleep, gettimeofday, waitpid, sem_open, sem_close, sem_post, sem_wait, sem_unlink

새로운 녀석들을 살펴보자.

1. fork

  • syntax :
#include <unistd.h>

pid_t fork(void);
  • description :
    현재 실행중인 프로세스를 복사해 자식 프로세스를 생성한다.
  • return :
    • 성공 시
      부모: 자식 프로세스의 pid / 자식: 0
    • 실패 시
      부모: -1 / 자식: 생성 안됨

2. kill

이 친구는 minitalk 과제를 할 때 사용한 적이 있다.

  • syntax :
#include <signal.h>

int kill(pid_t pid, int sig);
  • description :
    pid 가 양수일 때 sig 를 해당 pid 의 프로세스로 전송한다.
  • return :
    성공 시 0
    실패 시 -1

주요 시그널은 다음과 같다.

필로소퍼 과제에서 kill은 자식 프로세스를 죽이기 위해 사용하는데, 이때 사용되는 signal은 SIGINT 이다. 우리가 쉘에서 실행중인 프로그램을 종료할 때 ctrl + c 단축키를 사용하곤 하는데, 그게 바로 SIGINT 신호다!

3. waitpid

  • syntax :
#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);
  • description :
    pid 인자로 지정한 자식이 상태를 바꿀 때까지 호출 스레드의 실행을 중지한다. 인자의 optionsWNOHANG, WUNTRACED, WCONTINUED의 값을 OR 한 것인데, 나는 사용하지 않았기 때문에 0 으로 두었다. 인자의 pid 의 값은 다음과 같다.

    • < -1 : 프로세스 그룹 ID가 pid의 절댓값과 같은 아무 자식 프로세스나 기다리기
    • -1 : 아무 자식 프로세스나 기다리기
    • 0 : waitpid() 호출 시점에 프로세스 그룹 ID가 호출 프로세스의 프로세스 그룹 ID와 같은 아무 자식 프로세스나 기다리기
    • > 0 : 프로세스 ID가 pid 값과 같은 자식 기다리기
  • return :
    성공 시 : 상태가 바뀐 자식의 프로세스 ID
    실패 시 : -1

4. sem_open

  • syntax :
#include <fcntl.h>		/* For O_* constants */
#include <sys/stat.h>	/* For mode constants */
#include <semaphore.h>

sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
  • description :
    세마포어를 초기화하고 여는 함수다. 인자의 name으로 식별하게 되는데, oflagO_CREATO_EXCL이 함께 선언되어 있는 경우, name의 세마포어가 이미 존재한다면 새로운 세마포어는 생성되지 않고 error를 반환하게 된다. value는 생성될 세마포어 lock의 초깃값으로, SEM_VALUE_MAX 이하의 값을 가져야 한다. mode는 세마포어에 대한 접근 권한을 설정하는 값으로, 8진수의 값을 받지만 <sys/stat.h> 헤더를 포함할 경우 헤더 내의 상수를 사용할 수 있다. 나는 기본 값인 0644를 사용했다. chmod에 대한 값은 아래의 링크에서 확인할 수 있다.
  • return :
    성공 시 새로운 새마포어의 주소
    실패 시 SEM_FAILED

5. sem_close

  • syntax :
#include <semaphore.h>

int sem_close(sem_t *sem);
  • description :
    sem이 가리키고 있는 세마포어를 닫고 사용하는 메모리를 모두 반납한다.
  • return :
    성공 시 0
    실패 시 -1

6. sem_wait

  • syntax :
#include <semaphore.h>

int sem_wait(sem_t *sem);
  • description :
    sem이 가리키는 세마포어를 lock 하여 사용할 수 있는 세마포어의 수를 감소시킨다. 세마포어의 수가 0 보다 크다면 즉시 lock 하고 함수가 실행되지만 현재 세마포어의 값이 0이라면 사용할 수 있는 세마포어가 생기거나 프로세스가 종료될 때까지 기다리게 된다.
  • return :
    성공 시 0
    실패 시 -1 (세마포어의 값이 바뀌지 않는다)

7. sem_post

  • syntax :
#include <semaphore.h>

int sem_post(sem_t *sem);
  • description :
    sem이 가리키는 세마포어를 unlock한다. 즉, 사용 가능한 세마포어의 수를 증가 시켜 sem_wait 중인 다른 프로세스에서 세마포어를 lock 할 수 있도록 한다.
  • return :
    성공 시 0
    실패 시 -1 (세마포어의 값이 바뀌지 않는다)
  • syntax :
#include <semaphore.h>

int sem_unlink(const char *name);
  • description :
    name의 세마포어를 사용중인 프로세스들이 모두 세마포어를 close 했을 때 해당 세마포어를 제거한다.
  • return :
    성공 시 0
    실패 시 -1

✅ Mutex vs. Semaphore

사실 구현하는 건 세마포어를 사용하는 것이 더 쉬웠던 것 같다. 프로세스 단위로 실행되기 때문에 모니터링 스레드를 모든 필로소퍼들이 각자 가지고 있을 수 있어서 더 간단한 형태로 구현할 수 있었다.
세마포어는 커널에서 큐의 형태로 사용되기 때문에 기아 상태를 해결할 수 있다는 장점이 있다. 하지만 디스크에 접근해야 하기 때문에 속도가 비교적 느리다. 때문에 딜레이를 고려하여 time_to_die를 설정해야만 한다.

💡 기아상태 vs. 교착상태

  • 기아상태(Starvation) : 특정 프로세스의 우선순위가 낮아서 원하는 자원을 계속 할당 받지 못하는 상태로, 병행 컴퓨팅에서 주로 나타나는 문제다.
  • 교착상태(Deadlock) : 여러 프로세스가 동일한 자원을 점유하려고 할 때 나타나는 문제다. 자세한 내용은 이전 포스트(TIL 42일차 - [42서울] Philosophers(2))에서 확인할 수 있다.

세마포어에 대한 내용을 상세하게 포스팅하고 싶은데, 다음 과제인 minishell에 공부할 내용이 폭탄인데다가 42gg 업데이트와 코드리뷰 견학을 앞두고 있어서 가능할지 모르겠다. 짬을 내서 꼭 써보도록 하겠다...!!!!

✅ waitpid()

철학자 프로그램이 종료되는 조건은 두 가지다. 철학자 중 한 명이 굶어 죽든가(DIE), 모든 철학자가 배불러야 한다(FULL). mandatory part를 구현할 때는 구조체에 is_dead라는 변수를 넣고 해당 변수를 사용하는 임계 영역에 mutex를 걸어 죽은 철학자가 있는지 없는지를 판별했는데, bonus part에서는 각 철학자가 프로세스 단위로 실행되다보니 조금 다르게 구현할 수 있었다.

waitpid() 함수는 인자로 들어가는 status에 상태가 바뀐 자식 프로세스에 대한 정보를 받아올 수 있다. 해당 함수에서 제공하는 WEXITSTATUS(status)라는 매크로를 사용하면 자식이 exit() 호출에 지정한 status 인자나 main()의 return 문에 지정한 인자의 하위 8비트를 확인할 수 있다. 아래의 예시를 살펴보자.

# define DIE 5
# define FULL 6

void check_death(t_info *info, t_philo *philo)
{
	now = get_time() - info->t_start;
	if (get_time() - philo->t_last_eat >= info->t_die)
    // 현재 시간 - 마지막으로 밥을 먹은 시간이 time_to_die 보다 클 경우
    {
    	printf("%lld %d died\n", now, philo->id + 1);
        // 죽었다는 메시지를 남기고
        exit(DIE);
        // 죽는다.
    }
}

void end_process(t_info *info)
{
	int status;
    
	while (1)
    {
		waitpid(-1, &wstatus, 0);
        // 자식의 상태가 바뀌길 기다리다가
		if (WIFEXITED(status) && WEXITSTATUS(status) == DIE)
        // 바뀐 자식의 상태가 DIE 라면?
		{
			kill_pids();
            // 모든 프로세스를 종료하는 함수를 실행하고
			break ;
            // whille문을 빠져나간다.
		}
    }
}

위의 check_death는 자식 프로세스에서 사용되는 모니터링 함수를, end_process는 부모 프로세스에서 자식의 종료를 기다리는 함수를 간략하게 나타낸 것이다. WEXITSTATUS() 매크로가 자식의 종료 호출에 지정한 DIE를 확인할 수 있기 때문에 어떠한 자식이 죽었다는 사실을 바로 알아챌 수 있는 것이다.

void end_process(t_info *info)
{
	int i;

	i = -1;
	while (++i < n_philo)
    // while 문을 철학자의 수 만큼만 돈다.
    {
    	waitpid(-1, &wstatus, 0);
        // 자식의 시그널을 기다리다가
        if (WIFEXITED(status) && WEXITSTATUS(status) == FULL)
        // 배부르다는 소식을 받으면
			info->n_full_philo += 1;
            // 배부른 철학자를 체크하는 변수에 1을 더해준다.
    }
    if (info->n_full_philo == info->n_philo)
    // 배부른 철학자의 수가 총 철학자의 수와 같다면?
		printf("everyone is full!\n");
        // 다들 배부르다는 메시지를 출력해준다.
}

FULL을 체크하기 위해서는 while 문에 위와 같은 조건을 추가해 주면 된다. 배부른 철학자들이 종료될 때마다 waitpid()를 통과하기 때문에, 모든 철학자가 배부른지를 확인할 수 있는 것이다.

하지만 42서울에서는 허용하지 않은 매크로의 사용에 대한 의견이 분분하기 때문에 안전하게 사용하지 않는 편이 좋겠다는 생각을 했다. 다행히도 WEXITSTATUS() 매크로는 비트 연산을 통해 쉽게 나타낼 수 있기 때문에 코드 전체를 고칠 필요는 없었다!

참고

https://veneas.tistory.com/entry/Linux-%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%8B%9C%EA%B7%B8%EB%84%90-%EB%AA%85%EB%A0%B9%EC%96%B4%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EC%A2%85%EB%A3%8C-kill
https://wariua.github.io/man-pages-ko/waitpid%282%29/
https://man7.org/linux/man-pages/man3/sem_open.3.html
https://man7.org/linux/man-pages/man3/sem_close.3.html
https://man7.org/linux/man-pages/man3/sem_wait.3.html
https://man7.org/linux/man-pages/man3/sem_post.3.html
https://man7.org/linux/man-pages/man3/sem_unlink.3.html
https://ko.wikipedia.org/wiki/%EA%B8%B0%EC%95%84_%EC%83%81%ED%83%9C

0개의 댓글