PintOS: 의도된 복잡성

Pintos 프로젝트의 두번째,
 User Program을 다루는 주간이었다. Threads 파트에 비해 흐름이 복잡해졌다.
 Pintos의 구조의 하나씩 이해하자니 시간이 걸렸다.


하드웨어는 몇개 없는데 어떻게 이렇게나 복잡해질 수 있는가.
 Os가 만들어낸 추상화가 그 원인이었다. 프로세스가 무엇인지, Fd가 무엇인지, Wait가 무엇인지 따라가기 전에
 먼저 Os에게 왜 그런 추상화가 필요한지 생각해보고 싶었다.

pintos_empire.jpeg

Pintos를 작은 제국처럼 바라본 것도 그 때문이었다. 커널은 황제.
 모든 자원을 직접 쥐고 있고,
 사용자 프로그램은 신민.
 제한된 권한 안에서만 움직인다.


System Call은 그 사이의 검문소이고, 
Fd는 커널 객체에 접근하기 위한 허가증처럼 보였다. 중요한 건 비유 자체가 아니다.
 Pintos가 왜 지금의 추상화를 선택했는지부터 이해하고,
 한 번쯤 의심해보고 싶었다.

Os는 사용자에게 자유를 주는 것처럼 보이지만,
 사실은 위험한 자유를 안전한 형태로 바꿔서 제공한다.
 파일을 직접 주지 않고 Fd를 준다.
 주소 공간을 직접 공유하지 않고 Process로 격리한다. 
프로세스의 종료도 그냥 사라지게 두지 않고 Wait와 Exit로 회수한다.


가장 먼저 의심해볼 수 있었던 것은 Fd였다.
 Pintos에서 Fd는 기본적으로 정수 번호다.
 구현은 단순하고 이해하기 쉽다.
 하지만 실제 시스템을 생각하면 Fd가 단순한 번호인 것으로 충분한지 질문이 생긴다.

읽기만 가능한 Fd, 쓰기만 가능한 Fd, Exec 이후 닫혀야 하는 Fd,
 권한과 수명을 함께 표현할 수 있다면 더 안전한 추상화가 가능하다. 
이러면 Fd가 단순한 인덱스가 아닌 Capability가 되고,
 Read와 Write는 단순히 파일 함수를 호출하는 코드가 아니라,
 현재 프로세스가 그 작업을 할 권한이 있는지 확인하는 코드가 된다.


두 번째는 Fork였다.
 Pintos의 Fork는 부모의 주소 공간을 복사한다.
 교육용 구현으로는 명확하다.
 부모와 자식이 독립된 실행 흐름을 갖는다는 사실을 가장 직접적으로 보여준다. 하지만 곧바로 Exec를 호출할 자식이라면,
 방금 복사한 주소 공간 대부분은 버려질 수도 있다.


다른 Os들이 선택한 Copy-On-Write의 필요가 자연스럽게 이해된다.
 처음부터 전부 복사하지 않고,
 일단 공유하다가 실제로 쓰기가 발생할 때만 분리하는 방식이 더 나은 선택일 수 있다. Fork에서 “복사”보다 중요한 것은,
 “독립성을 언제, 어떤 비용으로 보장할 것인가”라는 점이다.


세 번째는 Wait와 Exit였다. 
Pintos에서 프로세스가 끝난다는 것은 단순히 실행이 멈추는 일이 아니다.
 Exit Status를 남겨야 하고,
 부모는 그것을 한 번만 회수해야 하며,
 부모가 먼저 죽거나 자식이 먼저 죽는 경우도 정리되어야 한다. Wait와 Exit는 수명 관리 프로토콜 같다.


자식의 종료 정보를 부모 리스트에 묶어두는 방식은 단순하지만,
 부모와 자식의 생명주기가 엇갈릴 때 책임이 애매해질 수 있다. Child 정보에 Parent_alive, Child_alive 같은 상태 외에도,
 Reference Count를 두는 방식도 생각해볼 수 있다.
 누가 마지막으로 그 정보를 해제해야 하는지 명확히 표현하기 위해서다.


Pintos의 추상화를 이해하고 의심하면서, 
재미있는 질문을 던지고 해결책을 고민해볼 수 있었다. 추상화는 복잡함을 감추는 수단인 줄 알았었다.


Pintos에서 본 추상화는 오히려 의도된 복잡성이다.
 권한, 비용, 수명을 어디에 둘지 결정하는 방식이다. Fd는 커널 객체를 직접 노출하지 않기 위한 권한 추상화. 
Fork는 독립성을 제공하기 위한 비용 모델.
 Wait와 Exit는 프로세스의 마지막 책임을 회수하기 위한 수명 추상화.

Pintos는 그 선택들을 일부러 단순하게 보여주고 있고,
 Os가 왜 이런 추상화를 필요로 하는지 질문하기 좋았다.