[PintOS Project 2] TIL - 4

옵주비·2022년 5월 31일
0

PintOS Project2

목록 보기
4/9
post-thumbnail

(5/31)
오늘은 주로 syscall.c 에서 작업하며 하루를 보냈다.

팀원들과 함께 system call 구현을 하다보니, 확실히 깃북에 대한 분석이 부족했던듯 하다.
셋이서 머리를 맞대고 가능한 선에서 도전해본 후에 남이 구현했던 코드를 살펴봤음에도, 도대체 왜 이 함수를 여기서 이렇게 사용하는지 이해하기 어려운 경우가 생각보다 많았다. 따라서 깃북에서 어떤 함수를 구현해야 하는지가 나오는 곳의 바로 직전까지의 배경 지식을 다시 한 번 정독하며 간단한 해석을 우선 글로 정리해봤다.

System Calls

Implement system call infrastructure

userprog/syscall.c 에 있는 system call handler (syscall handler() 함수) 를 수정해주어야 한다.
현재 기본적으로 제공된 함수에서는, 단순히 system call! 을 출력하고 해당 프로세스를 종료해주는 기능만 수행한다.
system call number와 arguments 를 탐색해서, 각 시스템 콜에 따라 적절한 액션이 수행되도록 구현해주는 것이 필요한 것이다.

-> 여기를 정리하면서, 왜 시스템 콜을 우리가 PintOS에 구현해주어야 하는지 그 당위성을 다시 한 번 확인했다.

그 어떤 시스템 콜이 들어오더라도 단순히 같은 문구를 출력하고 종료해주는게 아니라, 그때그때 해당하는 시스템 콜에 알맞은 작업을 PintOS가 수행하도록 우리가 구현을 해주어야 하는 것이구나!

이걸 느끼고 나니까 왜 그렇게 다양한 시스템 콜을 구현해야 하는지, 가짓 수는 왜 그렇게 많은지가 어느정도 납득이 갔다 :)

System Call Details

OS가 User program 으로부터 다시 제어(control)를 회수하는 데엔 두 가지 방법이 있다.

첫 번째 방법은, timer나 I/O device를 통한 external interrupt를 활용하는 것으로, 지난 Project 1에서 이미 구현한 방법이다. 그리고 두 번째 방법이, page fault 나 division by zero 등의 에어로 인해 발생하는 software exception들인데, 보통 프로그램의 code에서 발생하게 된다. Exception은 user program이, OS에 서비스를 요청하는 수단(system call)이기도 하다.

PintOS에서는 system call을 생성하기 위해 syscall을 호출(invoke)한다. syscall을 호출하기 전에 system call 번호, 그리고 부가적인 arguments는 레지스터들에 세팅이 되어야 한다. 특이한 점은, %rax 는 system call number 이고, 4번째 argument가 %rcx가 아니라 %r10 이라는 것이다.

따라서 system call handler가 제어를 획득했을 때, %rax에는 시스템 콜 번호가, 그리고 나머지 인자는 차례대로 %rdi, %rsi, %rdx, %r10, %r8, %r9에 전달된다는 것에 유의하자.

시스템 콜을 부른 caller의 레지스터들은, caller에 전달되었던 intr_frame에 접근이 가능하다. 이 때, intr_frame은 kernel stack에 속해있는 구조체이다. system call의 return value는 intr_frame의 RAX 필드를 수정함으로써, RAX 레지스터에 배치하는 것이 원칙이다.

-> 여기서는 % rax에 시스템 콜 번호가 들어있다는 것을 다시 한 번 확인했다. 그래서 아래와 같이 syscall_handler를 작성할 때, 레지스터의 rax를 읽어와 어떤 시스템 콜을 호출한 것인지 그 번호를 찾아주도록 했다는 것을 이해할 수 있었다. 그리고 첫 번째 인자로 주어진 rdi를 활용해 몇 가지 시스템 콜을 처리하는데 활용한다는 것도 어렴풋이 이해할 수 있었다. 다만 rdi에 들어가는 값은 반드시 program name은 아닌듯한데, 이에 대해선 추가적인 연구가 필요할듯 하다.


syscall.c에서, 오늘은 어제 구현한 halt에 이어 exit과 fork를 추가적으로 구현해보았다.
깃북에서 준 가이드를 노션에 정리한 내용, 그리고 구현한 코드를 각 함수별로 함께 첨부해보겠다.

EXIT


exit의 구현은 간단하다. 다만, 지금 종료해주는 프로세스를 wait하는 부모가 있는 경우, 여기서 설정하는 status가 그 부모의 wait에 대한 return 값으로 동일하게 주어진다는 것만 염두에 두면 될 것 같다.

FORK

보다시피 구현할 내용이 상당히 많았고, 그만큼 시간이 오래 걸렸다. fork는 워낙 어렵다고 해서, 일단 클론코딩으로라도 한 번 해보자고 팀원들과 합의한 후에 한줄한줄 뜯어가며 공부해보았다. 물론 여전히 이해가 안 가는 부분이 많지만, 아직 1주일이란 시간이 남아있으니 OS 기본개념을 병행해 쌓아가다보면 좀 더 전체적인 그림이 잡히지 않을까 싶다.
시스템 콜 fork는, 이미 PintOS에 주어진 함수인 process_fork를 불러와 수행하도록 하면 되었다.
당연히 수정해줘야 하므로, 지체없이 process_fork로 이동해보자.인자로 주어진 인터럽트 프레임(if_)을 직접 넘겨줄수는 없으니, fork를 호출하는 부모(현재 쓰레드)의 필드값인 parent_if에 담아서 넘겨주도록 하였다. 또한, thread_create의 가장 마지막 argument로 현재 쓰레드를 넘겨줘서, 새로 생성하는 자식 쓰레드 역시 같은 rsi(가상 메모리 공간)를 공유하도록 해주었다.

자식 쓰레드 생성이 정상적으로 완료되어 pid를 리턴받은 경우, 그 pid를 가지고 자식 쓰레드를 소환해서, 그 자식에 대한 fork_sema 를 가지고 sema_down 해줌으로써 정상적으로 fork 과정이 완료될 수 있도록 조치해주었다.

여기서 thread_create에 활용하는 __do_fork라는 함수를 타고 넘어가보자.

우선 노션 캡쳐본의 마지막 줄에서 언급된 'pml4_for_each()에 주어지는 함수'는, 살펴보니 위와 같이 duplicate_pte임을 확인할 수 있었다. 실제로 duplicate_pte로 타고 넘어가니 구현해주라는 주석이 스텝 바이 스텝으로 주어져있었고, 그에 맞추어 아래와 같이 구현해주었다.

다시 __do_fork로 돌아와보자면,

TODO 주석에 맞추어 fd_table을 복사해주는 코드를 구현해주었다.
노션 캡쳐본에서도 정리했듯이 자식 프로세스는 부모 프로세스와 동일한 file descriptor table을 갖도록 복제해주어야 하기 때문이다. fd_table 상의 0과 1번은 각각 stdin, stdout에 대한 것이니 곧장 복사를 해주었고, 2번부터는 주석에서 힌트로 준 file_duplicate 를 활용해 복사해주는 코드를 구현했다.

여기서 fork가 정상적으로 끝나면 do_iret을 통해 새로 생성한 자식 프로세스로 context switch를 해주고, 만약 fork 도중 에러가 발생한 경우엔 TID_ERROR를 status로 갖도록 시스템 콜 exit을 호출해주도록 하였다. 여기서 혹시 모르니 일단 에러가 난 현재쓰레드의 exit_status를 업데이트 해준 후에 exit을 호출했는데, 사실 exit의 parameter로 TID_ERROR을 주게 되면 그 시스템 콜 내에서 어차피 현재 쓰레드의 exit_status를 업데이트 해주므로 중복되기에 필요 없을듯 하다.

따라서 모든 구현이 끝난 후에 진행할 '실험' 중에 이 부분을 주석처리하고 진행하는 것도 진행할 예정이다.


마무리

어제는 분명 할만하다고 생각했는데, 오늘은 갑자기 난이도가 확 상승한 느낌이다.
애초에 한 학기 분량을 5주동안 진행하는 것이기에 모든 코드를 고민하고 짠다는 것은 욕심이란 것을 잘 알지만, 이렇게 진행하는 것이 맞을까란 의문이 들기도 한다. 어차피 웹 상에 존재하는 블로그나 코드들도 잘못된 것이 하도 많아서 '정답'이라고 할 순 없지만, 가이드의 역할은 충분히 제공한다는 면에서 아무래도 도움을 받는다는 느낌은 다소 찝찝하다.

다른 사람의 블로그를 보다보니, 프로그래머의 덕목은 코드를 잘 이해하는 것도 포함이라는 말이 인상 깊었다. 이미 있는 코드라 하더라도, 내 것으로 100% 만들 수 있다면 분명 얻어가는 것이 있지 않을까? OS가 어떻게 생겼는지, 각 시스템 콜이 어떻게 돌아가는지 일단 구현해보며 눈으로 확인한 다음에, 이해를 높여가다보면 어느순간 유레카를 외치는 내가 있기를..

마냥 붙잡고 우리 수준에서 하기 힘든 것에 너무 에너지를 소비하고 절망만 하기보다는, 일단 뭐라도 해보자란 마음으로 팀원들과 함께 같은 방향으로 공부해보고 있다는 점에도 의의가 있는 것 같다. PintOS 구현을 어느정도 도전해보고 나서, OS 기본개념을 각자 공부해서 이해력을 높인 다음에 다시 처음부터 코드를 뜯어보기로 했는데 벌써부터 설렌다. 저번 주처럼 마지막 순간에는 모든 코드를 이해하고 뿌듯해할 수 있었으면 좋겠다!

0개의 댓글