본문 바로가기

CS

Pintos Project2: User Programs

아 일단 시간이 너무 없어서 프로젝트가 다 끝난 뒤 정리용으로 써야 될 거 같다.  먼저 저번 프로젝트에서 부팅이 된 후 커널 스레드를 생성시켜 프로세스를 돌게 만들고 우선순위 별로 스케줄링하는 단계까지 구현을 했다. 이번 프로젝트에서는 사용자 명령어를 받아서 시스템 콜들을 실행하는 단계를 구현해야 한다. 그래서 일단은 argument passing을 먼저 처리해야 한다. 우리가 쉘에서 명령어를 입력하면 그 명령어대로 처리할 수 있게 입력된 명령어들을 잘 파싱 하여서 스택에 올리고 실행할 수 있게 넘겨주는 것이다. 이 동작은 밑에 코드를 보면 알 수 있다.

이전 단계에서는 무시되었던 코드다. 우리가 입력한 명령어가 저 task 인자로 들어간다.

이곳으로 들어오면 우리가 입력한 명령어에서 실행해야 될 커맨드만 꺼내서 쓰레드 이름으로 쓴다. 굳이 이러는 이유는 그래야 테스트를 통과한다. 아무튼 그럼 다시 initd라는 함수로 들어가게 된다 가보자. 아 여기서 palloc_get_page() 나중에 다시 보겠지만 싱글 페이지를 가져오는 함수다. 인자로 플래그가 들어가는데 저렇게 0을 넣으면 커널 페이지를 넘겨준다. 다른 플래그 들도 있다.  아무튼 initd라는 함수로 가보면 다시 밑에 보이는 exec으로 간다.

여기서는 우리가 실행할 프로세스로 바꿔야 하기 때문에 기존의 프로세스를 지워주고 로드 함수로 넘긴다. 그리고 구체적인 agument passing은 load에서 이뤄진다.

코드가 길어서 전체를 다 못 올렸다.  여기서 위에서 말한 입력 값들을 나눠준다. 저건 나중에 스택에 집어넣을 거다. strtok 잘 쓰면 된다. 그 뒤 쓰여있는 것처럼 페이지 디렉터리를 만들고 페이지 테이블과 TSS를 설정해 준다(process_activate에서 함). 그 뒤 실행 파일을 연다. 그 뒤 나중에 할 이야기지만 현재 실행 중인 파일에 작성하는 걸 막아주는 함수를 실행함. 그 뒤 유저 스택에 올리는 함수를 실행.

 이렇게 되면 일단 우리가 입력한 입력값들을 받을 수 있게 된다. 여기까지가 agument parsing 부분 하지만 아직 시스템 콜을 구현하지 않아서 테스트는 통과하지 못한다. 따라서 확인해 보고 싶다면 잘 올라 간지 체크만 할 수 있다. 밑에 코드처럼 해주면 된다.(주석 처리된 hex_dump부분

 

이제 시스템 콜들을 구현할 차례인데 우리가 흔히 아는 시스템 콜과 비슷하지만 약식으로 구현된 부분도 있다. 리눅스에서 사용하는 시스템 콜들 마다 찾아보면서 익히자. 일단 여기서는 공식 gitbook에 나와있는 것처럼 구현했다.

https://casys-kaist.github.io/pintos-kaist/project 2/system_call.html

 

System Calls · GitBook

Implement system call infrastructure. Implement the system call handler in userprog/syscall.c. The skeleton implementation we provide "handles" system calls by terminating the process. It will need to retrieve the system call number, then any system call a

casys-kaist.github.io

구현해야 할 시스템 콜들이다.

시스템 콜 별로 간단하게 정리하고 끝내 겠다. 먼저 halt와 exit는 설명대로 해주면 간단하게 구현 가능하다. 근데 exit 같은 경우 호출하면 thread_exit를 호출하는데 여기서 다시 process exit을 호출한다. process exit은 지금 실행 중이거나 열려있는 파일들 전부 닫아주면 된다.  사실 그전에 close 시스템 콜을 구현해서 file descriptor를 닫아 주면 좋다. 나중에 보겠지만 close를 포함한 여러 시스템 콜들은 파일을 열고 닫기 때문에 스레드 부분에서 file dsecriptor table을 만들어줘야 한다. 또 인덱스를 추가해 줘서 파일 디스크립터 인덱스들도 관리할 수 있다. 그리고 거기에 맞게 file descriptor를 사용해서 파일을 찾거나 , fd를 테이블에 추가하거나 빼는 등의 함수들도 작성해 주어야 한다. 생각보다 할거 많다. 아무튼 다시 exit으로 돌아와서 close를 통해서 fd 테이블에 존재하는 파일들 다 지울 수 있다. 다음으로는 exec을 보자. 현재 프로세스를 우리가 실행하고 싶은 새로운 실행 가능한 프로세스로 바꾸는 것이다. 그래서 보통 fork 하고 exec 한다.

잘 보면 또 palloc_get_page 해주는데 우리가 카피한 파일에 페이지 할당하는 거다. 2^12 즉 4KB 크기의 페이지를 준다.  여기서는 왜 이러냐면 process_exec을 보면 바로 들어가자마자 clean up 해버려서 그렇다. 자 그럼 다음으로 wait 봐보자. wait는 실행하면 바로 process_wait함수로 가게 된다. wait는 알다시피 부모 프로세스가 자식 프로세스를 기다리는 동작이다. 보통은 자식이 종료된 뒤 SIGCHLD 시그널을 보내면 wait 하고 있는 부모가 받아서 처리해 주면 자식은 완전히 종료된다. 그렇지 않으면 자식은 리소스는 모두 해체되지만  커널에 의해서 프로세스 테이블에 존재하는 좀비 프로세스가 된다. 아무튼 그래서 시그널 보내면 핸들러에서 wait 해주면 된다. 다시 돌아와서 우리는 위의 동작을 구현하는데, 여기서 동기화를 위해 세마포를 사용해야 된다. 세마포는 0으로 초기화돼서 일단 만나면 block 시켜준다. 그리고 자식도 일단 종료되면 부모가 완전히 없앨 때까지 block 되었다가 바로 지워진다. 밑은 process_wait 일부분이다.

설명한 것처럼 자식을 기다렸다가 끝나면 free_sema를 올려준다. 그럼 다시 자식 block이 해제될 것이다. 계속 말하는 자식 프로세스가 진행되고 있는 부분이 어디냐 하면 exit 부분이다. 자식 프로세스가 exit를 호출할 때 위의 과정이 진행된다.

 

다음은 파일 시스템 관련 시스템 콜들을 볼 텐데 read와 write만 보자. 나머지는 설명대로 구현하면 된다. read에서  체크해야 될 부분은 우리가 보통 STDIN 즉 표준 입출력에서 읽어 올 수 있어서 이 부분은 따로 처리해 주면 된다.  그게 아니면 그냥 파일 읽어 주면 된다. file_read는 이미 filesystem에 구현되어 있다. write는 STDOUT 출력을 해 줄 수 있기 때문에 이것만 처리해 주면 된다. 그리고 가장 신경 써야 될 부분은 당연히 동기화 부분이다. read , write 둘 다. 쓰고 읽을 때 lock을 걸어줘서 처리해 줘야 한다. 그런데 나는 여기서 문제가 발생한 게 이렇게 해줘도 syn-write와 syn-read 테스트를 통과하지 못했다. 그래서 위에서 설명하지 않았지만 보일 텐데, process_exec에서 보면 load 하기 전에도 따로 load_lock이란 걸 만들어서 lock을 걸어주었다. 사실 테스트 결과를 보고 맞춰서 구현한 것이어서 정확하지는 않은 것 같다. 여기까지 이고 나머지 시스템 콜들은 그냥 넘어가겠다. 

운이 통했다.

아 맞다 dup2.. 

넘어가려 했지만 간단하게 정리해 보자. 원래 dup2랑 똑같이 구현 한다. src 디스크립터가 가리키는 파일을 dst가 가리키게 만드는 것이다.

dup2

중간에 개수들 세주는건 표준 입출력이 아니라면 복사된 개수를 세어준다. 그래야 나중에 지울때 파악할 수 있다. 

'CS' 카테고리의 다른 글

Threads  (0) 2021.10.22
Pintos Project3: Virtual Memory  (0) 2021.10.21
Virtual Memory  (0) 2021.10.07
유저 스택과 커널 스택  (0) 2021.10.05
Pintos project 1 Alarm clock, priority scheduling  (0) 2021.10.04