TT Lab
시작하기
배우기 러닝패스 코스

운영체제

프로세스 — 운영체제가 파는 격리라는 상품

TT Lab 에서 이어서 보기

한 줄 요약

프로세스는 "이 프로그램은 자기만의 컴퓨터를 갖고 있다"는 착각을 운영체제가 만들어 파는 상품이고, 그 착각의 핵심은 독립된 주소 공간이다.

왜 이게 필요했나

한 대의 기계에서 여러 프로그램을 돌리려면 두 가지를 보장해야 한다. 첫째, 한 프로그램의 버그가 다른 프로그램의 메모리를 망가뜨리면 안 된다. 둘째, 프로그램이 자기가 언제 멈추고 언제 재개되는지 신경 쓰지 않아도 돼야 한다.

이 둘을 함께 제공하는 추상이 프로세스다. 각 프로세스는 0번지부터 시작하는 자기만의 가상 주소 공간을 갖고, CPU 를 잠시 빌렸다가 반납하지만 그 사실을 알아차리지 못한다.

통째로 복제한다는데 왜 싸게 끝나는가

fork 는 부모를 복제하고 exec 이 그 위에 새 프로그램을 덮어쓴다. 무섭게 들리지만 실제 복사는 거의 없다.

  1. fork 직후부모와 자식이 같은 물리 페이지를 가리킨다. 아직 아무것도 복사되지 않았다.
  2. 누군가 쓰려는 순간그 페이지 하나만 복사된다. 이것이 쓰기 시 복사다.
  3. 곧바로 exec 을 부르면주소 공간을 통째로 갈아 치우므로 복사할 일이 거의 생기지 않는다. 흔한 패턴이 싼 이유다.

여기서 구분할 것 컨텍스트 스위치도 같은 오해가 있다. 레지스터 몇십 개를 옮기는 직접 비용은 짧고, 진짜 비용은 캐시와 TLB 가 쓸모없어져 미스가 쏟아지는 간접 비용이다.

잠깐, 예측해 보세요 fork 로 큰 프로세스를 복제하면 메모리가 두 배가 될까?

설명 확인 · 채점 없는 자가 점검

곧바로 exec 을 부른다면 거의 늘지 않는다. 다만 부모가 쓰기를 많이 하는 상태로 오래 함께 살면 그만큼 페이지가 실제로 복사된다.

근거 문서

어떻게 동작하나

운영체제는 프로세스마다 PCB(Process Control Block) 를 유지한다. 여기에 담기는 것은 대략 이렇다.

컨텍스트 스위치는 현재 프로세스의 레지스터를 PCB 에 저장하고 다음 프로세스의 것을 복원하는 일이다. 레지스터 몇십 개를 옮기는 것 자체는 짧지만, 진짜 비용은 그다음에 온다. 새 프로세스가 실행되기 시작하면 캐시와 TLB 에 남아 있던 이전 프로세스의 내용이 쓸모없어져 미스가 쏟아진다. 이 간접 비용이 직접 비용보다 훨씬 크다.

리눅스에서 프로세스를 만드는 방식도 특징적이다. fork() 는 부모를 통째로 복제하고 exec() 가 그 위에 새 프로그램을 덮어쓴다. 통째로 복제한다는 말이 무섭게 들리지만 실제로는 쓰기 시 복사(copy-on-write) 로 처리된다. 부모와 자식이 같은 물리 페이지를 공유하다가, 누군가 쓰려는 순간 그 페이지만 복사한다. 곧바로 exec() 를 호출하는 흔한 패턴에서는 복사가 거의 일어나지 않는다.

프로세스 상태 전이에서 자주 헷갈리는 것이 좀비다. 자식이 끝났는데 부모가 아직 wait() 로 종료 상태를 거두지 않으면 PCB 만 남은 좀비가 된다. 좀비는 메모리를 거의 쓰지 않지만 프로세스 테이블 항목을 차지하므로, 쌓이면 새 프로세스를 못 만든다. 반대로 부모가 먼저 죽으면 자식은 고아가 되어 init(또는 컨테이너의 1번 프로세스)에 입양된다. 컨테이너 안에서 애플리케이션을 1번 프로세스로 띄웠을 때 좀비가 쌓이는 사고가 여기서 나온다. 1번은 고아를 거둘 책임이 있는데 보통의 애플리케이션은 그 코드를 갖고 있지 않다.

현장에서 만나는 모습

ps 출력의 STAT 열에서 Z 를 보면 위 상황이다. 컨테이너 이미지에 tini 같은 작은 init 을 넣거나 런타임의 init 옵션을 켜는 것이 표준 대응이다. D 상태(중단 불가 대기)도 알아 둘 만한데, 대개 디스크나 네트워크 파일시스템 I/O 를 기다리는 중이며 이 상태의 프로세스는 kill -9 로도 즉시 죽지 않는다.

좀비와 고아는 반대 방향의 사고다

둘 다 부모와 자식의 끝나는 순서 때문에 생기지만 남는 것과 대응이 다르다.

  • 좀비자식이 끝났는데 부모가 종료 상태를 거두지 않은 상태다. 메모리는 거의 안 쓰지만 프로세스 테이블 항목을 차지해 쌓이면 새 프로세스를 못 만든다.
  • 고아부모가 먼저 죽어 자식이 1번 프로세스에 입양된 상태다. 1번은 고아를 거둘 책임이 있는데 보통의 애플리케이션에는 그 코드가 없다.

여기서 구분할 것 컨테이너에서 애플리케이션을 1번으로 띄우면 이 둘이 만난다. 고아가 입양되는데 1번이 거두지 못해 좀비가 쌓인다.

잠깐, 예측해 보세요 ps 의 STAT 열에 Z 가 늘고 있다. 컨테이너라면 무엇을 하겠는가?

설명 확인 · 채점 없는 자가 점검

작은 init 을 1번으로 두거나 런타임의 init 옵션을 켠다. 애플리케이션 코드를 고치는 것보다 이것이 표준 대응이다.

근거 문서

이어지는 퀴즈에서 확인할 것

fork 가 왜 비싸 보이는데 실제로는 싸게 끝나는지, 컨텍스트 스위치의 진짜 비용이 어디에 있는지 설명할 수 있는지 확인한다.