오늘은 프로세스에 대해 알아보겠습니다. 우리가 컴퓨터를 사용할 때 수많은 프로그램이 동시에 실행되는데, 이 각각의 실행 단위를 프로세스라고 부릅니다. 운영체제는 이 프로세스들을 어떻게 관리하고 있을까요? 프로세스의 상태 변화와 재미있는 가족 관계(?)인 계층 구조에 대해 쉽고 자세하게 알아봅시다! ✨
본 내용은 <혼자 공부하는 컴퓨터 구조 + 운영체제>를 바탕으로 만들어졌습니다.
🚦 프로세스의 5가지 상태 변화: 태어나서 끝날 때까지
모든 프로세스는 자신만의 '상태'를 가집니다. 운영체제는 **PCB(Process Control Block)**라는 특별한 기록부에 이 상태를 적어두고 프로세스를 관리하죠. 프로세스는 마치 우리 인생처럼 여러 상태를 거쳐갑니다.
- 🌱 생성 (New)
- 프로세스가 막 만들어지는 단계예요.
- 운영체제가 프로세스를 위해 메모리 공간을 확보하고, PCB를 할당해 줍니다.
- 생성 상태가 끝나면 바로 실행되는 것이 아니라, CPU 차례를 기다리는 '준비 상태'가 됩니다.
- 🚦 준비 (Ready)
- CPU만 할당받으면 당장이라도 실행될 수 있지만, 다른 프로세스가 CPU를 사용 중이라 잠시 대기하는 상태예요.
- 준비 상태 큐(Queue)에서 자기 차례를 기다립니다.
- 준비 상태에서 실행 상태로 바뀌는 것을 **디스패치(dispatch)**라고 불러요.
- 🏃♂️ 실행 (Running)
- 드디어 CPU를 할당받아 명령어를 하나씩 실행하는, 가장 활발한 상태입니다!
- 하지만 CPU를 무한정 쓸 수는 없어요. 할당된 시간이 끝나면 (타이머 인터럽트 발생!) 다시 '준비 상태'로 돌아가 다음 차례를 기다립니다.
- 만약 실행 도중 파일 읽기/쓰기 같은 입출력(I/O) 작업이 필요하면, 작업이 끝날 때까지 기다려야 하므로 '대기 상태'로 넘어갑니다.
- ⏳ 대기 (Waiting/Blocked)
- 주로 입출력 작업이 완료되기를 기다리는 상태예요. 입출력 장치는 CPU보다 훨씬 느리기 때문에, 마냥 기다릴 수 없어 CPU를 다른 프로세스에게 양보하고 대기하는 것이죠.
- 입출력 작업 외에도 특정 이벤트가 발생하기를 기다릴 때 대기 상태가 될 수 있습니다.
- 기다리던 작업이나 이벤트가 완료되면, 다시 '준비 상태'로 돌아가 CPU 할당을 기다립니다.
- 🏁 종료 (Terminated)
- 프로세스가 맡은 일을 모두 마치고 실행이 끝난 상태입니다.
- 운영체제는 이 프로세스가 사용했던 메모리와 PCB 등 모든 자원을 정리합니다.
* 대기 상태의 일반적인 정의
- 프로세스의 대기 상태가 되는 이유에 입출력 작업만 있는 것이 아니다.
- 특정 이벤트가 일어나길 기다릴 때 대기 상태가 된다.
- 프로세스가 대기 상태가 되는 대부분의 원인이 입출력 작업이기 때문에 "프로세스가 입출력 작업을 하면 대기 상태가 된다"고 알면 된다.
👨👩👧👦 프로세스 가족 관계: 계층 구조
많은 운영체제는 프로세스들을 계층적인 구조로 관리합니다. 마치 가족처럼 부모와 자식 관계가 있어요.
- 부모 프로세스 (Parent Process): 새로운 프로세스를 생성한 프로세스.
- 자식 프로세스 (Child Process): 부모 프로세스에 의해 생성된 프로세스.
부모와 자식은 별개의 프로세스이기 때문에 각자 고유한 **PID (Process ID)**를 가집니다. 일부 운영체제에서는 자식 프로세스의 PCB에 부모 프로세스의 ID인 **PPID (Parent PID)**를 기록해두기도 해요.
자식 프로세스는 또 다른 자식 프로세스를 낳을 수 있어서, 컴퓨터가 부팅될 때 실행되는 최초의 프로세스부터 시작해 나무(Tree)처럼 계층 구조가 만들어집니다.
예시: 우리가 컴퓨터를 켜고 로그인한 뒤, 터미널(셸)에서 문서 편집기(Vim)를 실행하는 과정을 생각해 볼까요?
- 컴퓨터 부팅 시 최초 프로세스가 실행됩니다. (이 프로세스는 모든 프로세스의 조상이)
- 최초 프로세스는 로그인 담당 프로세스를 자식으로 생성합니다.
- 로그인 성공 후, 로그인 프로세스는 **사용자 인터페이스 프로세스(셸, 예를 들어 bash)**를 자식으로 생성합니다.
- 사용자가 셸에서 vim 명령어를 입력하면, 셸 프로세스는 Vim 프로세스를 자식으로 생성합니다.
🤔 모든 프로세스의 시작점은 누구일까요?
- 유닉스 계열: init
- 리눅스: systemd
- macOS: launchd
이 최초 프로세스들은 항상 PID 1번을 가지며, 모든 프로세스의 최상위 부모 역할을 합니다. 리눅스나 macOS에서 pstree 명령어를 터미널에 입력하면 이 계층 구조를 직접 확인할 수 있습니다.
pstree 명령어는 프로세스의 계층 구조를 보여주는 명령어입니다. 리눅스에서 pstree 명령어를 입력하면 systemd가 최상단에 있다는 것을 확인할 수 있고, macOS에서 pstree 명령어를 입력하면 launchd가 최상단에 있는 것을 확인할 수 있습니다.
✨ 프로세스는 어떻게 태어날까?: fork()와 exec()
부모 프로세스는 어떻게 자식 프로세스를 만들까요? 바로 fork()와 exec()라는 특별한 기술(시스템 호출)을 사용합니다! 비유하자면, **'복제 후 옷 갈아입기'**와 같아요.
- fork() - 복제하기 👯♀️
- 부모 프로세스는 fork()를 호출해서 자기 자신과 똑같은 복사본을 만듭니다. 이 복사본이 바로 자식 프로세스입니다.
- 자식 프로세스는 부모의 메모리 내용, 열려있는 파일 목록 등 대부분의 자원을 물려받습니다.
- 하지만 복사본이라도 엄연히 다른 프로세스! PID나 메모리 주소는 부모와 다릅니다.
- exec() - 새 옷 갈아입기 👔
- fork()로 만들어진 자식 프로세스(부모 복사본)는 종종 자신만의 새로운 임무를 수행해야 합니다. 이때 exec()를 호출해요.
- exec()는 현재 프로세스(자식)의 메모리 공간을 완전히 새로운 프로그램의 내용으로 덮어씌웁니다.
- 코드 영역, 데이터 영역 등이 새로운 프로그램의 것으로 바뀌고, 다른 영역은 초기화됩니다. 마치 자식 프로세스가 새 옷으로 갈아입는 것과 같습니다.
예시: 아까 셸에서 ls 명령어를 실행하는 경우를 다시 볼까요?
- 사용자가 ls를 입력하면, **셸 프로세스(부모)**는 fork()를 호출해 자신의 복사본인 자식 프로세스를 만듭니다.
- 이 자식 프로세스는 즉시 exec()를 호출하여, 자신의 메모리 공간을 ls 프로그램의 내용으로 덮어씌웁니다.
- 이제 자식 프로세스는 더 이상 셸의 복사본이 아니라, ls 명령을 실행하는 프로세스가 되어 파일 목록을 화면에 출력합니다.
이렇게 대부분의 프로세스는 부모로부터 fork되어 복제된 후, exec를 통해 각자 맡은 프로그램으로 변신하여 실행됩니다. 이 과정이 반복되면서 복잡한 프로세스 계층 구조가 만들어지는 것이다.
(💡 참고: 가끔은 fork()만 하고 exec()를 호출하지 않는 경우도 있어요. 이때는 부모와 자식이 동일한 코드를 실행하며 동시에 작업을 처리하게 됩니다. 이 경우 프로세스와 자식 프로세스는 같은 코드를 병행하여 실행하는 프로세스가 됩니다.)
🚀 마치며
오늘은 프로세스가 어떤 상태들을 거치며 실행되고 종료되는지, 그리고 어떻게 부모-자식 관계를 통해 계층 구조를 이루는지 알아보았습니다. 특히 fork()와 exec()를 이용한 '복제 후 옷 갈아입기' 방식은 운영체제가 프로그램을 실행하는 핵심 원리 중 하나랍니다.
'운영체제' 카테고리의 다른 글
📌 11-1. CPU 스케줄링 (CPU Scheduling) (0) | 2025.04.20 |
---|---|
🧵 스레드(Thread)와 프로세스(Process)의 이해 (0) | 2025.04.19 |
🖥 운영체제를 알아야 하는 이유와 큰 그림 (0) | 2025.04.19 |
운영체제(OS)란 무엇일까? 컴퓨터 시스템의 핵심 파헤치기 ✌️ (0) | 2025.04.17 |
🧠 프로세스 개요 (4) | 2025.04.14 |