OS · Process · Memory

프로그램이 실행되면 무엇이 되나 - 프로세스

디스크의 파일 하나가 실행되면 프로세스가 된다. 자기만의 메모리, 열어둔 자원 목록, 어디까지 실행했는지 - 프로세스가 받는 세 가지를 보면, 왜 프로그램끼리 서로의 메모리를 못 건드리는지가 풀린다.

·프로그램은 어떻게 도나 1편
목차
  1. 디스크의 파일이 어떻게 살아 움직이나
  2. 프로그램과 프로세스는 다른 것이다
  3. 실행되면 세 가지를 받는다
  4. 주소공간: 자기만의 메모리
  5. 자원 목록: 열어둔 것들
  6. 실행 상태: 어디까지 했나
  7. 메모리는 못 봐도 존재는 서로 보인다
  8. 프로세스는 나무처럼 갈라진다
  9. 실무에서: 프로세스를 본다는 것
  10. 정리

우리가 매일 쓰는 스레드·풀·컨테이너는 모두 프로세스 위에 서 있다. 그런데 정작 프로세스가 무엇인지는 건너뛴다. 디스크의 파일 하나가 실행되면 무엇이 되는지, 그 밑바닥부터 시작한다.

디스크의 파일이 어떻게 살아 움직이나

server.jar, app.exe 같은 파일은 디스크에 가만히 있는 바이트 덩어리다. 그 자체로는 아무것도 안 한다. 그런데 실행하면 갑자기 메모리를 쓰고, CPU를 돌리고, 파일을 읽는 살아 있는 무언가가 된다.

이 “살아 있는 실행체”가 프로세스(process) 다. 파일은 재료고, 프로세스는 그 재료로 지금 돌아가는 활동이다.

프로그램과 프로세스는 다른 것이다

이 둘을 가르는 게 첫 단추다.

server.jar
디스크에 가만히 있는 파일
PID 811
자기만의 메모리
자원 목록
실행 상태
PID 812
자기만의 메모리
자원 목록
실행 상태
PID 813
자기만의 메모리
자원 목록
실행 상태

파일은 하나인데 실행체는 각각이다. 셋은 메모리열어둔 것어디까지 했는지도 서로 따로 갖는다.

레시피 한 장으로 요리를 여러 개 동시에 할 수 있듯, 프로그램 하나를 여러 프로세스로 띄울 수 있다. 같은 브라우저를 창 여러 개 띄우면 프로세스가 여럿이다. 파일은 하나지만 실행체는 각각이다.

실행되면 세 가지를 받는다

OS가 프로그램을 프로세스로 띄울 때, 그 프로세스에게 세 가지를 챙겨 준다.

  • 자기만의 메모리(주소공간) - 코드·변수·데이터가 놓일 공간.
  • 자원 목록 - 열어둔 파일, 연결 같은 것들의 목록.
  • 실행 상태 - 지금 코드의 어디까지 실행했는지.

이 셋이 프로세스의 정체다. 하나씩 보면 우리가 쓰던 개념들의 밑바닥이 드러난다.

주소공간: 자기만의 메모리

프로세스는 저마다 자기만의 메모리 공간을 받는다. 놀라운 건, 각 프로세스가 “이 메모리는 전부 내 것”이라고 믿는다는 점이다. 두 프로세스가 똑같은 주소 0x1000을 써도 서로 다른 실제 메모리를 가리킨다 - 남의 변수를 실수로 덮어쓸 수가 없다.

이게 어떻게 가능한지가 다음 글 가상 메모리다. 지금은 “프로세스마다 격리된 자기 메모리를 가진다”만 쥐고 가자. 이 격리가 프로그램들이 한 컴퓨터에서 안전하게 공존하는 바탕이다.

자원 목록: 열어둔 것들

프로세스는 파일을 열고, DB에 연결하고, 네트워크 소켓을 연다. OS는 이 열어둔 것들을 프로세스별 목록으로 관리하고, 프로세스는 작은 번호로 그것들을 가리킨다.

이 번호가 파일 디스크립터이고, 연결 풀이 아끼는 그 “연결”도 결국 이 목록의 한 자리다. 프로세스가 죽으면 이 목록도 통째로 정리된다 - 그래서 프로세스를 내리면 열어둔 파일·연결이 함께 닫힌다.

실행 상태: 어디까지 했나

프로세스는 “지금 코드의 몇 번째 줄을 실행 중이고, 계산하던 값은 무엇인지”를 들고 있다. CPU 안의 작은 저장소(레지스터)에 담긴 이 상태 덕에, 프로세스는 자기가 어디까지 했는지 안다.

이 상태를 저장했다 불러오는 것이 컨텍스트 스위치의 핵심이다. CPU 하나로 여러 프로세스를 번갈아 돌릴 수 있는 것도, 이 “어디까지 했나”를 갈무리했다 되살릴 수 있기 때문이다.

메모리는 못 봐도 존재는 서로 보인다

한 가지 오해를 걷어내자. 프로세스는 서로의 메모리는 못 본다(주소공간이 격리돼 있으니까). 하지만 서로가 존재한다는 것, 그리고 같은 디스크의 파일·같은 네트워크 포트는 다 보인다.

그래서 container 글이 “프로세스는 원래 서로가 다 보인다”고 한 것이다 - 메모리는 격리돼도 프로세스 목록·파일·포트는 공유된다. 컨테이너는 바로 그 “보이는 부분”까지 커널이 가려주는 것이다. 메모리 격리는 프로세스가 원래 주고, 그 위의 격리를 컨테이너가 더한다.

프로세스는 나무처럼 갈라진다

프로세스는 무에서 생기지 않는다. 이미 있는 다른 프로세스로부터 생겨난다 - 유닉스 계열은 부모가 자기를 복제하는 방식이다(윈도우는 새로 띄우는 식으로 세부가 다르다). 그래서 시스템의 모든 프로세스는 최초 프로세스에서 갈라져 나온 나무 구조를 이룬다 - 부모가 자식을 낳고, 자식이 또 자식을 낳는다.

웹 서버가 요청을 처리할 새 프로세스를 띄우거나, 셸이 명령을 실행할 때가 이 갈라짐이다. 부모는 자식이 끝나기를 기다리거나, 자식을 독립시켜 따로 돌게 둘 수 있다.

실무에서: 프로세스를 본다는 것

  • PID로 가리킨다 - 프로세스마다 번호(PID)가 붙는다. 특정 프로세스를 멈추거나 신호를 보낼 때 이 번호로 지목한다.
  • 메모리·CPU를 프로세스 단위로 잰다 - “이 앱이 메모리를 얼마나 쓰나”는 그 프로세스의 주소공간이 실제 물리 메모리를 얼마나 차지하나다. 모니터링 도구가 보여주는 게 이것이다.
  • 프로세스를 내리면 자원이 회수된다 - 열어둔 파일·연결·메모리가 프로세스에 매여 있어, 프로세스가 끝나면 OS가 다 거둬 간다. 이게 “재시작하면 깨끗해지는” 이유다.

정리

  • 프로그램은 디스크의 파일(레시피), 프로세스는 그걸 지금 실행 중인 활동(요리)이다 - 하나의 프로그램에서 여러 프로세스가 뜬다.
  • 프로세스는 실행될 때 자기만의 메모리·자원 목록·실행 상태 셋을 받는다 - 이 셋이 뒤 글들의 밑바닥이다.
  • 프로세스끼리 메모리는 격리되지만 존재·파일·포트는 서로 보여서, 그 나머지를 컨테이너가 더 가린다.
  • 프로세스는 PID로 가리키고, 죽으면 열어둔 자원이 함께 회수된다.

다음 글은 그 “자기만의 메모리”가 어떻게 가능한지 - 가상 메모리로 간다.