"프로세스는 실행 중인 프로그램이다. 운영체제의 작업 단위."
프로그램은 디스크에 저장된 수동적인 파일이고, 프로세스는 그것이 메모리에 올라와 실행 중인 능동적인 존재다. OS는 프로세스를 생성·스케줄·종료하며 시스템 자원을 관리한다.
| 구분 | 프로그램 | 프로세스 |
|---|---|---|
| 정의 | 디스크에 저장된 실행 파일 | 메모리에 올라와 실행 중인 프로그램 |
| 상태 | 수동적 (passive) | 능동적 (active) |
| 존재 위치 | 보조 저장장치 | 메모리 |
| 예시 | /usr/bin/chrome 파일 | 실행 중인 크롬 창 |
같은 프로그램을 두 번 실행하면 두 개의 독립된 프로세스가 생성된다.
프로세스는 메모리에서 아래와 같은 구조로 배치된다.
높은 주소
┌─────────────┐
│ Stack │ ← 함수 호출, 지역 변수, 매개변수 (아래로 성장)
├─────────────┤
│ ↓ │
│ │
│ ↑ │
├─────────────┤
│ Heap │ ← 동적 메모리 할당 (위로 성장)
├─────────────┤
│ Data │ ← 전역 변수, 정적 변수
├─────────────┤
│Text (Code) │ ← 실행할 프로그램 코드 (읽기 전용)
└─────────────┘
낮은 주소
| 영역 | 내용 | 크기 |
|---|---|---|
| Text | 실행 코드 | 고정 |
| Data | 전역·정적 변수 | 고정 |
| Heap | 동적 할당 메모리 (malloc, new) | 가변 |
| Stack | 함수 호출 스택, 지역 변수 | 가변 |
Stack과 Heap은 서로 반대 방향으로 성장한다. 둘이 만나면 스택 오버플로우 또는 힙 오버플로우가 발생한다.
프로세스는 실행되는 동안 다음 5가지 상태 중 하나에 있다.
생성
│
▼
[New]
│ admitted
▼
┌──── [Ready] ◄────────────────────┐
│ │ scheduler dispatch │
│ ▼ │ I/O 완료 또는
│ [Running] ─── interrupt ──────┘ 이벤트 완료
│ │
│ ├── I/O 또는 이벤트 대기 ──► [Waiting]
│ │
│ └── exit
│ │
└──────────► [Terminated]
| 상태 | 설명 |
|---|---|
| New | 프로세스가 막 생성된 상태 |
| Ready | CPU를 할당받기 위해 대기 중인 상태 |
| Running | CPU에서 명령어가 실행 중인 상태 |
| Waiting | I/O 완료나 이벤트를 기다리는 상태 |
| Terminated | 실행이 끝난 상태 |
한 번에 하나의 프로세스만 CPU에서 Running 상태일 수 있다.
단, 멀티코어 시스템에서는 코어 수만큼 동시에 Running 가능하다.
OS는 각 프로세스의 정보를 PCB(프로세스 제어 블록) 라는 자료구조에 저장한다.
┌──────────────────────┐
│ 프로세스 상태 │ ← New / Ready / Running / Waiting / Terminated
├──────────────────────┤
│ 프로세스 번호 (PID) │
├──────────────────────┤
│ 프로그램 카운터 (PC)│ ← 다음 실행할 명령어 주소
├──────────────────────┤
│ CPU 레지스터 │ ← 문맥 교환 시 저장/복원
├──────────────────────┤
│ CPU 스케줄링 정보 │ ← 우선순위, 스케줄링 큐 포인터
├──────────────────────┤
│ 메모리 관리 정보 │ ← 페이지 테이블, 세그먼트 테이블
├──────────────────────┤
│ 입출력 상태 정보 │ ← 열린 파일 목록 등
├──────────────────────┤
│ 계정 정보 │ ← CPU 사용 시간, 시간 제한 등
└──────────────────────┘
PCB는 프로세스가 CPU를 빼앗겼다가 다시 돌아올 때 어디서부터 이어 실행할지 알 수 있게 해주는 핵심 자료구조다.
OS는 프로세스를 큐(Queue)로 관리한다.
| 큐 이름 | 설명 |
|---|---|
| Ready Queue | CPU를 기다리는 프로세스의 집합 |
| Wait Queue | I/O 완료나 특정 이벤트를 기다리는 프로세스의 집합 |
┌──────────────────┐
새 프로세스 ──► │ Ready Queue │ ──► CPU
└──────────────────┘ │
▲ │ I/O 요청
│ ┌─────▼──────┐
└───────── │ Wait Queue │
I/O 완료 └────────────┘
| 스케줄러 | 역할 | 실행 빈도 |
|---|---|---|
| 장기 스케줄러 (Long-term) | 어떤 프로세스를 메모리에 올릴지 결정 | 드물게 |
| 단기 스케줄러 (Short-term / CPU) | Ready Queue에서 어떤 프로세스에게 CPU를 줄지 결정 | 매우 자주 |
| 중기 스케줄러 (Medium-term) | 메모리가 부족할 때 프로세스를 디스크로 내보냄 (스와핑) | 필요시 |
현대 OS는 대부분 장기 스케줄러를 두지 않는다. 단기 스케줄러가 핵심이다.
CPU가 한 프로세스에서 다른 프로세스로 전환할 때, 현재 실행 중인 프로세스의 상태를 PCB에 저장하고 다음 프로세스의 PCB를 불러오는 작업이다.
프로세스 P0 실행
│
│ 인터럽트 또는 시스템 콜
▼
P0 상태를 PCB0에 저장
│
▼
PCB1에서 P1 상태 복원
│
▼
프로세스 P1 실행
│
│ 인터럽트
▼
P1 상태를 PCB1에 저장
│
▼
PCB0에서 P0 상태 복원
│
▼
프로세스 P0 재개
문맥 교환이 일어나는 동안 CPU는 아무 유용한 작업도 하지 못한다. 이 시간을 오버헤드(Overhead) 라고 한다. 전환이 잦을수록 오버헤드도 커진다.
OS는 프로세스 트리 구조로 프로세스를 관리한다. 프로세스가 다른 프로세스를 생성하면 생성한 쪽이 부모(Parent), 생성된 쪽이 자식(Child) 이다.
init (PID 1)
/ | \
bash sshd cron
/ \
ls vim
Unix/Linux에서의 프로세스 생성은 두 단계다.
pid = fork(); // 부모를 복사해 자식 프로세스 생성
exec(); // 자식 프로세스에 새 프로그램을 덮어씌워 실행
| 시스템 콜 | 역할 |
|---|---|
fork() | 현재 프로세스를 복사해 자식 프로세스를 생성. 반환값이 0이면 자식, 양수면 부모 |
exec() | 현재 프로세스의 메모리를 새 프로그램으로 교체하여 실행 |
자식 프로세스는 부모의 주소 공간을 복사해서 시작한다.
이후 exec()를 호출하면 자식은 완전히 다른 프로그램으로 대체된다.
| 시스템 콜 | 역할 |
|---|---|
exit() | 프로세스가 스스로 종료. OS에 자원 반납 요청 |
wait() | 부모 프로세스가 자식의 종료를 기다림 |
abort() | 부모 프로세스가 자식을 강제 종료 |
알아야 할 개념
wait() 없이 먼저 종료되어 부모 없이 남겨진 자식 프로세스. 보통 init이 새 부모가 됨wait()를 호출하지 않아 PCB가 메모리에 남아 있는 상태자식 종료 → exit() 호출 → 자원 반납
│
▼
부모가 wait() 호출 전까지 PCB 유지 ← Zombie
│
▼
부모가 wait() 호출 → PCB 제거 → 완전 종료
프로세스는 기본적으로 독립적이다. 각자의 메모리 공간을 가지며 서로 직접 접근할 수 없다. 협력이 필요한 경우 OS가 제공하는 IPC(Inter-Process Communication) 메커니즘을 사용한다.
IPC 방식은 크게 두 가지다.
| 방식 | 설명 | 특징 |
|---|---|---|
| 공유 메모리 (Shared Memory) | 두 프로세스가 동일한 메모리 영역을 공유 | 빠름, 동기화 문제 발생 가능 |
| 메시지 전달 (Message Passing) | OS를 통해 메시지를 주고받음 | 느림, 동기화 문제 없음 |
두 프로세스가 동일한 메모리 영역을 직접 읽고 쓰는 방식이다.
┌─────────────────────────────────────────┐
│ 메모리 │
│ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ 프로세스A │ │공유 메모리│ │ 프로세스B│ │
│ │ 공간 │ │ 영역 │ │ 공간 │ │
│ └──────────┘ └──────────┘ └────────┘ │
└─────────────────────────────────────────┘
A ──────────read/write──────────► B
생산자-소비자 문제는 공유 메모리의 대표적인 예시다.
생산자 프로세스: 데이터를 버퍼에 넣음 (write)
소비자 프로세스: 버퍼에서 데이터를 꺼냄 (read)
버퍼의 종류:
OS 커널을 통해 메시지를 주고받는 방식이다. 프로세스가 직접 메모리를 공유하지 않는다.
프로세스 A ──► send(msg) ──► [OS Kernel] ──► receive(msg) ──► 프로세스 B
통신 방식 분류
| 구분 | 직접 통신 | 간접 통신 |
|---|---|---|
| 방식 | 프로세스 이름을 명시해서 전송 | 메일박스(포트)를 통해 전송 |
| 예시 | send(P, msg) | send(mailbox, msg) |
| 특징 | 수신자가 고정됨 | 여러 프로세스가 같은 메일박스 공유 가능 |
| 구분 | 동기 (Blocking) | 비동기 (Non-blocking) |
|---|---|---|
| send | 수신자가 받을 때까지 대기 | 보내고 바로 다른 작업 진행 |
| receive | 메시지가 올 때까지 대기 | 메시지가 없으면 null 반환 |
| 용량 | 설명 |
|---|---|
| Zero Capacity | 큐 없음. 발신자는 수신자가 받을 때까지 무조건 대기 |
| Bounded Capacity | 큐 용량 제한. 가득 차면 발신자 대기 |
| Unbounded Capacity | 큐 무제한. 발신자는 절대 대기하지 않음 |
공유 메모리는 속도가 중요할 때, 메시지 전달은 단순성과 안전성이 중요할 때 선택한다.
| 절 | 핵심 내용 |
|---|---|
| 3.1 | 프로세스 = 실행 중인 프로그램. 메모리 구조(Text/Data/Heap/Stack), 5가지 상태, PCB |
| 3.2 | Ready/Wait Queue, 장기·단기·중기 스케줄러, 문맥 교환과 오버헤드 |
| 3.3 | fork()로 생성, exec()로 교체. exit()/wait()로 종료. 좀비·고아 프로세스 |
| 3.4 | IPC = 공유 메모리(빠름, 동기화 필요) vs 메시지 전달(안전, OS 경유) |