본문 바로가기

[pintOS] 시스템 콜 wait 구현기

@정소민fan2025. 5. 21. 17:32

pintOS 두번째 과제 유저 프로그램을 ALL pass 받고 뒤늦게 쓰는 wait 구현기...

이전 기수분들은 깃허브를 잘 안올려주셔서 코드를 참고하기가 너무 힘들었다..

이게 한이 되어 올리는 우리팀의 깃허브 !!

https://github.com/Week09-11-Pinots/Pintos-user-program

 

GitHub - Week09-11-Pinots/Pintos-user-program: 10-11주차 과제

10-11주차 과제. Contribute to Week09-11-Pinots/Pintos-user-program development by creating an account on GitHub.

github.com

바로 여기!! 만약 보고 도움이 됐다면 star를 찍어다오...

 

과제 설명

먼저 wait 과제 설명서를 보자

이 시스템 콜을 호출하면 자신의 자식 프로세스 pid의 종료를 기다리고, 종료 코드를 받아서 반환해야 한다.

그럼 어떻게 기다려야 할까? 세마포어를 사용해서 부모 프로세스를 block 시키면 될 것 같지 않은가?

자식 프로세스가 종료될 때 부모가 기다리던 세마포어를 up 시켜주면 될 것 같다.

 

종료 코드도 받아야 하는데, 이는 자식 프로세스가 exit(n)을 호출했을 때 시스템 콜로 종료 코드를 n으로 저장해두고, 부모 프로세스에서 n을 꺼내어서 반환해야 한다.

그럼 thread.h에 새로 추가해야할 필드들이 세마포어 하나, 종료 코드 필드 하나 이렇게 되겠다.

 

그리고 이미 wait가 호출된 적이 있는지도 알아야 하고... 나의 자식이 맞는지도 확인해야 한다. fork는 여러번 호출 할 수 있으니 자식 프로세스를 담을만한 리스트도 하나 만들어야 한다.

 

thread 구조체 필드 추가

struct thread
{
    ...
    struct semaphore wait_sema; // wait를 위한 세마포어
    int exit_status; /* 종료 코드 저장 */
    struct list children_list; /* 나의 자식 프로세스 리스트 */
    struct list_elem child_elem;
    ...
}

당연히 wait_sema는 초기화 과정을 거쳐줘야 한다. 위치가 애매한데, 이전의 fork_sema는 process_init에 만들어줬다, 하지만 wait_sema는 자식 프로세스가 fork 도중 process_init 함수를 호출하지도 못하고 에러를 통해 죽어버리면 wait 세마가 초기화되지 못하는 불상사가 발생한다. 따라서 thread_create 내의 init_thread에서 초기화 작업을 해주기로 했다.

static void
init_thread(struct thread *t, const char *name, int priority)
{
    ...
    list_init(&t->children_list);
    sema_init(&t->wait_sema, 0);
    ...
}

0으로 초기화해주는 이유는? 당연히 부모 프로세스가 바로 block 상태로 들어가야 하니까 !!

process_wait 구현

그럼 이제 process_wait를 구현해볼까?

wait를 호출할 땐 당연히 부모 프로세스 내의 자식 프로세스 pid를 인자로 넘겨주어야 한다. 따라서 우리가 구현할 로직은 다음과 같다.

  • 자식 리스트를 뒤져서 인자로 받은 pid가 자식이 맞는지 확인
  • 맞다면 sema_down으로 자식 프로세스 종료 대기
  • 깨어난 후 자식의 exit_status 확인하고 반환

pintos에서는 스레드와 프로세스의 구분을 따로 두지는 않는 것 같다. tid == pid라고 보자 !!

그럼 먼저 이 tid가 내 자식 리스트에 존재하는지 확인하는 함수를 먼저 만들어두자

static struct thread *get_my_child(tid_t tid)
{
	struct list_elem *e;
	struct thread *cur = thread_current();
	for (e = list_begin(&cur->children_list); e != list_end(&cur->children_list); e = list_next(e))
	{
		struct thread *child = list_entry(e, struct thread, child_elem);
		if (child->tid == tid)
			return child;
	}
	return NULL;
}

만약 다 뒤졌는데도 없으면 NULL을 리턴해준다

int process_wait(tid_t child_tid UNUSED)
{
	struct thread *cur = thread_current();
	if (list_empty(&cur->children_list))
		return -1;

	struct thread *child = get_my_child(child_tid);
	if (child == NULL)
		return -1;
	/* 자식의 wait_sema를 대기합니다. process_exit에서 wait_sema를 up 해줍니다 */
	sema_down(&child->wait_sema);
    
	int status = child->exit_status;
	list_remove(&child->child_elem);
	if (status < 0)
		return -1;
	return status;
}

 

 

그럼 process_wait 함수 내의 로직을 확인해보자

아까 위에서 적었던 세가지 로직을 그대로 옮겨놨다.

get_my_child로 내 자식이 맞는지 확인하고, sema_down으로 자식이 종료되기를 기다린다.

이후 깨어나면 자식의 exit_status를 반환한다.

또한, 자신이 꺠어났다는 것은 자식이 종료되었다는 뜻이므로 자신의 자식 리스트에서 자식을 삭제해준다.

아 쉽다 쉬워

 

pintos 별거없네

하지만 이게 끝이 아니다. 저 exit_status는 대체 어디서 설정되는거지?

종료 코드니까 당연히 process_exit에서 구현되지 않겠는가?

그럼 process_exit에서는 어떻게 해줘야 하는지 확인해보자

process_exit 수정

/* 프로세스를 종료합니다. 이 함수는 thread_exit()에 의해 호출됩니다. */
void process_exit(void)
{
	struct thread *curr = thread_current();

	if (curr->fd_table != NULL)
	{
		for (int i = 0; i < MAX_FD; i++)
		{
			if (curr->fd_table[i] != NULL)
				file_close(curr->fd_table[i]); // 각 파일들 닫아주기
		}
		free(curr->fd_table);
		curr->fd_table = NULL;
	}

	sema_up(&curr->wait_sema);
	process_cleanup();
}

https://gooch123.tistory.com/19#11 여기서 fork 시에 fd_table도 다 복사해왔고, 시스템 콜로 새로운 fd를 가져왔을수도 잇으니까 파일 테이블을 순회하면서 모두 닫아주자 !!

그 다음 wait_sema를 up 해줘서 자신을 기다리고 있던 부모 프로세스를 깨워주면 된다

syscall_handler 수정

당연히 구현한 다음 syscall_handler에 등록해줘야겠지??

/* The main system call interface */
void syscall_handler(struct intr_frame *f UNUSED)
{
    ...
    case SYS_WAIT:
    f->R.rax = process_wait((tid_t)arg1);
    break;
    ...
}

 

 

 

fork에 비하면 뭐 크게 복잡하지도 않다. 과제 설명서에서 제일 복잡하다고 해서 지레 겁먹지 말고 시도해보자

정소민fan
@정소민fan :: 코딩은 관성이야

코딩은 관성적으로 해야합니다 즐거운 코딩 되세요

목차