← Posts

OS · Process · Memory

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

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

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

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

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

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

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

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

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

plaintext
프로그램(program)  =  디스크에 있는 파일. 실행 절차가 적힌 레시피.
프로세스(process)  =  그 레시피로 지금 요리 중인 활동.

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

실행되면 세 가지를 받는다

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

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

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

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

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

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

자원 목록: 열어둔 것들

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

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

실행 상태: 어디까지 했나

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

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

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

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

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

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

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

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

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

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

정리

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

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

관련 글