지금 이 글을 읽는 순간, 여러분의 컴퓨터에서는 브라우저, 음악 플레이어, 메신저, 백신 프로그램이 동시에 돌아가고 있을 것입니다. 그런데 CPU 코어는 한 번에 딱 하나의 명령어만 처리할 수 있습니다. 운영체제가 여러 프로그램을 동시에 실행하는 원리는 대체 무엇일까요? 정답은 ‘진짜 동시’가 아니라 ‘엄청나게 빠른 번갈아 가며 실행’입니다. 초당 수천 번씩 프로그램을 전환하며 마치 동시에 실행되는 것처럼 보이게 만드는 운영체제의 마법, 지금부터 낱낱이 파헤쳐 드립니다.
목차
- 멀티태스킹의 진실 — ‘동시’가 아닌 ‘착각’의 기술
- 프로세스란 무엇인가 — 실행 중인 프로그램의 해부학
- CPU 스케줄링 — 누가, 언제, 얼마나 쓸지 결정하는 법
- 컨텍스트 스위칭의 비용과 부작용
- 멀티스레드와 멀티코어 — 진짜 동시 실행의 세계
- 전문가 관점: 현대 OS 스케줄링 트렌드와 실무 적용
1. 멀티태스킹의 진실 — ‘동시’가 아닌 ‘착각’의 기술 {#1}
“동시에 실행된다”는 표현은 엄밀히 말하면 틀렸습니다. 정확히는 **”너무 빨리 번갈아가며 실행되어 동시처럼 보인다”**입니다. 이 개념을 이해하는 것이 운영체제 동작 원리의 출발점입니다.
단일 코어 CPU의 한계와 시분할 시스템
CPU 코어 하나는 어떤 순간에도 딱 하나의 명령어 스트림만 처리할 수 있습니다. 이것은 물리 법칙이며 예외가 없습니다. 그렇다면 1960년대 초 연구자들은 어떻게 하나의 컴퓨터를 여러 사람이 동시에 쓰는 문제를 해결했을까요?
해답은 **시분할 시스템(Time-Sharing System)**이었습니다. 시간을 아주 작은 조각(Time Slice, 또는 Quantum)으로 쪼개어 각 프로그램에게 순서대로 CPU를 넘겨주는 방식입니다. MIT에서 1961년 개발한 CTSS(Compatible Time-Sharing System)가 그 시초입니다.
[시분할 시스템의 동작 개념]
시간 → |──20ms──|──20ms──|──20ms──|──20ms──|──20ms──|
브라우저 음악앱 메신저 브라우저 음악앱
↑ ↑ ↑ ↑ ↑
타이머 타이머 타이머 타이머 타이머
인터럽트 인터럽트 인터럽트 인터럽트 인터럽트
20밀리초(ms)는 0.02초입니다. 사람이 인지할 수 없을 만큼 짧습니다. 음악이 끊기지 않고 들리고, 브라우저가 멈추지 않아 보이는 이유는 이 전환이 너무 빠르기 때문입니다. 마치 영화 필름이 초당 24장의 정지 이미지를 연속으로 보여주어 움직임처럼 보이게 하는 것과 같은 원리입니다.
타이머 인터럽트: 강제로 CPU를 빼앗는 신호
각 프로그램이 자발적으로 “이제 다른 프로그램에게 CPU를 넘기겠습니다”라고 양보하길 기다린다면 어떻게 될까요? 무한 루프에 빠진 프로그램이 CPU를 영원히 독점할 것입니다.
운영체제는 이를 막기 위해 하드웨어 타이머 인터럽트를 사용합니다. 시스템 타이머가 일정 시간마다 CPU에 인터럽트 신호를 보내면, CPU는 현재 실행 중인 프로그램을 강제로 중단하고 운영체제의 인터럽트 핸들러로 제어권을 넘깁니다. 운영체제는 이 기회를 이용해 다음에 실행할 프로그램을 선택합니다.
[타이머 인터럽트 처리 흐름]
1. 프로그램 A 실행 중
2. 타이머 인터럽트 발생 (하드웨어)
3. CPU → 커널 모드 전환 (사용자 모드 → 커널 모드)
4. 인터럽트 핸들러 실행 (운영체제 코드)
5. 스케줄러가 다음 실행할 프로그램 B를 선택
6. 컨텍스트 스위칭 (A의 상태 저장 → B의 상태 복원)
7. 커널 모드 → 사용자 모드 전환
8. 프로그램 B 실행 재개
이처럼 운영체제는 자신이 직접 CPU를 점유해 실행하는 것이 아니라, 인터럽트라는 신호를 통해 주기적으로 제어권을 획득하고 교통정리를 한 뒤 다시 물러납니다. 운영체제 자체는 대부분의 시간 동안 대기 상태에 있습니다.
2. 프로세스란 무엇인가 — 실행 중인 프로그램의 해부학 {#2}
운영체제가 여러 프로그램을 동시에 실행하는 원리를 이해하려면 ‘프로세스’가 무엇인지부터 정확히 알아야 합니다. 프로그램과 프로세스는 다릅니다.
프로그램 vs 프로세스: 악보 vs 연주
**프로그램(Program)**은 디스크에 저장된 정적인 코드 덩어리입니다. 악보와 같습니다. 악보 자체는 소리를 내지 않습니다.
**프로세스(Process)**는 프로그램이 메모리에 올라와 실제로 실행되고 있는 동적인 상태입니다. 오케스트라가 악보를 연주하는 것과 같습니다. 같은 악보(프로그램)를 여러 오케스트라(프로세스)가 동시에 연주할 수 있습니다. 크롬 브라우저 하나를 여러 개 실행하면 동일한 프로그램에서 여러 프로세스가 생성되는 것도 같은 이치입니다.
프로세스의 메모리 구조
프로세스가 생성되면 운영체제는 해당 프로세스에게 독립적인 가상 메모리 공간을 할당합니다.
[프로세스의 가상 메모리 레이아웃]
높은 주소
┌─────────────────────┐
│ 커널 영역 │ ← 운영체제 코드 (사용자 접근 불가)
├─────────────────────┤
│ 스택 (Stack) │ ← 함수 호출 정보, 지역 변수
│ ↓ │ (높은 주소 → 낮은 주소 방향으로 성장)
│ │
│ ↑ │
│ 힙 (Heap) │ ← 동적 할당 메모리 (new, malloc)
├─────────────────────┤ (낮은 주소 → 높은 주소 방향으로 성장)
│ BSS 영역 │ ← 초기화되지 않은 전역/정적 변수
├─────────────────────┤
│ 데이터 영역 │ ← 초기화된 전역/정적 변수
├─────────────────────┤
│ 코드 영역 (Text) │ ← 실행할 프로그램 명령어 (읽기 전용)
└─────────────────────┘
낮은 주소
각 프로세스는 이 가상 메모리 공간을 오로지 자신만의 공간으로 인식합니다. 프로세스 A는 프로세스 B의 메모리를 직접 볼 수도, 건드릴 수도 없습니다. 운영체제가 하드웨어의 **MMU(Memory Management Unit)**와 협력해 각 프로세스의 가상 주소를 물리 주소로 변환하고, 영역 침범을 원천 차단합니다. 한 프로그램이 오류로 폭주해도 다른 프로그램이 영향을 받지 않는 이유가 바로 이 격리(isolation) 덕분입니다.
PCB: 프로세스의 이력서
운영체제는 각 프로세스에 대한 모든 정보를 **PCB(Process Control Block, 프로세스 제어 블록)**라는 자료구조에 기록합니다.
[PCB의 주요 구성 요소]
┌────────────────────────────────────┐
│ Process Control Block │
├────────────────────────────────────┤
│ 프로세스 ID (PID) : 4823 │ ← 고유 식별자
│ 프로세스 상태 : RUNNING │ ← 현재 상태
│ 프로그램 카운터 (PC) : 0x00A4F2 │ ← 다음 실행할 명령어 주소
│ CPU 레지스터 값 : {...} │ ← 현재 레지스터 스냅샷
│ 메모리 관리 정보 : {...} │ ← 페이지 테이블 포인터
│ 열린 파일 목록 : [fd0,fd1] │ ← 사용 중인 파일 디스크립터
│ CPU 스케줄링 정보 : priority=5│ ← 우선순위, 대기 시간
│ I/O 상태 정보 : {...} │ ← 대기 중인 I/O 목록
└────────────────────────────────────┘
PCB는 컨텍스트 스위칭의 핵심 도구입니다. 프로세스가 CPU를 잠시 빼앗길 때, 현재 상태를 PCB에 저장해두고, 다시 CPU를 받으면 PCB에서 복원합니다. 마치 책에 북마크를 끼워두고 나중에 정확히 그 페이지부터 다시 읽는 것과 같습니다.
프로세스의 5가지 상태
프로세스는 생성부터 소멸까지 다섯 가지 상태를 오갑니다.
생성
│
▼
┌──────────────(New)──────────────┐
│ 프로세스 생성 중 │
└───────────────┬─────────────────┘
│ 승인
▼
┌─────────────(Ready)─────────────┐
│ CPU 할당 대기 중 (준비 완료) │◄──────────────┐
└───────────────┬─────────────────┘ │
│ 디스패치 (CPU 할당) │
▼ │
┌───────────(Running)─────────────┐ 타이머 │
│ 현재 CPU에서 실행 중 │──인터럽트──────┘
└───────┬───────────────┬─────────┘
│ I/O 요청 등 │ 완료
▼ ▼
┌────(Waiting)─┐ ┌──(Terminated)──┐
│ I/O 완료 대기│ │ 프로세스 종료 │
└──────┬───────┘ └────────────────┘
│ I/O 완료
└────────────► Ready
이 상태 전환이 초당 수백~수천 번 일어나며, 운영체제는 수십~수백 개의 프로세스 상태를 동시에 관리합니다.
3. CPU 스케줄링 — 누가, 언제, 얼마나 쓸지 결정하는 법 {#3}
준비(Ready) 상태의 프로세스가 여러 개 있을 때, 다음에 CPU를 누구에게 줄지 결정하는 것이 CPU 스케줄링입니다. 어떤 알고리즘을 쓰느냐에 따라 시스템 전체 성능과 사용자 경험이 크게 달라집니다.
스케줄링의 두 가지 큰 분류
선점형(Preemptive) 스케줄링: 운영체제가 강제로 CPU를 빼앗을 수 있습니다. 타이머 인터럽트를 이용해 실행 중인 프로세스를 강제 중단하고 다른 프로세스에게 CPU를 넘깁니다. 현대 운영체제(Linux, Windows, macOS)는 모두 선점형입니다. 응답성이 좋고 한 프로세스의 독점을 방지할 수 있습니다.
비선점형(Non-Preemptive) 스케줄링: 프로세스가 자발적으로 CPU를 반납할 때까지 기다립니다. 구현이 단순하지만 한 프로세스가 오래 실행되면 다른 프로세스의 응답이 느려집니다. 초기 배치 처리 시스템에서 사용했습니다.
주요 CPU 스케줄링 알고리즘
① FCFS (First-Come, First-Served) — 먼저 온 순서대로
가장 단순한 알고리즘. 먼저 도착한 프로세스를 먼저 처리합니다.
[FCFS 예시]
프로세스: P1(실행시간 24ms) → P2(3ms) → P3(3ms)
타임라인: |────────P1────────|─P2─|─P3─|
시간(ms): 0 24 27 30
P1 대기시간: 0ms
P2 대기시간: 24ms ← 짧은 작업이 긴 작업 뒤에 오면 오래 기다림
P3 대기시간: 27ms
평균 대기시간: (0 + 24 + 27) / 3 = 17ms
단점: 호위 효과(Convoy Effect). 짧은 프로세스가 긴 프로세스 뒤에 줄을 서면 전체 대기 시간이 크게 늘어납니다.
② SJF (Shortest Job First) — 짧은 작업 우선
실행 시간이 짧은 프로세스를 먼저 처리해 평균 대기 시간을 최소화합니다.
[SJF 예시 (같은 프로세스, 순서 변경)]
프로세스: P2(3ms) → P3(3ms) → P1(24ms)
타임라인: |─P2─|─P3─|────────P1────────|
시간(ms): 0 3 6 30
P2 대기시간: 0ms
P3 대기시간: 3ms
P1 대기시간: 6ms
평균 대기시간: (0 + 3 + 6) / 3 = 3ms ← FCFS의 17ms 대비 획기적 감소
단점: 기아(Starvation). 실행 시간이 긴 프로세스는 짧은 프로세스가 계속 들어오면 영원히 실행되지 못할 수 있습니다. 또한 프로세스의 실행 시간을 미리 알기 어렵습니다.
③ Round Robin (RR) — 돌아가며 공평하게
현대 운영체제의 핵심 알고리즘입니다. 각 프로세스에게 동일한 시간 할당량(Time Quantum, 보통 10~100ms)을 부여하고, 시간이 지나면 강제로 다음 프로세스로 넘어갑니다.
[Round Robin 예시 (Quantum = 4ms)]
프로세스: P1(24ms), P2(3ms), P3(3ms)
타임라인:
|──P1──|─P2─|─P3─|──P1──|──P1──|──P1──|──P1──|──P1──|
0 4 7 10 14 18 22 26 30
→ P2, P3는 4ms 내에 완료되어 빠르게 응답
→ P1은 6번의 Time Quantum으로 나뉘어 실행
평균 대기시간: (6 + 4 + 7) / 3 = 5.67ms
Time Quantum 설정의 트레이드오프:
Time Quantum 너무 작음 → 컨텍스트 스위칭 빈번 → 오버헤드 증가
Time Quantum 너무 큼 → FCFS와 유사 → 응답성 저하
일반적으로 10~100ms 사이에서 시스템 특성에 맞게 설정
④ Priority Scheduling — 우선순위 스케줄링
각 프로세스에 우선순위를 부여하고 높은 우선순위 프로세스를 먼저 실행합니다. 긴급한 시스템 프로세스(바이러스 검사, 시스템 업데이트)와 일반 사용자 프로세스를 구분하는 데 유용합니다.
java
// Linux에서 프로세스 우선순위 확인 (Java에서 실행)
ProcessBuilder pb = new ProcessBuilder("ps", "-eo", "pid,ni,comm");
Process process = pb.start();
// nice 값: -20(최고 우선순위) ~ 19(최저 우선순위)
// 일반 사용자 프로세스: nice = 0 (기본값)
// 백그라운드 작업: nice = 10~19 (낮은 우선순위)
⑤ MLFQ (Multi-Level Feedback Queue) — 현대 OS의 실제 방식
Linux, Windows, macOS가 실제로 사용하는 방식은 위 알고리즘들의 조합입니다. 여러 우선순위 큐를 두고, 프로세스의 행동 패턴에 따라 큐 사이를 이동시킵니다.
[MLFQ 구조]
우선순위 높음
┌─────────────────────────────────┐
│ Q0: Quantum=8ms (대화형 작업) │ ← UI, 마우스 입력 등
├─────────────────────────────────┤
│ Q1: Quantum=16ms │
├─────────────────────────────────┤
│ Q2: Quantum=32ms (배치 작업) │ ← 파일 압축, 백업 등
└─────────────────────────────────┘
우선순위 낮음
규칙:
- 새 프로세스는 Q0에 진입
- Q0에서 Quantum을 다 쓰면 → Q1으로 강등 (CPU 집중형으로 판단)
- I/O 후 복귀하면 → 상위 큐로 승격 (대화형으로 판단)
- 오래 기다린 프로세스 → 주기적으로 상위 큐로 승격 (기아 방지)
4. 컨텍스트 스위칭의 비용과 부작용 {#4}
컨텍스트 스위칭은 멀티태스킹을 가능하게 하는 핵심 메커니즘이지만, 공짜가 아닙니다. 운영체제가 여러 프로그램을 동시에 실행하는 원리에서 컨텍스트 스위칭의 비용을 이해하는 것은 실무 성능 최적화에 직결됩니다.
컨텍스트 스위칭의 정확한 절차
[프로세스 A → 프로세스 B로의 컨텍스트 스위칭]
1. 타이머 인터럽트 발생
└─ CPU 제어권 → 커널
2. 현재 프로세스 A의 상태 저장 (PCB_A에 기록)
├─ 프로그램 카운터 (PC) 저장
├─ CPU 레지스터 전체 저장 (범용 레지스터, 스택 포인터 등)
├─ 메모리 관리 정보 저장
└─ I/O 상태 정보 저장
3. 스케줄러 실행
└─ 준비 큐에서 다음 실행할 프로세스 B 선택
4. 프로세스 B의 상태 복원 (PCB_B에서 로드)
├─ 프로그램 카운터 복원
├─ CPU 레지스터 전체 복원
├─ 메모리 관리 정보 복원 (TLB 플러시 포함)
└─ I/O 상태 복원
5. 커널 → 사용자 모드 전환
└─ 프로세스 B 실행 재개
컨텍스트 스위칭의 직접 비용
레지스터 저장·복원 자체는 수십 나노초에 불과합니다. 그러나 진짜 비용은 **캐시 오염(Cache Pollution)**에서 옵니다.
프로세스 A가 실행되는 동안 CPU 캐시(L1/L2/L3)에는 A의 데이터와 명령어가 가득 차 있습니다. 프로세스 B로 전환되면 이 캐시 내용은 대부분 쓸모없어지고, B의 데이터를 RAM에서 다시 불러와야 합니다. 이 캐시 콜드 스타트(Cache Cold Start) 때문에 스위칭 직후 프로세스 B는 잠시 느려집니다.
컨텍스트 스위칭 비용 분류:
직접 비용 (수십 나노초)
├─ 레지스터 저장/복원
├─ 커널-사용자 모드 전환
└─ 스케줄러 실행
간접 비용 (수 마이크로초~수십 마이크로초)
├─ TLB(Translation Lookaside Buffer) 플러시
│ → 가상→물리 주소 변환 캐시 초기화
├─ CPU 캐시 오염 (L1/L2/L3 캐시 미스 증가)
└─ 파이프라인 플러시
실무에서의 교훈
java
// ❌ 과도한 스레드 생성 — 컨텍스트 스위칭 오버헤드 유발
ExecutorService executor = Executors.newFixedThreadPool(1000); // 스레드 1000개
for (int i = 0; i < 1000; i++) {
executor.submit(() -> {
// 아주 짧은 작업 (수 마이크로초)
int result = heavyComputation();
});
}
// 실제 연산 시간 < 컨텍스트 스위칭 오버헤드 → 오히려 느려짐
// ✅ CPU 코어 수에 맞는 스레드 풀 — 스위칭 최소화
int coreCount = Runtime.getRuntime().availableProcessors();
ExecutorService executor = Executors.newFixedThreadPool(coreCount);
// CPU 코어 수 = 스레드 수 → 컨텍스트 스위칭 최소화
스레드 수가 CPU 코어 수를 크게 초과하면 스위칭 오버헤드가 실제 연산 시간을 잠식합니다. 이것이 고성능 서버가 스레드 풀 크기를 신중하게 조율하는 이유입니다.
5. 멀티스레드와 멀티코어 — 진짜 동시 실행의 세계 {#5}
지금까지는 단일 코어에서의 ‘가짜 동시 실행’을 다뤘습니다. 이제 진짜 동시 실행인 멀티스레드와 멀티코어를 살펴봅니다.
스레드: 프로세스 안의 실행 단위
**스레드(Thread)**는 프로세스 내의 실행 흐름 단위입니다. 하나의 프로세스 안에 여러 스레드가 존재할 수 있으며, 스레드들은 같은 프로세스의 코드 영역, 데이터 영역, 힙을 공유합니다.
[프로세스 vs 스레드 메모리 구조]
단일 스레드 프로세스 멀티스레드 프로세스
┌─────────────────┐ ┌─────────────────────────────┐
│ 코드 영역 │ │ 코드 영역 │ ← 공유
│ 데이터 영역 │ │ 데이터 영역 │ ← 공유
│ 힙 │ │ 힙 │ ← 공유
│ 스택 (T1) │ │ 스택(T1) │스택(T2)│스택(T3) │ ← 각자 보유
│ 레지스터 (T1) │ │ 레지스터 │레지스터│레지스터 │ ← 각자 보유
└─────────────────┘ └─────────────────────────────┘
**프로세스 간 통신(IPC)**은 파이프, 소켓, 공유 메모리 등 복잡한 메커니즘이 필요하지만, 같은 프로세스의 스레드 간 통신은 공유 메모리를 직접 읽고 쓰면 됩니다. 이것이 스레드가 경량(Lightweight)이라 불리는 이유이며, 스레드 간 컨텍스트 스위칭 비용이 프로세스 간 스위칭보다 훨씬 저렴한 이유입니다.
멀티코어: 진짜 물리적 병렬 처리
코어가 4개인 CPU는 진짜로 4개의 프로세스(또는 스레드)를 물리적으로 동시에 실행할 수 있습니다.
[4코어 CPU의 병렬 실행]
코어 0 코어 1 코어 2 코어 3
시간 t1: P1 실행 P2 실행 P3 실행 P4 실행 ← 진짜 동시!
시간 t2: P5 실행 P1 실행 P6 실행 P2 실행
시간 t3: P3 실행 P7 실행 P1 실행 P8 실행
운영체제의 스케줄러는 이제 단순히 “다음엔 누구?”가 아니라 “어느 코어에서 누구를?”이라는 2차원 문제를 풀어야 합니다. **부하 균형(Load Balancing)**이 핵심 과제로 등장합니다. 특정 코어만 과부하되고 다른 코어가 놀면 전체 성능이 낭비됩니다.
경쟁 조건과 동기화: 공유의 대가
멀티스레드의 공유 메모리는 강력하지만 위험합니다. 두 스레드가 동시에 같은 데이터를 수정하면 **경쟁 조건(Race Condition)**이 발생합니다.
java
// ❌ 경쟁 조건 발생 예시
public class BankAccount {
private int balance = 1000;
// 두 스레드가 동시에 호출하면?
public void withdraw(int amount) {
if (balance >= amount) { // 스레드A: 잔고 1000 확인 ✓
// 스레드B: 잔고 1000 확인 ✓ (동시!)
balance -= amount; // 스레드A: 1000 - 800 = 200
// 스레드B: 1000 - 800 = 200 (덮어씀!)
} // 결과: 800원을 두 번 인출했는데 잔고가 200원!
}
}
// ✅ synchronized로 임계 구역 보호
public class BankAccount {
private int balance = 1000;
public synchronized void withdraw(int amount) {
// 한 번에 하나의 스레드만 진입 가능
if (balance >= amount) {
balance -= amount;
}
}
}
이러한 동기화 문제를 잘못 처리하면 **교착상태(Deadlock)**가 발생할 수 있습니다. 스레드 A는 자원 1을 갖고 자원 2를 기다리고, 스레드 B는 자원 2를 갖고 자원 1을 기다리는 상황입니다. 둘 다 영원히 기다리게 됩니다.
[교착상태 발생 조건]
스레드 A: 자원1 보유 → 자원2 대기 중
↑ ↓
스레드 B: 자원2 보유 → 자원1 대기 중
→ 두 스레드 모두 영원히 대기 상태 (시스템 멈춤)
6. 전문가 관점: 현대 OS 스케줄링 트렌드와 실무 적용 {#6}
운영체제가 여러 프로그램을 동시에 실행하는 원리는 수십 년에 걸쳐 지속적으로 발전해왔습니다. 현대 OS와 최신 트렌드를 살펴봅니다.
Linux CFS: 완전 공정 스케줄러
Linux 커널 2.6.23(2007년)부터 채택된 **CFS(Completely Fair Scheduler)**는 현재 대부분의 Linux 배포판이 사용하는 스케줄러입니다. 핵심 철학은 “모든 프로세스에게 CPU를 공평하게 나눠준다”입니다.
CFS는 각 프로세스의 **가상 실행 시간(vruntime)**을 추적합니다. 가장 적게 실행된 프로세스(vruntime이 가장 작은)를 다음에 실행합니다. 우선순위는 nice 값으로 조절하며, 높은 우선순위 프로세스는 vruntime이 더 천천히 증가합니다.
bash
# Linux에서 프로세스 스케줄링 정보 확인
$ cat /proc/<PID>/sched
# nice 값 변경 (우선순위 낮추기)
$ nice -n 10 ./my_background_task
# 실행 중인 프로세스의 우선순위 실시간 변경
$ renice -n 5 -p <PID>
비동기 I/O와 이벤트 루프: 다른 접근법
Node.js, Nginx 같은 시스템은 멀티스레드 대신 **단일 스레드 이벤트 루프(Event Loop)**로 수만 개의 동시 연결을 처리합니다.
[이벤트 루프 동작 원리]
단일 스레드
│
▼
┌─────────────────────────────┐
│ 이벤트 루프 │
│ │
│ ① 이벤트 큐에서 작업 꺼냄 │
│ ② 콜백 실행 (빠르게!) │
│ ③ I/O는 OS에 위임 │
│ ④ 다음 이벤트 처리 │
└─────────────────────────────┘
▲
│ I/O 완료 시 콜백 등록
[OS 비동기 I/O]
(파일읽기, 네트워크 등)
javascript
// Node.js 이벤트 루프 예시
// 단일 스레드지만 수천 개의 요청을 동시에 처리
const http = require('http');
const fs = require('fs');
http.createServer((req, res) => {
// I/O 작업을 OS에 위임하고 즉시 반환
// → 스레드가 블록되지 않음 → 다음 요청 즉시 처리 가능
fs.readFile('./data.json', (err, data) => {
// I/O 완료 후 콜백이 이벤트 큐에 등록되어 실행
res.end(data);
});
}).listen(3000);
CPU 집중 작업에는 멀티스레드가, I/O 집중 작업에는 이벤트 루프 방식이 효율적입니다. 현대 백엔드 시스템은 두 방식을 적절히 조합합니다.
가상화와 컨테이너: 프로세스 격리의 진화
Docker 컨테이너와 가상머신(VM)은 운영체제의 프로세스 격리를 한 단계 더 발전시킨 기술입니다.
[격리 수준 비교]
프로세스 격리 (기본 OS)
└─ 가상 메모리로 메모리 공간 분리
단점: 같은 OS 커널 공유, 파일시스템 공유
컨테이너 (Docker)
└─ Linux Namespace + cgroups 활용
→ 프로세스 트리, 네트워크, 파일시스템, 사용자 독립
→ 커널은 공유 (가볍고 빠름)
가상머신 (VM)
└─ 하이퍼바이저가 하드웨어 가상화
→ OS 전체를 독립적으로 실행
→ 완전한 격리 (무겁지만 강력한 보안)
결국 운영체제의 프로세스 스케줄링 원리는 클라우드 컴퓨팅의 자원 관리, 쿠버네티스의 파드 스케줄링, 서버리스 함수의 실행 관리까지 그대로 연장됩니다. OS의 기초를 이해하면 현대 인프라 전체가 보입니다.
결론
운영체제가 여러 프로그램을 동시에 실행하는 원리는 세 가지 핵심으로 정리됩니다. 첫째, 시분할 시스템과 타이머 인터럽트로 CPU를 초고속으로 번갈아 사용해 동시 실행처럼 보이게 만듭니다. 둘째, PCB와 컨텍스트 스위칭으로 각 프로세스의 상태를 정확하게 저장하고 복원합니다. 셋째, Round Robin과 MLFQ 같은 스케줄링 알고리즘이 공정성과 응답성을 균형 있게 유지합니다. 이 원리를 이해하면 스레드 풀 설계, 동기화 처리, 성능 튜닝이 훨씬 명확해집니다. 오늘 학습한 프로세스 상태 다이어그램을 직접 그려보고, 자신이 쓰는 언어로 간단한 멀티스레드 코드를 작성해보는 것부터 시작해보세요.
답글 남기기
댓글을 달기 위해서는 로그인해야합니다.