운영체제

프로세스 : 상태 변화와 계층 구조

잔잔한 흐름 2025. 4. 19. 14:17

오늘은 프로세스에 대해 알아보겠습니다. 우리가 컴퓨터를 사용할 때 수많은 프로그램이 동시에 실행되는데, 이 각각의 실행 단위를 프로세스라고 부릅니다. 운영체제는 이 프로세스들을 어떻게 관리하고 있을까요? 프로세스의 상태 변화와 재미있는 가족 관계(?)인 계층 구조에 대해 쉽고 자세하게 알아봅시다! ✨

본 내용은 <혼자 공부하는 컴퓨터 구조 + 운영체제>를 바탕으로 만들어졌습니다.


🚦 프로세스의 5가지 상태 변화: 태어나서 끝날 때까지

모든 프로세스는 자신만의 '상태'를 가집니다. 운영체제는 **PCB(Process Control Block)**라는 특별한 기록부에 이 상태를 적어두고 프로세스를 관리하죠. 프로세스는 마치 우리 인생처럼 여러 상태를 거쳐갑니다.

  1. 🌱 생성 (New)
    • 프로세스가 막 만들어지는 단계예요. 
    • 운영체제가 프로세스를 위해 메모리 공간을 확보하고, PCB를 할당해 줍니다.
    • 생성 상태가 끝나면 바로 실행되는 것이 아니라, CPU 차례를 기다리는 '준비 상태'가 됩니다. 
  2. 🚦 준비 (Ready)
    • CPU만 할당받으면 당장이라도 실행될 수 있지만, 다른 프로세스가 CPU를 사용 중이라 잠시 대기하는 상태예요. 
    • 준비 상태 큐(Queue)에서 자기 차례를 기다립니다.
    • 준비 상태에서 실행 상태로 바뀌는 것을 **디스패치(dispatch)**라고 불러요. 
  3. 🏃‍♂️ 실행 (Running)
    • 드디어 CPU를 할당받아 명령어를 하나씩 실행하는, 가장 활발한 상태입니다! 
    • 하지만 CPU를 무한정 쓸 수는 없어요. 할당된 시간이 끝나면 (타이머 인터럽트 발생!) 다시 '준비 상태'로 돌아가 다음 차례를 기다립니다.
    • 만약 실행 도중 파일 읽기/쓰기 같은 입출력(I/O) 작업이 필요하면, 작업이 끝날 때까지 기다려야 하므로 '대기 상태'로 넘어갑니다. 
  4. ⏳ 대기 (Waiting/Blocked)
    • 주로 입출력 작업이 완료되기를 기다리는 상태예요. 입출력 장치는 CPU보다 훨씬 느리기 때문에, 마냥 기다릴 수 없어 CPU를 다른 프로세스에게 양보하고 대기하는 것이죠.
    • 입출력 작업 외에도 특정 이벤트가 발생하기를 기다릴 때 대기 상태가 될 수 있습니다. 
    • 기다리던 작업이나 이벤트가 완료되면, 다시 '준비 상태'로 돌아가 CPU 할당을 기다립니다. 
  5. 🏁 종료 (Terminated)
    • 프로세스가 맡은 일을 모두 마치고 실행이 끝난 상태입니다. 
    • 운영체제는 이 프로세스가 사용했던 메모리와 PCB 등 모든 자원을 정리합니다.

<프로세스 상태 다이어그램>

 

* 대기 상태의 일반적인 정의

  • 프로세스의 대기 상태가 되는 이유에 입출력 작업만 있는 것이 아니다.
  • 특정 이벤트가 일어나길 기다릴 때 대기 상태가 된다.
  • 프로세스가 대기 상태가 되는 대부분의 원인이 입출력 작업이기 때문에 "프로세스가 입출력 작업을 하면 대기 상태가 된다"고 알면 된다.

👨‍👩‍👧‍👦 프로세스 가족 관계: 계층 구조

많은 운영체제는 프로세스들을 계층적인 구조로 관리합니다. 마치 가족처럼 부모와 자식 관계가 있어요.

  • 부모 프로세스 (Parent Process): 새로운 프로세스를 생성한 프로세스.
  • 자식 프로세스 (Child Process): 부모 프로세스에 의해 생성된 프로세스.

부모와 자식은 별개의 프로세스이기 때문에 각자 고유한 **PID (Process ID)**를 가집니다. 일부 운영체제에서는 자식 프로세스의 PCB에 부모 프로세스의 ID인 **PPID (Parent PID)**를 기록해두기도 해요. 

자식 프로세스는 또 다른 자식 프로세스를 낳을 수 있어서, 컴퓨터가 부팅될 때 실행되는 최초의 프로세스부터 시작해 나무(Tree)처럼 계층 구조가 만들어집니다. 

예시: 우리가 컴퓨터를 켜고 로그인한 뒤, 터미널(셸)에서 문서 편집기(Vim)를 실행하는 과정을 생각해 볼까요? 

  1. 컴퓨터 부팅 시 최초 프로세스가 실행됩니다. (이 프로세스는 모든 프로세스의 조상이)
  2. 최초 프로세스는 로그인 담당 프로세스를 자식으로 생성합니다.
  3. 로그인 성공 후, 로그인 프로세스는 **사용자 인터페이스 프로세스(셸, 예를 들어 bash)**를 자식으로 생성합니다.
  4. 사용자가 셸에서 vim 명령어를 입력하면, 셸 프로세스는 Vim 프로세스를 자식으로 생성합니다.

🤔 모든 프로세스의 시작점은 누구일까요?

  • 유닉스 계열: init
  • 리눅스: systemd
  • macOS: launchd

이 최초 프로세스들은 항상 PID 1번을 가지며, 모든 프로세스의 최상위 부모 역할을 합니다. 리눅스나 macOS에서 pstree 명령어를 터미널에 입력하면 이 계층 구조를 직접 확인할 수 있습니다.

 

pstree 명령어는 프로세스의 계층 구조를 보여주는 명령어입니다. 리눅스에서 pstree 명령어를 입력하면 systemd가 최상단에 있다는 것을 확인할 수 있고, macOS에서 pstree 명령어를 입력하면 launchd가 최상단에 있는 것을 확인할 수 있습니다.

 


✨ 프로세스는 어떻게 태어날까?: fork()와 exec()

부모 프로세스는 어떻게 자식 프로세스를 만들까요? 바로 fork()exec()라는 특별한 기술(시스템 호출)을 사용합니다! 비유하자면, **'복제 후 옷 갈아입기'**와 같아요. 

  1. fork() - 복제하기 👯‍♀️
    • 부모 프로세스는 fork()를 호출해서 자기 자신과 똑같은 복사본을 만듭니다. 이 복사본이 바로 자식 프로세스입니다.
    • 자식 프로세스는 부모의 메모리 내용, 열려있는 파일 목록 등 대부분의 자원을 물려받습니다. 
    • 하지만 복사본이라도 엄연히 다른 프로세스! PID메모리 주소는 부모와 다릅니다. 
  2. exec() - 새 옷 갈아입기 👔
    • fork()로 만들어진 자식 프로세스(부모 복사본)는 종종 자신만의 새로운 임무를 수행해야 합니다. 이때 exec()를 호출해요.
    • exec()는 현재 프로세스(자식)의 메모리 공간을 완전히 새로운 프로그램의 내용으로 덮어씌웁니다. 
    • 코드 영역, 데이터 영역 등이 새로운 프로그램의 것으로 바뀌고, 다른 영역은 초기화됩니다. 마치 자식 프로세스가 새 옷으로 갈아입는 것과 같습니다.

예시: 아까 셸에서 ls 명령어를 실행하는 경우를 다시 볼까요? 

  1. 사용자가 ls를 입력하면, **셸 프로세스(부모)**는 fork()를 호출해 자신의 복사본인 자식 프로세스를 만듭니다.
  2. 자식 프로세스는 즉시 exec()를 호출하여, 자신의 메모리 공간을 ls 프로그램의 내용으로 덮어씌웁니다.
  3. 이제 자식 프로세스는 더 이상 셸의 복사본이 아니라, ls 명령을 실행하는 프로세스가 되어 파일 목록을 화면에 출력합니다. 

이렇게 대부분의 프로세스는 부모로부터 fork되어 복제된 후, exec를 통해 각자 맡은 프로그램으로 변신하여 실행됩니다. 이 과정이 반복되면서 복잡한 프로세스 계층 구조가 만들어지는 것이다.

(💡 참고: 가끔은 fork()만 하고 exec()를 호출하지 않는 경우도 있어요. 이때는 부모와 자식이 동일한 코드를 실행하며 동시에 작업을 처리하게 됩니다. 이 경우 프로세스와 자식 프로세스는 같은 코드를 병행하여 실행하는 프로세스가 됩니다.)


🚀 마치며

오늘은 프로세스가 어떤 상태들을 거치며 실행되고 종료되는지, 그리고 어떻게 부모-자식 관계를 통해 계층 구조를 이루는지 알아보았습니다. 특히 fork()와 exec()를 이용한 '복제 후 옷 갈아입기' 방식은 운영체제가 프로그램을 실행하는 핵심 원리 중 하나랍니다.