본문 바로가기

크래프톤 정글/WIL

정글[Week_10]: Pintos의 프로세스의 생애 주기 구현하기

Pintos 2주차에 접어들었다. 이번 주 차는 Process로 주제가 좀 더 확장되었다. 상용 프로세스와 다르게 Pintos는 각 프로세스에 스레드가 하나만 들어 있는데, 이번에는 스레드에 옷?을 입혀 프로세스로 만들고 이의 생애주기를 구현하는 프로젝트라 보면 되겠다.

 

 

프로세스는 유저 모드와 커널 모드를 왕복한다

커널 모드와 유저 모드라는 단어들 들어 보았는가? 우리의 컴퓨터는 여러 응용 프로그램들이 가동되도록 설계되어 있는데, 이런 application들도 결국 프로그램이니 컴퓨터의 자원(cpu, 램, 디스크 등)을 필요로 한다. 만약 수 많은 app들이 제각각 컴퓨터에 자원을 차지하면 이는 컴퓨터 입장에서 치명적일 수 밖에 없다. 이러한 문제를 해결하기 위해... 개발자들은 app이 가동될 때 최소 사용할 수 있는 권한유저모드직접 자원을 건드릴 수 있는 권한커널모드로 분류하였다.

유저 모드와 커널 모드

예를 들어, 어떠한 app이 모니터 화면에 문구를 출력하고자 할 경우, 실제 모니터 화면과 이에 입출력하는 컴퓨터 자원이 사용되어야 한다. 여기서 app은 system call을 통해 운영체제에게 자원 사용을 요청한다. 운영체제(여기서는 pintos)는 요청을 받고 실제 자원을 할당해 준 다움 결과를 app에 반환하는 과정을 거친다. 이렇게 컴퓨터 자원에 필요할 때마다, app에서는 요청을 하고 os는 받아주고 응답하는 과정을 수없이 왕복한다.

 

 

프로세스에는 유저 영역과 커널 영역이 있다

실행파일과 메모리에 대한 관계
우리의 컴퓨터만 해도 메모리는 10~20GB임에 반해 디스크는 많게는 2TB라는 막대한 저장공간이 있음을 알 필요가 있다. 디스크에는 수 많은 프로그램들이 실행 파일이라는 형태로 잠자고 있다. 여기서 특정 프로그램을 run하는 명령 실행 시, 컴퓨터는 디스크에 있는 해당 파일들을 가져와 메모리에 적재를 한다. 이렇게 적재된 메모리는 보통 작업 단위로 나눠 프로세스가 되며, 그 안에는 한 개부터 여러 개의 스레드가 존재한다.

프로세스의 분리된 영역들
이렇게 프로그램을 위한 각 프로세스는... 다양한 영역으로 분리되어 있는데, 가장 크게는 유저 영역과 커널 영역이 대표적이다.

프로세스의 영역

위의 그림을 통해 좀 더 쉽게 접근할 수 있는데, 위는 커널 아래는 유저 영역이다. 유저 영역에는 맨 위에서 아래로 자라는 user stack이 존재하고, 아래에서 위로 자라는 runtime heap 영역이 있다. 모두 유저 모드에서 응용 프로그램이 자유?롭게 메모리를 적재하고 해제가 가능한 영역인 곳이다. 다만, 커널 영역은 유저 모드에서는 접근이 불가하다. 하지만 커널 모드는 기본적으로 더 높은 권한(Ring 0)을 가지므로, 유저 영역도 편집이 가능하다.

 

 

유저모드가 실행 되기에 앞서 필요한 것: Argument Passing

만약 프로그램이 유저 모드로 왔다고 가정하자. 그런데, 프로그램이 실행에 필요한 "시작 명령"이 없다면, 먹통의 상태가 된다. 즉, 맨 처음 유저 모드로 들어가기 전 조차 누군가가 시작 명령을 유저 스택에 주입해줘야 하는데... 이는 결국 커널에서 수행을 하고, 이를 argument passing이라고 한다.

핀토스의 2주차, user programs에 진입하면 가장 먼저 구현해야 하는 것이 바로 이것이다. 일단 프로그램이 돌아가도록 해야 하니까, 어떻게든 첫 삽을 뜰 수 있도록 하는 작업인 셈이다.

Args를 어떻게 넣는 방법 중 하나

Argument는 직접 유저 스택에 메모리 byte수를 계산을 하여 적재를 해야 한다. 위의 그림이 예시가 될 수 있는데, argument를 공백을 기준으로 마디마디를 나눈 다음, 이를 배열로 가정하여 stack에 적재를 하는 것이다. 단순 문자열만 적재하는 것이 아닌, 페딩 영역과 각 마디의 시작 주소값도 포인터로 넣어야 한다.

 

 

시스템 콜은 file관리과 process관리로 나뉜다

명확하게 국경처럼 나뉘는 것이 아니지만, pintos에서 수 많은 system call을 구현할 때 각 콜이 요청하는 대상이 두 가지로 나뉨을 알게 되었다. File, 즉 디스크에 진입하여 열기/읽기/쓰기를 진행하는 syscall과 프로세서를 생성/복제/갈아끼우기를 진행하는 syscall이 대표적이다.

File에 관련된 syscall은 아주 low level의 직접 파일에 접근하는 메서드는 이미 구현되어 있었고, 이를 활용을 하는 방식으로 진행이 되었다. 다만, 파일을 open할 때마다 해당 통로를 마치 창구 번호표 처럼 fd를 갖는데 이에 대한 로직 구현이 중요하다.

 

process는 부모-자식 관계를 갖는다
프로세스에 관련된 syscall이 요청되면 pintos는 이에 맞게 프로세스를 복제 및 갈아끼우기 등을 수행해야 한다. 먼저 fork함수를 통해 부모 프로세스를 기반으로 자식 프로세스가 내용물이 그대로 복제가 되는데... 이후 exec을 통해 내용물을 갈아끼워서 새 프로그램에 맞도록 환경을 셋팅해준다.

그렇다면 왜 부모-자식이라는 위계 관계를 띄는 것일까? 이유가 다양하겠지만, 자식 프로세스가 오류가 나거나, 비정상 종료가 되더라도 부모 프로세스에 이를 통보하고 이후 다른 재작업(자원 회수 등)을 할 수 있기 때문이다. 특히 fork할 때 환경 셋팅을 포함해 그대로 복제하기 때문에, 자식 프로세스는 설정 초기화 비용을 아낄 수 있다.

 

 

프로세스의 흐름을 이해하기

과제 구현이 우선이었지만, 왜 이러한 기능이 필요한지 의구심을 가지는 경우가 많았다. 하지만, 우리가 일상에서 사용하는 프로그램을 예시로 생각하며, 각 syscall이 어떠한 경우에 필요한지 머릿속에 그림을 그리면서 이해해 나갈 수 있었다.

File관련 콜의 경우, 컴퓨터 내부의 디스크와 상호작용에 있어서 필요함을 파악하였고, 워드 문서를 읽고 쓰고 저장하고 등을 수행하면서 일어나는 일이, 실제 syscall을 통해 이루어짐을 파악할 수 있었다.

Process의 경우, 프로그램이 구동되면서 더 많은 자원을 요청할 경우 요청하는 syscall로 파악하였다. 예를 들어, 브라우저에 창이 계속 많아지고 영상 재생과 같은 고비용 작업이 필요할 때... 기존 프로세스나 그 작업 성질?만으로는 충분치 않을 것이다. 이 때마다, 브라우저라는 응용 프로그램이 수 많은 syscall을 요청하겠구나... 라는 그림이 머릿속에 그려지니 이해의 범위가 확장되는 쾌감을 느낄 수 있었다.