⚡ CPU 스케줄링 완전 마스터 가이드¶
운영체제의 두뇌 - CPU를 효율적으로 관리하는 핵심 기술
문서 범위와 검증 기준¶
- 범위: 단일 CPU의 교과서형 작업 모델에서 시작해 다중처리기·실시간 스케줄링으로 확장하는 학습 자료입니다. 의사 코드와 점수 그래프는 운영체제 구현 코드나 범용 성능 순위가 아닙니다.
- 전제: 별도 표기가 없는 계산 예제는 CPU burst가 알려져 있고 I/O, 컨텍스트 스위치 비용, 캐시·NUMA 효과가 없는 모델을 가정합니다. SJF/RM/EDF의 최적성은 각 절에 적은 제한 조건 안에서만 성립합니다.
- 근거 상태와 환경: Linux·Windows·Solaris 설명은 커널/제품 버전을 고정해 공식 문서와 trace로 다시 확인해야 합니다. 지연·처리량·비율·별점은 입력과 측정 환경이 없는 한 설명용 가상값이며 SLA가 아닙니다.
- 실패/재시도: 시뮬레이션은 잘못된 입력, 빈 ready queue, deadline miss와 과부하를 명시적 상태로 기록합니다. 실패한 태스크를 무한 재실행하지 말고 재시도 한도·취소·격리 정책을 둡니다.
- 완료 증거: 비교 결과에는 workload, 도착/burst/deadline, quantum, CPU 수, 선점·오버헤드 모델, OS/커널 버전, 반복 횟수와 원시 trace를 남깁니다. 평균값뿐 아니라 starvation과 deadline miss가 없는지 확인해야 완료입니다.
📋 목차 (Table of Contents)¶
- 🎯 개요와 핵심 개념
- 🏗️ 기본 개념과 프로세스 상태
- 🎛️ CPU 스케줄러와 디스패처
- 📏 스케줄링 기준과 성능 지표
- 🧮 스케줄링 알고리즘 심화
- 🔀 다중처리기 스케줄링
- ⏱️ 실시간 CPU 스케줄링
- 🖥️ 실제 운영체제 사례
- 🎯 연습 문제와 시뮬레이션
- 📚 핵심 요약과 추가 학습
graph TB
subgraph "🎯 학습 목표"
L1[CPU 스케줄링 원리 이해]
L2[다양한 알고리즘 비교 분석]
L3[성능 지표 측정 및 최적화]
L4[실무 적용 능력 배양]
end
subgraph "🔧 핵심 구성요소"
C1[스케줄러 Scheduler]
C2[디스패처 Dispatcher]
C3[준비 큐 Ready Queue]
C4[CPU 버스트 CPU Burst]
end
L1 --> C1
L2 --> C2
L3 --> C3
L4 --> C4
style L1 fill:#ff6b6b,color:#fff
style L2 fill:#4ecdc4,color:#fff
style L3 fill:#45b7d1,color:#fff
style L4 fill:#96ceb4,color:#fff
🎯 개요와 핵심 개념¶
**CPU 스케줄링**은 실행 가능한 작업에 CPU 시간을 배분하는 운영체제 메커니즘입니다. 처리량, 지연, 공정성, 에너지와 deadline은 서로 충돌할 수 있으므로 정책 선택만으로 효율이나 공정성이 자동 보장되지는 않습니다.
🌟 CPU 스케줄링이란?¶
graph TD
A[다수의 프로세스] --> B[CPU 스케줄러]
B --> C{어떤 프로세스를<br/>언제 실행할까?}
C --> D[📊 성능 최적화]
C --> E[⚖️ 공정성 보장]
C --> F[⚡ 응답성 향상]
D --> G[CPU 활용률 ↑<br/>처리량 ↑]
E --> H[모든 프로세스가<br/>적절한 시간 확보]
F --> I[사용자 체감<br/>반응속도 ↑]
style B fill:#ff6b6b,color:#fff
style D fill:#4ecdc4,color:#fff
style E fill:#45b7d1,color:#fff
style F fill:#96ceb4,color:#fff
🎯 핵심 학습 목표¶
| 영역 | 학습 목표 | 실무 적용 |
|---|---|---|
| 🔧 기본 원리 | 다중 프로그래밍 환경에서의 CPU 스케줄링 역할 | 시스템 성능 분석 |
| 📊 알고리즘 | FCFS, SJF, RR, Priority 등 다양한 알고리즘 | 부하와 목표에 맞는 후보 비교 |
| ⚖️ 성능 평가 | 평균 대기시간, 응답시간, 처리율 측정 | 시스템 튜닝 및 최적화 |
| 🌐 실제 적용 | 실시간, 다중처리기 환경 이해 | 고성능 시스템 설계 |
🚀 실생활 비유로 이해하기¶
graph LR
subgraph "🏥 병원 접수 시스템"
P1[환자 1] --> W[대기실]
P2[환자 2] --> W
P3[환자 3] --> W
W --> D[의사]
D --> T[치료실]
end
subgraph "💻 CPU 스케줄링"
PR1[프로세스 1] --> RQ[준비 큐]
PR2[프로세스 2] --> RQ
PR3[프로세스 3] --> RQ
RQ --> CPU[CPU]
CPU --> EX[실행]
end
style W fill:#feca57,color:#000
style RQ fill:#feca57,color:#000
style D fill:#ff6b6b,color:#fff
style CPU fill:#ff6b6b,color:#fff
🏥 병원 시스템과 CPU 스케줄링의 유사점:
- 대기실 = 준비 큐: 처리를 기다리는 공간
- 의사 = CPU: 실제 작업을 수행하는 자원
- 접수 방식 = 스케줄링 알고리즘: 누구를 먼저 처리할지 결정하는 방법
💡 왜 CPU 스케줄링이 중요한가?¶
다음 비율은 경험 데이터가 아니라 영향 영역을 한 그림에 배치하기 위한 **설명용 가중치**입니다.
pie title CPU 스케줄링 영향 영역 (설명용, 실측 비율 아님)
"시스템 성능" : 35
"사용자 만족도" : 25
"자원 효율성" : 20
"공정성" : 15
"전력 효율성" : 5
📈 성능 향상 효과¶
🔥 가상 시나리오의 Before vs After:
아래 값은 특정 시스템의 예상 개선율이 아닙니다. 같은 workload에서 기준선과 후보 정책을 반복 측정하는 보고 형식을 보여주는 예시입니다.
| 지표 | 최적화 전 | 최적화 후 | 개선율 |
|---|---|---|---|
| CPU 활용률 | 60% | 95% | ⬆️ 58% |
| 평균 응답시간 | 500ms | 50ms | ⬇️ 90% |
| 처리량 | 100 작업/분 | 300 작업/분 | ⬆️ 200% |
| 사용자 만족도 | 6/10 | 9/10 | ⬆️ 50% |
🌍 실제 시스템 적용 사례¶
graph TB
subgraph "📱 모바일 OS"
M1[배터리 최적화]
M2[터치 반응성]
M3[앱 전환 속도]
end
subgraph "🖥️ 데스크톱 OS"
D1[멀티태스킹]
D2[게임 성능]
D3[백그라운드 작업]
end
subgraph "☁️ 서버 시스템"
S1[높은 처리량]
S2[응답시간 보장]
S3[자원 격리]
end
subgraph "🚗 임베디드"
E1[실시간 제약]
E2[최소 지연시간]
E3[안전성 보장]
end
style M1 fill:#ff6b6b,color:#fff
style D1 fill:#4ecdc4,color:#fff
style S1 fill:#45b7d1,color:#fff
style E1 fill:#96ceb4,color:#fff
5.2.1 프로세스 실행 모델 (Process Execution Model)¶
프로세스 실행은 CPU 실행과 I/O 대기 구간이 번갈아 나타나는 사이클로 구성되며, 이를 **CPU-I/O 버스트 사이클**이라고 합니다.
graph TD
A[프로세스 시작<br/>Process Start] --> B[CPU 버스트<br/>CPU Burst]
B --> C[I/O 요청<br/>I/O Request]
C --> D[I/O 대기<br/>I/O Wait]
D --> E[I/O 완료<br/>I/O Complete]
E --> F[CPU 버스트<br/>CPU Burst]
F --> G{더 많은 I/O?<br/>More I/O?}
G -->|예 Yes| C
G -->|아니오 No| H[프로세스 종료<br/>Process End]
style B fill:#e1f5fe,stroke:#01579b,stroke-width:2px
style F fill:#e1f5fe,stroke:#01579b,stroke-width:2px
style D fill:#fff3e0,stroke:#ef6c00,stroke-width:2px
classDef korean font-weight:bold,font-size:11px
class A,B,C,D,E,F,G,H korean
🔍 실제 프로세스 예시¶
timeline
title 텍스트 에디터 프로세스의 실행 패턴
section 초기화
프로그램 로드 : CPU 버스트 (50ms)
설정 파일 읽기 : I/O 대기 (20ms)
section 사용자 입력 대기
UI 렌더링 : CPU 버스트 (5ms)
키보드 입력 대기 : I/O 대기 (∞)
section 파일 편집
텍스트 처리 : CPU 버스트 (10ms)
화면 업데이트 : CPU 버스트 (3ms)
section 파일 저장
데이터 압축 : CPU 버스트 (30ms)
디스크 쓰기 : I/O 대기 (100ms)
5.2.2 CPU 버스트 분포 (CPU Burst Distribution)¶
전통적인 설명에서는 다음 패턴을 자주 사용하지만 실제 분포는 workload와 관측 구간에서 측정해야 합니다.
- 짧은 CPU 버스트가 많음: I/O 중심 프로세스의 특징
- 긴 CPU 버스트가 적음: CPU 중심 프로세스의 특징
pie title CPU 버스트 시간 분포 (70/20/10 가상 예시)
"짧은 버스트 (1-10ms)<br/>Short bursts" : 70
"중간 버스트 (10-50ms)<br/>Medium bursts" : 20
"긴 버스트 (50ms+)<br/>Long bursts" : 10
📊 프로세스 유형별 특성 비교¶
graph LR
subgraph "I/O 중심 프로세스 (I/O-bound)"
A1[짧은 CPU 버스트] --> A2[빈번한 I/O 요청]
A2 --> A3[높은 사용자 상호작용]
A3 --> A4[예: 텍스트 에디터, 웹 브라우저]
end
subgraph "CPU 중심 프로세스 (CPU-bound)"
B1[긴 CPU 버스트] --> B2[적은 I/O 요청]
B2 --> B3[높은 계산 처리]
B3 --> B4[예: 과학 계산, 이미지 처리]
end
style A1 fill:#e8f5e8
style A2 fill:#e8f5e8
style A3 fill:#e8f5e8
style A4 fill:#e8f5e8
style B1 fill:#fff3e0
style B2 fill:#fff3e0
style B3 fill:#fff3e0
style B4 fill:#fff3e0
5.2.3 다중 프로그래밍의 이점 (Multiprogramming Benefits)¶
다중 프로그래밍은 다음과 같은 방법으로 CPU 활용률을 최대화합니다:
- 실행 중첩 (Overlapping execution): 한 프로세스가 I/O를 기다리는 동안 다른 프로세스가 CPU 사용
- 유휴 시간 감소 (Reducing idle time): CPU가 준비된 프로세스들로 계속 바쁘게 유지
- 처리량 향상 (Improving throughput): 단위 시간당 완료되는 프로세스 수 증가
sequenceDiagram
participant CPU as CPU 코어
participant P1 as 프로세스 1<br/>(텍스트 에디터)
participant P2 as 프로세스 2<br/>(웹 브라우저)
participant IO as I/O 장치<br/>(디스크/네트워크)
Note over CPU,IO: 시간 순서로 보는 CPU 스케줄링
CPU->>P1: 실행 (파일 편집 처리)
P1->>IO: I/O 요청 (파일 저장)
Note over P1,IO: P1이 I/O 대기 중
CPU->>P2: 프로세스 2로 전환
P2->>CPU: 계속 실행 (웹페이지 렌더링)
IO->>P1: I/O 완료 (파일 저장 완료)
Note over P1: P1이 다시 준비 상태
P2->>IO: I/O 요청 (네트워크 데이터 수신)
CPU->>P1: P1으로 다시 전환
Note over CPU: CPU 유휴 시간 최소화!
⚡ 성능 비교: 단일 vs 다중 프로그래밍¶
gantt
title CPU 활용률 비교
dateFormat X
axisFormat %L ms
section 단일 프로그래밍
P1 실행 :0, 50
CPU 유휴 :50, 200
P1 실행 :200, 250
CPU 유휴 :250, 400
P1 실행 :400, 450
section 다중 프로그래밍
P1 실행 :0, 50
P2 실행 :50, 200
P1 실행 :200, 250
P3 실행 :250, 400
P1 실행 :400, 450
결과 분석:
- 단일 프로그래밍: 이 예제 입력에서는 CPU 활용률 = 33% (150ms 실행 / 450ms 총 시간)
- 다중 프로그래밍: 이 예제는 I/O 대기가 완전히 겹친다고 가정해 100%로 계산합니다. 실제로는 idle gap, 스케줄링·I/O 병목 때문에 보장되지 않습니다.
5.3 CPU 스케줄러 (CPU Scheduler)¶
5.3.1 스케줄러 기능 (Scheduler Function)¶
**CPU 스케줄러**는 준비 큐(ready queue)에서 프로세스를 선택하여 CPU 코어를 할당합니다. 준비 큐는 다양한 방식으로 구성될 수 있습니다:
graph TD
A[준비 큐 구성 방식<br/>Ready Queue Organization] --> B[FIFO<br/>First-In-First-Out]
A --> C[우선순위 큐<br/>Priority Queue]
A --> D[다단계 큐<br/>Multi-level Queue]
A --> E[기타 자료구조<br/>Other Data Structures]
B --> F[구현 간단<br/>Simple Implementation]
C --> G[중요도 반영<br/>Importance-based]
D --> H[프로세스 분류<br/>Process Classification]
style B fill:#e1f5fe
style C fill:#e8f5e8
style D fill:#fff3e0
style E fill:#fce4ec
🔄 스케줄러의 작동 과정¶
flowchart LR
subgraph "1단계: 프로세스 도착"
A[새 프로세스] --> B[준비 큐 삽입]
end
subgraph "2단계: 스케줄링 결정"
B --> C{스케줄링<br/>정책 적용}
C --> D[최적 프로세스<br/>선택]
end
subgraph "3단계: CPU 할당"
D --> E[디스패처 호출]
E --> F[컨텍스트 스위치]
F --> G[프로세스 실행]
end
style C fill:#ffeb3b
style F fill:#ff5722,color:#fff
5.3.2 스케줄링 결정 시점 (Scheduling Decision Points)¶
CPU 스케줄링 결정은 프로세스가 상태 간에 전환될 때 발생합니다:
stateDiagram-v2
[*] --> New : 프로세스 생성
New --> Ready : 승인됨<br/>Admitted
Ready --> Running : 스케줄러 디스패치<br/>Scheduler dispatch
Running --> Ready : 인터럽트 (선점)<br/>Interrupt (preemption)
Running --> Waiting : I/O 또는 이벤트 대기<br/>I/O or event wait
Waiting --> Ready : I/O 또는 이벤트 완료<br/>I/O or event completion
Running --> [*] : 종료<br/>Exit
note right of Ready : 결정 시점 2,3<br/>Decision Point 2,3
note right of Running : 결정 시점 1,4<br/>Decision Point 1,4
state Running {
direction LR
[*] --> CPU실행중
CPU실행중 --> [*]
}
📋 스케줄링 결정 시점 상세 분석¶
| 결정 시점 | 상태 전환 | 스케줄링 유형 | 예시 상황 |
|---|---|---|---|
| 1번 | 실행 → 대기 | 비선점형 | 파일 읽기 요청, 네트워크 대기 |
| 2번 | 실행 → 준비 | 선점형 | 타임 슬라이스 만료, 높은 우선순위 프로세스 도착 |
| 3번 | 대기 → 준비 | 선점형 | I/O 완료, 이벤트 발생 |
| 4번 | 실행 → 종료 | 비선점형 | 프로그램 정상 종료, 오류로 인한 종료 |
⚖️ 선점형 vs 비선점형 스케줄링 심화 분석¶
graph TB
subgraph "비선점형 스케줄링 (Non-preemptive)"
A1[프로세스가 CPU 점유] --> A2[자발적 양보까지 계속 실행]
A2 --> A3[I/O 요청 또는 종료시에만 해제]
A3 --> A4[🟢 장점: 단순, 경합 조건 없음<br/>🔴 단점: 응답성 저하]
end
subgraph "선점형 스케줄링 (Preemptive)"
B1[OS가 강제로 CPU 회수 가능] --> B2[타이머 인터럽트 사용]
B2 --> B3[우선순위나 시간 할당량 기반]
B3 --> B4[🟢 장점: 응답성, 공정성<br/>🔴 단점: 복잡성, 경합 조건]
end
style A1 fill:#fff3e0
style A2 fill:#fff3e0
style A3 fill:#fff3e0
style A4 fill:#ffcdd2
style B1 fill:#e8f5e8
style B2 fill:#e8f5e8
style B3 fill:#e8f5e8
style B4 fill:#c8e6c9
5.3.3 선점형 스케줄링 고려사항 (Preemptive Scheduling Considerations)¶
선점형 스케줄링을 구현할 때 고려해야 할 사항들:
- 공유 데이터 접근 (Shared data access): 경합 조건 발생 가능
- 커널 모드 작업 (Kernel mode operations): 시스템 호출 중 중단 문제
- 중요한 OS 활동 (Critical OS activities): 하드웨어 인터럽트 처리
// 예시: 선점형 스케줄링에서의 경합 조건
// Example: Race condition in preemptive scheduling
int shared_counter = 0; // 공유 변수
void process_A() {
shared_counter++; // 여기서 선점될 수 있음!
// Can be preempted here!
// 선점되면 다른 프로세스가 shared_counter를 수정할 수 있음
// If preempted, another process might modify shared_counter
}
void process_B() {
shared_counter--; // 경합 조건 발생 가능
// Race condition possible
}
🛡️ 선점형 스케줄링의 안전장치¶
graph TD
A[선점형 스케줄링 실행] --> B{임계 구역인가?}
B -->|예| C[선점 금지<br/>Disable Preemption]
B -->|아니오| D[정상 선점 허용<br/>Allow Preemption]
C --> E[임계 구역 실행]
E --> F[선점 허용 복구<br/>Enable Preemption]
F --> G[스케줄링 재개]
D --> H[타이머 인터럽트]
H --> I[컨텍스트 스위치]
style C fill:#ff5722,color:#fff
style F fill:#4caf50,color:#fff
classDef critical fill:#ffcdd2,stroke:#d32f2f
class E critical
⚠️ 실제 문제 사례¶
sequenceDiagram
participant T1 as 스레드 1
participant Var as 공유변수<br/>(counter=5)
participant T2 as 스레드 2
participant OS as 운영체제
Note over T1,T2: 경합 조건 발생 시나리오
T1->>Var: 읽기 (counter=5)
T1->>T1: 계산 (5+1=6)
Note over OS: 타이머 인터럽트 발생!
OS->>T1: 선점 (PREEMPT)
OS->>T2: 실행 전환
T2->>Var: 읽기 (counter=5)
T2->>Var: 쓰기 (counter=4)
OS->>T2: 선점
OS->>T1: 실행 재개
T1->>Var: 쓰기 (counter=6)
Note over Var: 최종 결과: 6 (예상: 4)<br/>T2의 감소 연산이 손실됨!
5.4 스케줄러 유형 (Scheduler Types)¶
현대 운영체제는 다단계 스케줄링을 사용합니다:
graph TD
A[디스크의 작업 풀<br/>Job Pool on Disk] -->|장기 스케줄러<br/>Long-term scheduler| B[메모리의 준비 큐<br/>Ready Queue in Memory]
B -->|단기 스케줄러<br/>Short-term scheduler| C[CPU]
C -->|시간 할당량 만료<br/>Time quantum expires| B
C -->|I/O 요청<br/>I/O request| D[I/O 큐<br/>I/O Queue]
D -->|I/O 완료<br/>I/O complete| B
B -->|중기 스케줄러<br/>Medium-term scheduler| E[대기 큐<br/>Suspended Queue]
E -->|스왑 인<br/>Swap in| B
style A fill:#ffebee,stroke:#c62828
style B fill:#e8f5e8,stroke:#2e7d32
style C fill:#e3f2fd,stroke:#1565c0
style D fill:#fff3e0,stroke:#ef6c00
style E fill:#fce4ec,stroke:#ad1457
🎯 스케줄러별 특성 비교¶
graph LR
subgraph "장기 스케줄러 (Long-term)"
L1[📁 디스크 → 메모리]
L2[⏰ 수초~수분 간격]
L3[🎚️ 다중프로그래밍 정도 제어]
L4[⚖️ I/O vs CPU 균형]
end
subgraph "단기 스케줄러 (Short-term)"
S1[⚡ 메모리 → CPU]
S2[⏱️ 밀리초 간격]
S3[🎯 응답성 최적화]
S4[🏃 빠른 실행 필수]
end
subgraph "중기 스케줄러 (Medium-term)"
M1[💾 메모리 ↔ 디스크]
M2[🔄 스와핑 제어]
M3[📊 메모리 사용량 관리]
M4[⏳ 필요시 실행]
end
style L1 fill:#ffcdd2
style S1 fill:#c8e6c9
style M1 fill:#d1c4e9
5.4.1 장기 스케줄러 (Long-term Scheduler, Job Scheduler)¶
- 기능 (Function): 디스크에서 메모리로 가져올 작업 제어
- 실행 빈도 (Frequency): 드물게 실행 (수초, 수분 간격)
- 목표 (Goal): 다중프로그래밍 정도 제어
- 고려사항 (Consideration): I/O 중심과 CPU 중심 프로세스의 균형
def long_term_scheduler():
"""장기 스케줄러 구현 예시"""
while True:
if memory_available() and multiprogramming_level < MAX_PROCESSES:
job = select_job_from_disk()
if job:
load_to_memory(job)
add_to_ready_queue(job)
time.sleep(5) # 5초마다 확인
📈 메모리 로드 밸런싱 전략¶
아래 60/25/15 비율은 “이상적”인 보편값이 아니라 혼합 workload를 설명하기 위한 예시입니다. 실제 admission 정책은 CPU·I/O queue, 메모리 압박과 목표 지연으로 조정합니다.
pie title 설명용 프로세스 혼합비
"I/O 중심 프로세스 60%" : 60
"CPU 중심 프로세스 25%" : 25
"혼합형 프로세스 15%" : 15
이 예시가 보여 주려는 점
- I/O 중심: 빈번한 I/O로 CPU 양보 → 다른 프로세스에게 기회 제공
- CPU 중심: 긴 계산 작업 처리 → 전체적인 작업 처리량 확보
- 혼합형: 시스템 유연성 제공
5.4.2 단기 스케줄러 (Short-term Scheduler, CPU Scheduler)¶
- 기능 (Function): 준비된 프로세스 중 CPU를 받을 프로세스 선택
- 실행 빈도 (Frequency): 매우 빈번하게 실행 (밀리초 단위)
- 목표 (Goal): CPU 활용률과 시스템 응답성 최대화
- 요구사항 (Must be fast): 스케줄러 오버헤드는 최소화되어야 함
⚡ 단기 스케줄러의 성능 요구사항¶
graph LR
A[스케줄링 요청] --> B{측정 지연이<br/>환경별 budget 안인가?}
B -->|충족| C[후보 유지]
B -->|경계| D[분포와 과부하 재측정]
B -->|초과| E[원인 분해 및 정책 조정]
C --> F[p50/p95/p99 기록]
D --> G[run queue와 switch cost 확인]
E --> H[deadline/응답 SLO 영향 확인]
style C fill:#c8e6c9
style D fill:#fff3e0
style E fill:#ffcdd2
5.4.3 중기 스케줄러 (Medium-term Scheduler, Swapper)¶
- 기능 (Function): 프로세스의 메모리 스왑 인/아웃 제어
- 목적 (Purpose): 메모리 사용량과 다중프로그래밍 정도 관리
- 메커니즘 (Mechanism): 프로세스를 일시적으로 메모리에서 제거
🔄 스와핑 결정 알고리즘¶
flowchart TD
A[메모리 부족 감지] --> B{스왑 아웃 대상 선택}
B --> C[우선순위 낮은 프로세스]
B --> D[오랫동안 대기 중인 프로세스]
B --> E[메모리 사용량 큰 프로세스]
C --> F[디스크로 스왑 아웃]
D --> F
E --> F
F --> G[메모리 여유 공간 확보]
G --> H{메모리 충분?}
H -->|예| I[프로세스 로드 계속]
H -->|아니오| B
style F fill:#ff5722,color:#fff
style I fill:#4caf50,color:#fff
5.4.4 스케줄러와 함께하는 프로세스 상태 전환¶
stateDiagram-v2
[*] --> New : 프로세스 생성<br/>Create process
New --> Ready : 장기 스케줄러<br/>Long-term scheduler
Ready --> Running : 단기 스케줄러<br/>Short-term scheduler
Running --> Ready : 선점<br/>Preemption
Running --> Waiting : I/O 요청<br/>I/O request
Waiting --> Ready : I/O 완료<br/>I/O completion
Ready --> SuspendedReady : 중기 스케줄러 (스왑 아웃)<br/>Medium-term scheduler (swap out)
SuspendedReady --> Ready : 중기 스케줄러 (스왑 인)<br/>Medium-term scheduler (swap in)
Waiting --> SuspendedBlocked : 중기 스케줄러 (스왑 아웃)<br/>Medium-term scheduler (swap out)
SuspendedBlocked --> Waiting : 중기 스케줄러 (스왑 인)<br/>Medium-term scheduler (swap in)
Running --> [*] : 종료<br/>Terminate
state Ready {
direction LR
[*] --> 메모리대기
메모리대기 --> CPU할당대기
}
state Running {
direction LR
[*] --> CPU실행중
CPU실행중 --> [*]
}
📊 스케줄러별 성능 지표¶
| 스케줄러 | 실행 빈도 | 결정 시간 | 주요 목표 | 성능 영향 |
|---|---|---|---|---|
| 장기 | 수초~수분 | 100ms~1s | 시스템 안정성 | 장기적 성능 |
| 단기 | 수ms | <1ms | 응답성 | 즉각적 성능 |
| 중기 | 수십초~수분 | 10~100ms | 메모리 효율성 | 중기적 성능 |
🎛️ 다중 레벨 스케줄링의 협력¶
sequenceDiagram
participant D as 디스크
participant LTS as 장기<br/>스케줄러
participant M as 메모리
participant STS as 단기<br/>스케줄러
participant CPU as CPU
participant MTS as 중기<br/>스케줄러
Note over D,CPU: 통합 스케줄링 프로세스
D->>LTS: 새 작업 요청
LTS->>M: 메모리 로드 결정
M->>STS: 준비 큐 추가
STS->>CPU: 실행 프로세스 선택
Note over CPU: 프로세스 실행 중
CPU->>M: I/O 요청으로 대기
Note over M: 메모리 부족 상황
M->>MTS: 스왑 아웃 요청
MTS->>D: 일부 프로세스 스왑
MTS->>M: 메모리 공간 확보
M->>STS: 다른 프로세스 선택
STS->>CPU: 새 프로세스 실행
5.5 디스패처 (Dispatcher)¶
5.5.1 디스패처 기능 (Dispatcher Functions)¶
디스패처 모듈은 단기 스케줄러가 선택한 프로세스에게 CPU 제어권을 전달합니다. 이 과정은 다음을 포함합니다:
- 컨텍스트 스위칭 (Context switching): 프로세스 상태 저장/복원
- 모드 전환 (Mode switching): 사용자 모드로 전환
- 프로그램 카운터 (Program counter): 사용자 프로그램의 적절한 위치로 점프
// 간소화된 디스패처 의사코드
// Simplified dispatcher pseudocode
void dispatcher(Process* old_process, Process* new_process) {
// 1. 이전 프로세스의 컨텍스트 저장
// Save context of old process
save_context(old_process);
// 2. 프로세스 상태 업데이트
// Update process state
old_process->state = READY;
new_process->state = RUNNING;
// 3. 메모리 컨텍스트 전환
// Switch memory context
switch_memory_space(new_process->page_table);
// 4. 새 프로세스의 컨텍스트 복원
// Restore context of new process
restore_context(new_process);
// 5. 사용자 모드로 점프하여 실행 계속
// Jump to user mode and continue execution
jump_to_user_mode(new_process->program_counter);
}
🔄 디스패처 작동 과정 상세¶
sequenceDiagram
participant S as 스케줄러
participant D as 디스패처
participant CPU as CPU
participant M as 메모리
participant P1 as 프로세스 1
participant P2 as 프로세스 2
Note over S,P2: 컨텍스트 스위치 과정
S->>D: 새 프로세스 P2 선택
D->>CPU: P1 레지스터 저장
D->>M: P1 상태를 PCB에 저장
D->>M: P2 상태를 PCB에서 로드
D->>CPU: P2 레지스터 복원
D->>CPU: 사용자 모드로 전환
CPU->>P2: 실행 재개
Note over D: 디스패치 지연 시간
5.5.2 디스패치 지연 (Dispatch Latency)¶
**디스패치 지연**은 디스패처가 한 프로세스를 멈추고 다른 프로세스를 시작하는 데 걸리는 시간입니다.
gantt
title 디스패치 지연 구성 요소 (Dispatch Latency Components)
dateFormat X
axisFormat %s
section 컨텍스트 스위치
이전 컨텍스트 저장 :0, 2
자료구조 업데이트 :2, 3
새 컨텍스트 복원 :3, 5
section 모드 전환
커널→사용자 모드 :5, 6
사용자 프로그램 점프 :6, 7
⚡ 디스패치 지연에 영향을 주는 요소들¶
mindmap
root((디스패치 지연<br/>Dispatch Latency))
하드웨어 요소
저장할 레지스터 수
메모리 접근 속도
캐시 효과
파이프라인 플러시
소프트웨어 요소
메모리 관리 복잡도
페이지 테이블 크기
컨텍스트 데이터 양
우선순위 계산
시스템 상태
메모리 사용률
I/O 활동
인터럽트 빈도
프로세스 수
📊 실제 시스템에서의 디스패치 지연¶
고정 범위는 CPU·커널·보안 완화·부하에 따라 쉽게 달라집니다. 다음처럼 환경별로 budget과 증거를 채워 비교합니다.
| 시스템 유형 | 목표를 정하는 기준 | 필요한 증거 |
|---|---|---|
| 데스크톱 | 입력·화면 응답 SLO | interactive 부하의 wake-to-run 분포 |
| 서버 | 요청 latency·throughput SLO | run queue, p95/p99 지연, switch rate |
| 실시간 | 태스크별 deadline·jitter budget | WCET 가정과 deadline-miss trace |
| 임베디드 | CPU·전력·안전 요구사항 | 대상 보드에서 측정한 최악 지연 |
5.5.3 컨텍스트 스위치 모니터링 (Context Switch Monitoring)¶
현대 시스템은 컨텍스트 스위치를 모니터링하는 도구를 제공합니다:
# vmstat 출력에서 컨텍스트 스위치 비율 확인
# vmstat output showing context switch rate
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 0 1234567 256 98765 0 0 0 0 567 1234 25 15 60 0 0
↑ ↑
인터럽트 컨텍스트_스위치
interrupts context_switches
🔍 시스템 성능 분석¶
# 개별 프로세스의 컨텍스트 스위치 정보 확인
$ cat /proc/[pid]/status | grep ctxt
voluntary_ctxt_switches: 1234
nonvoluntary_ctxt_switches: 567
# 실시간 컨텍스트 스위치 모니터링
$ sar -w 1 5
Average: cswch/s
Average: 1234.56
⚖️ 컨텍스트 스위치 최적화 전략¶
graph TD
A[높은 컨텍스트 스위치 감지] --> B{원인 분석}
B --> C[타임 슬라이스가 너무 짧음]
B --> D[우선순위 조정이 빈번함]
B --> E[I/O 대기가 많음]
C --> F[타임 퀀텀 크기 조정]
D --> G[스케줄링 정책 변경]
E --> H[I/O 스케줄링 최적화]
F --> I[성능 향상]
G --> I
H --> I
style A fill:#ffcdd2
style I fill:#c8e6c9
5.6 스케줄링 기준 (Scheduling Criteria)¶
5.6.1 성능 지표 (Performance Metrics)¶
CPU 스케줄링 알고리즘은 여러 기준에 따라 평가됩니다:
| 지표 (Metric) | 설명 (Description) | 목표 (Goal) | 공식 (Formula) |
|---|---|---|---|
| CPU 활용률 | CPU가 바쁜 시간의 비율 | 최대화 | (바쁜 시간 / 전체 시간) × 100% |
| 처리량 (Throughput) | 단위 시간당 완료된 프로세스 수 | 최대화 | 완료된 프로세스 / 시간 단위 |
| 반환 시간 | 제출부터 완료까지의 총 시간 | 최소화 | 완료 시간 - 도착 시간 |
| 대기 시간 | 준비 큐에서 보낸 시간 | 최소화 | 반환 시간 - 버스트 시간 |
| 응답 시간 | 요청부터 첫 응답까지의 시간 | 최소화 | 첫 응답 - 도착 시간 |
🎯 성능 지표의 중요도¶
graph TD
A[시스템 유형별 성능 지표 중요도]
A --> B[Batch: CPU 8 / 처리량 9 / 반환 7 / 대기 6 / 응답 5]
A --> C[Interactive: CPU 6 / 처리량 5 / 반환 9 / 대기 8 / 응답 10]
A --> D[Real-time: CPU 9 / 처리량 8 / 반환 6 / 대기 7 / 응답 4]
A --> E[Mixed: CPU 7 / 처리량 6 / 반환 8 / 대기 9 / 응답 8]
style A fill:#1976d2,color:#fff
style B fill:#e3f2fd
style C fill:#e8f5e9
style D fill:#fff3e0
style E fill:#f3e5f5
5.6.2 지표 간의 관계 (Metric Relationships)¶
graph LR
A[프로세스 도착<br/>Process Arrival] --> B[준비 큐<br/>Ready Queue]
B --> C[CPU 실행<br/>CPU Execution]
C --> D[프로세스 완료<br/>Process Completion]
A -.->|응답 시간<br/>Response Time| E[첫 CPU 접근<br/>First CPU Access]
A -.->|반환 시간<br/>Turnaround Time| D
B -.->|대기 시간<br/>Waiting Time| C
style E fill:#e8f5e8
style D fill:#ffebee
style C fill:#e3f2fd
⚖️ 지표 간 트레이드오프¶
graph TD
A[성능 목표] --> B[높은 처리량]
A --> C[짧은 응답 시간]
A --> D[공정성]
B -.->|상충| C
C -.->|상충| B
D -.->|상충| B
E[해결책] --> F[적응형 스케줄링]
E --> G[다단계 큐]
E --> H[우선순위 부스팅]
style B fill:#ffcdd2
style C fill:#ffcdd2
style D fill:#ffcdd2
style F fill:#c8e6c9
style G fill:#c8e6c9
style H fill:#c8e6c9
A -.->|Turnaround Time| D
B -.->|Waiting Time| C
style E fill:#e8f5e8
style D fill:#ffebee
### 5.6.3 계산 예시 (Example Calculation)
다음 세 프로세스를 고려해봅시다:
| 프로세스 | 도착 시간 | 버스트 시간 |
|----------|-----------|-------------|
| P1 | 0 | 10 |
| P2 | 1 | 5 |
| P3 | 2 | 3 |
**FCFS 스케줄링:**
시간: 0 10 15 18 |P1 |P2 |P3 |
**계산 과정:**
- **P1**: 반환시간 = 10-0 = 10, 대기시간 = 10-10 = 0
- **P2**: 반환시간 = 15-1 = 14, 대기시간 = 14-5 = 9
- **P3**: 반환시간 = 18-2 = 16, 대기시간 = 16-3 = 13
**평균 반환 시간** = (10+14+16)/3 = 13.33ms
**평균 대기 시간** = (0+9+13)/3 = 7.33ms
### 📊 시각적 분석
```mermaid
gantt
title FCFS 스케줄링 분석
dateFormat X
axisFormat %L ms
section 프로세스 실행
P1 실행 :0, 10
P2 실행 :10, 15
P3 실행 :15, 18
section 대기 시간
P2 대기 :1, 10
P3 대기 :2, 15
💡 성능 개선 아이디어¶
만약 SJF로 스케줄링했다면:
- 평균 반환 시간: (1+7+18)/3 = 8.67ms (35% 개선!)
- 평균 대기 시간: (0+2+8)/3 = 3.33ms (55% 개선!)
5.7 스케줄링 알고리즘 (Scheduling Algorithms)¶
5.7.1 선입선출 (First-Come, First-Served, FCFS)¶
특성:
- 비선점형 알고리즘
- 간단한 FIFO 큐 구현
- 버스트 시간의 편차가 클 때 성능 저하
def fcfs_scheduling(processes):
"""FCFS 스케줄링 알고리즘 구현"""
# 도착 시간순으로 정렬
processes.sort(key=lambda p: p.arrival_time)
current_time = 0
for process in processes:
# 프로세스 도착 시간이 현재 시간보다 늦으면 대기
if current_time < process.arrival_time:
current_time = process.arrival_time
# 시작 시간과 완료 시간 계산
process.start_time = current_time
process.completion_time = current_time + process.burst_time
process.turnaround_time = process.completion_time - process.arrival_time
process.waiting_time = process.turnaround_time - process.burst_time
# 현재 시간을 완료 시간으로 업데이트
current_time = process.completion_time
return processes
⚠️ 호송 효과 (Convoy Effect)¶
문제: 짧은 프로세스들이 긴 프로세스 뒤에서 대기하는 현상
gantt
title FCFS 호송 효과 예시 (FCFS Convoy Effect Example)
dateFormat X
axisFormat %s
section CPU 스케줄
P1 (버스트=10ms) :0, 10
P2 (버스트=1ms) :10, 11
P3 (버스트=1ms) :11, 12
📊 호송 효과 분석¶
graph TD
A[긴 CPU 버스트 프로세스 P1] --> B[짧은 프로세스 P2, P3 대기]
B --> C[전체 평균 대기 시간 증가]
C --> D[시스템 응답성 저하]
E[이상적인 경우: P2, P3 먼저 실행] --> F[평균 대기 시간 단축]
F --> G[더 나은 사용자 경험]
style A fill:#ffcdd2
style B fill:#ffcdd2
style C fill:#ffcdd2
style D fill:#ffcdd2
style E fill:#c8e6c9
style F fill:#c8e6c9
style G fill:#c8e6c9
계산 예시:
- FCFS: 평균 대기 시간 = (0 + 10 + 11) / 3 = 7ms
- 최적 순서: 평균 대기 시간 = (0 + 1 + 2) / 3 = 1ms
5.7.2 최단 작업 우선 (Shortest-Job-First, SJF)¶
특성:
- 선점형(SRTF) 또는 비선점형 가능
- 모든 작업이 동시에 준비되고 burst가 정확히 알려진 비선점 단일 CPU 모델에서는 평균 대기 시간을 최소화
- 버스트 시간 예측이 필요
def sjf_scheduling(processes):
"""SJF 스케줄링 알고리즘 구현"""
# 도착 시간과 버스트 시간으로 정렬
processes.sort(key=lambda p: (p.arrival_time, p.burst_time))
ready_queue = []
current_time = 0
completed = []
while processes or ready_queue:
# 도착한 프로세스들을 준비 큐에 추가
while processes and processes[0].arrival_time <= current_time:
ready_queue.append(processes.pop(0))
if ready_queue:
# 가장 짧은 작업 선택
ready_queue.sort(key=lambda p: p.burst_time)
current_process = ready_queue.pop(0)
current_process.start_time = current_time
current_time += current_process.burst_time
current_process.completion_time = current_time
completed.append(current_process)
else:
current_time += 1 # 유휴 시간
return completed
🎯 SJF의 최적성 증명¶
graph LR
A[SJF는 명시된 단일 CPU 모델에서 평균 대기 시간 최소화] --> B[교환 논리]
B --> C[교환 논리]
C --> D[긴 작업과 짧은 작업 순서 바꾸기]
D --> E[동시 도착·정확한 burst 가정에서 감소 또는 동일]
style A fill:#4caf50,color:#fff
style E fill:#4caf50,color:#fff
5.7.3 최단 잔여 시간 우선 (Shortest Remaining Time First, SRTF)¶
SJF의 선점형 버전입니다:
gantt
title SRTF 실행 예시 (SRTF Example)
dateFormat X
axisFormat %s
section 프로세스 실행
P1 (잔여=6ms) :0, 2
P2 (잔여=4ms) :2, 6
P1 (잔여=4ms) :6, 8
P3 (잔여=2ms) :8, 10
🔄 SRTF 알고리즘 동작 과정¶
flowchart TD
A[새 프로세스 도착] --> B{현재 실행 중인<br/>프로세스가 있는가?}
B -->|예| C{새 프로세스의 버스트 시간이<br/>더 짧은가?}
B -->|아니오| D[새 프로세스 실행]
C -->|예| E[현재 프로세스 선점]
C -->|아니오| F[준비 큐에 추가]
E --> G[새 프로세스 실행]
G --> H[선점된 프로세스는<br/>준비 큐로]
F --> I[현재 프로세스 계속 실행]
style E fill:#ff5722,color:#fff
style G fill:#4caf50,color:#fff
💡 SJF vs SRTF 비교¶
| 측면 | SJF (비선점형) | SRTF (선점형) |
|---|---|---|
| 선점 | 불가능 | 가능 |
| 평균 대기 시간 | 동시 도착·정확한 burst 모델에서 최소 | 선점 비용이 없는 대응 모델에서 최소 |
| 구현 복잡도 | 단순 | 복잡 |
| 컨텍스트 스위치 | 적음 | 많음 |
| 응답 시간 | 길 수 있음 | 짧음 |
gantt
title SRTF 실행 타임라인
dateFormat X
axisFormat %L ms
section Timeline
P1 :0, 2
P2 :2, 6
P1 :6, 8
P3 :8, 10
5.7.4 우선순위 스케줄링 (Priority Scheduling)¶
우선순위에 따라 프로세스를 실행하는 알고리즘:
📋 우선순위 스케줄링 특성¶
mindmap
root((우선순위 스케줄링))
특성
각 프로세스에 우선순위 번호
높은 우선순위 프로세스 먼저 실행
선점형/비선점형 가능
문제점
기아 현상 (Starvation)
낮은 우선순위 프로세스 무한 대기
해결책
에이징 (Aging)
시간에 따른 우선순위 상승
🔄 우선순위 스케줄링 동작 과정¶
flowchart TD
A[준비 큐에서 프로세스 선택] --> B{가장 높은 우선순위<br/>프로세스 확인}
B --> C[선택된 프로세스 실행]
C --> D{선점형인가?}
D -->|예| E{더 높은 우선순위<br/>프로세스 도착?}
D -->|아니오| F[프로세스 완료까지 실행]
E -->|예| G[현재 프로세스 선점]
E -->|아니오| H[계속 실행]
G --> I[새 프로세스 실행]
F --> J[다음 프로세스 선택]
H --> K{프로세스 완료?}
K -->|예| J
K -->|아니오| E
style C fill:#4caf50,color:#fff
style G fill:#ff5722,color:#fff
style I fill:#4caf50,color:#fff
💻 에이징 기법 구현 예제¶
// 에이징 기법을 적용한 우선순위 스케줄링
#define AGING_FACTOR 10
#define MAX_PRIORITY 0
#define MIN_PRIORITY 139
typedef struct {
int pid; // 프로세스 ID
int burst_time; // CPU 버스트 시간
int priority; // 현재 우선순위 (낮을수록 높은 우선순위)
int original_priority; // 원래 우선순위
int age; // 대기 시간
time_t last_scheduled; // 마지막 스케줄링 시간
int waiting_time; // 총 대기 시간
} Process;
// 에이징 프로세스: 대기 시간에 따라 우선순위 향상
void age_processes(Process processes[], int n) {
time_t current_time = time(NULL);
for (int i = 0; i < n; i++) {
if (processes[i].last_scheduled > 0) {
// 대기 시간 계산
int time_waiting = current_time - processes[i].last_scheduled;
processes[i].age += time_waiting;
// 에이징: 일정 시간마다 우선순위 향상
int priority_boost = processes[i].age / AGING_FACTOR;
// 우선순위 개선 (0에 가까울수록 높은 우선순위)
processes[i].priority = processes[i].original_priority - priority_boost;
// 최고 우선순위 제한
if (processes[i].priority < MAX_PRIORITY) {
processes[i].priority = MAX_PRIORITY;
}
}
}
}
// 우선순위 기반 프로세스 선택
int select_highest_priority_process(Process processes[], int n) {
int highest_priority_index = -1;
int highest_priority = MIN_PRIORITY + 1;
for (int i = 0; i < n; i++) {
if (processes[i].burst_time > 0 && processes[i].priority < highest_priority) {
highest_priority = processes[i].priority;
highest_priority_index = i;
}
}
return highest_priority_index;
}
📊 우선순위 스케줄링 실행 예시¶
gantt
title 우선순위 스케줄링 실행 타임라인
dateFormat X
axisFormat %s
section 프로세스 실행
P2 (우선순위=1) :0, 4
P1 (우선순위=2) :4, 7
P3 (우선순위=3) :7, 15
⚡ 우선순위 스케줄링 장단점¶
| 장점 | 단점 |
|---|---|
| ✅ 중요한 프로세스 우선 처리 | ❌ 기아 현상 발생 가능 |
| ✅ 시스템 요구사항 반영 가능 | ❌ 우선순위 할당 복잡성 |
| ✅ 실시간 시스템에 적합 | ❌ 컨텍스트 스위치 오버헤드 |
| ✅ 유연한 스케줄링 정책 | ❌ 공정성 문제 |
🛠️ 에이징 기법 효과¶
graph LR
A[초기 상태] --> B[P1: 우선순위 5<br/>P2: 우선순위 3<br/>P3: 우선순위 1]
B --> C[시간 경과]
C --> D[에이징 적용]
D --> E[P1: 우선순위 3<br/>P2: 우선순위 1<br/>P3: 우선순위 1]
E --> F[기아 현상 해결]
style A fill:#ffcdd2
style F fill:#c8e6c9
5.7.5 라운드 로빈 (Round Robin, RR)¶
라운드 로빈은 동일 우선순위의 계속 실행 가능한 작업을 순환시키는 시분할 정책입니다. 양의 quantum과 유한한 ready set 같은 전제에서 기아 위험을 낮추지만 전체 시스템의 공정성을 보장하지는 않습니다.
🔄 라운드 로빈 특성¶
mindmap
root((라운드 로빈))
특성
선점형 알고리즘
시간 할당량 (Time Quantum)
원형 큐 구조
공정한 CPU 시간 분배
장점
응답 시간 향상
단순 동일 우선순위 모델에서 유한 대기
대화형 시스템에 적합
단점
컨텍스트 스위치 오버헤드
처리율 저하 가능
시간 할당량 설정 중요
💻 라운드 로빈 구현¶
from collections import deque
import time
class Process:
def __init__(self, pid, burst_time, arrival_time=0):
self.pid = pid
self.burst_time = burst_time
self.remaining_time = burst_time
self.arrival_time = arrival_time
self.completion_time = 0
self.waiting_time = 0
self.turnaround_time = 0
def round_robin_scheduling(processes, time_quantum):
"""
라운드 로빈 스케줄링 알고리즘 구현
"""
queue = deque(processes) # 원형 큐 사용
current_time = 0
completed_processes = []
execution_order = []
print(f"라운드 로빈 스케줄링 시작 (시간 할당량: {time_quantum}ms)")
print("-" * 50)
while queue:
process = queue.popleft() # 큐에서 다음 프로세스 선택
# 실행할 시간 결정
execution_time = min(process.remaining_time, time_quantum)
# 프로세스 실행
print(f"시간 {current_time}ms: 프로세스 P{process.pid} 실행 ({execution_time}ms)")
execution_order.append((process.pid, current_time, current_time + execution_time))
current_time += execution_time
process.remaining_time -= execution_time
if process.remaining_time == 0:
# 프로세스 완료
process.completion_time = current_time
process.turnaround_time = process.completion_time - process.arrival_time
process.waiting_time = process.turnaround_time - process.burst_time
completed_processes.append(process)
print(f" → P{process.pid} 완료 (완료 시간: {current_time}ms)")
else:
# 프로세스 재큐잉
queue.append(process)
print(f" → P{process.pid} 재큐잉 (잔여 시간: {process.remaining_time}ms)")
return completed_processes, execution_order
# 사용 예시
if __name__ == "__main__":
# 프로세스 생성
processes = [
Process(1, 24),
Process(2, 3),
Process(3, 3)
]
# 라운드 로빈 스케줄링 실행
completed, order = round_robin_scheduling(processes.copy(), time_quantum=4)
# 결과 출력
print("\n" + "="*50)
print("스케줄링 결과:")
total_waiting = sum(p.waiting_time for p in completed)
total_turnaround = sum(p.turnaround_time for p in completed)
print(f"평균 대기 시간: {total_waiting/len(completed):.2f}ms")
print(f"평균 반환 시간: {total_turnaround/len(completed):.2f}ms")
📊 라운드 로빈 실행 예시¶
gantt
title 라운드 로빈 스케줄링 (시간 할당량 = 4ms)
dateFormat X
axisFormat %s
section 프로세스 실행
P1 (4ms) :0, 4
P2 (3ms) :4, 7
P3 (3ms) :7, 10
P1 (4ms) :10, 14
P1 (4ms) :14, 18
P1 (4ms) :18, 22
P1 (4ms) :22, 26
P1 (4ms) :26, 30
⚙️ 시간 할당량의 영향¶
graph TD
A[시간 할당량 설정] --> B{할당량 크기}
B -->|매우 작음| C[높은 컨텍스트 스위치<br/>오버헤드]
B -->|매우 큼| D[FCFS와 유사<br/>응답시간 저하]
B -->|적절함| E[균형잡힌 성능]
C --> F[시스템 성능 저하]
D --> G[대화형 성능 저하]
E --> H[최적 성능]
style C fill:#ffcdd2
style D fill:#ffcdd2
style E fill:#c8e6c9
style H fill:#4caf50,color:#fff
📈 시간 할당량별 성능 비교¶
아래 점수는 측정값이 아니라 quantum에 따른 상충 관계를 시각화한 **정규화 가상 점수**입니다. 8ms가 최적이라는 결론을 일반화하지 않습니다.
graph LR
A[시간 할당량] --> B1[1ms: 응답 20 / 스위치 90 / 처리율 30]
A --> B2[2ms: 응답 40 / 스위치 80 / 처리율 50]
A --> B3[4ms: 응답 80 / 스위치 60 / 처리율 70]
A --> B4[8ms: 응답 90 / 스위치 40 / 처리율 85]
A --> B5[16ms: 응답 70 / 스위치 30 / 처리율 80]
A --> B6[32ms: 응답 50 / 스위치 20 / 처리율 60]
style A fill:#1565c0,color:#fff
style B4 fill:#c8e6c9
🎯 라운드 로빈 최적화 기법¶
flowchart TD
A[라운드 로빈 최적화] --> B[적응적 시간 할당량]
A --> C[우선순위 기반 RR]
A --> D[멀티레벨 피드백]
B --> E[프로세스 특성에 따라<br/>할당량 조정]
C --> F[우선순위별 다른<br/>시간 할당량]
D --> G[큐 간 이동으로<br/>성능 최적화]
style B fill:#e3f2fd
style C fill:#e8f5e8
style D fill:#fff3e0
💡 라운드 로빈 vs 다른 알고리즘 비교¶
| 특성 | 라운드 로빈 | FCFS | SJF | 우선순위 |
|---|---|---|---|---|
| 공정성 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐ |
| 응답 시간 | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 처리율 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| 구현 복잡도 | ⭐⭐ | ⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 오버헤드 | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
| 기아 현상 | ❌ | ❌ | ⚠️ | ⚠️ |
5.7.6 다단계 큐 스케줄링 (Multilevel Queue Scheduling)¶
프로세스 유형별로 별도의 큐를 사용하는 스케줄링 방식:
🏗️ 다단계 큐 구조¶
flowchart TD
A[다단계 큐 스케줄러] --> B[시스템 프로세스 큐]
A --> C[대화형 프로세스 큐]
A --> D[대화형 편집 큐]
A --> E[배치 프로세스 큐]
A --> F[학생 프로그램 큐]
B --> |최고 우선순위| G[CPU]
C --> |높은 우선순위| G
D --> |중간 우선순위| G
E --> |낮은 우선순위| G
F --> |최저 우선순위| G
B -.-> H[RR q=2ms]
C -.-> I[RR q=4ms]
D -.-> J[RR q=8ms]
E -.-> K[FCFS]
F -.-> L[FCFS]
style B fill:#ffcdd2
style C fill:#f8bbd9
style D fill:#e1bee7
style E fill:#d1c4e9
style F fill:#c5cae9
💻 다단계 큐 구현¶
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_QUEUES 5
#define MAX_PROCESSES 100
// 프로세스 유형 정의
typedef enum {
SYSTEM_PROCESS = 0, // 시스템 프로세스
INTERACTIVE_PROCESS, // 대화형 프로세스
INTERACTIVE_EDITING, // 대화형 편집
BATCH_PROCESS, // 배치 프로세스
STUDENT_PROCESS // 학생 프로그램
} ProcessType;
typedef struct {
int pid;
int burst_time;
int remaining_time;
int arrival_time;
ProcessType type;
int priority; // 큐 내 우선순위
} Process;
typedef struct {
Process* processes[MAX_PROCESSES];
int front, rear;
int count;
ProcessType queue_type;
int time_quantum; // 해당 큐의 시간 할당량
char* algorithm; // 큐에서 사용하는 스케줄링 알고리즘
} Queue;
// 다단계 큐 스케줄러
typedef struct {
Queue queues[MAX_QUEUES];
int queue_priorities[MAX_QUEUES]; // 큐별 우선순위
int current_time;
char* queue_names[MAX_QUEUES];
} MultilevelQueueScheduler;
// 스케줄러 초기화
void init_scheduler(MultilevelQueueScheduler* scheduler) {
// 큐 이름 설정
scheduler->queue_names[0] = "시스템 프로세스";
scheduler->queue_names[1] = "대화형 프로세스";
scheduler->queue_names[2] = "대화형 편집";
scheduler->queue_names[3] = "배치 프로세스";
scheduler->queue_names[4] = "학생 프로그램";
// 큐별 설정 초기화
for (int i = 0; i < MAX_QUEUES; i++) {
scheduler->queues[i].front = 0;
scheduler->queues[i].rear = -1;
scheduler->queues[i].count = 0;
scheduler->queues[i].queue_type = i;
scheduler->queue_priorities[i] = i; // 낮은 인덱스 = 높은 우선순위
}
// 큐별 시간 할당량 및 알고리즘 설정
scheduler->queues[0].time_quantum = 2; // 시스템: RR 2ms
scheduler->queues[0].algorithm = "RR";
scheduler->queues[1].time_quantum = 4; // 대화형: RR 4ms
scheduler->queues[1].algorithm = "RR";
scheduler->queues[2].time_quantum = 8; // 편집: RR 8ms
scheduler->queues[2].algorithm = "RR";
scheduler->queues[3].time_quantum = 0; // 배치: FCFS
scheduler->queues[3].algorithm = "FCFS";
scheduler->queues[4].time_quantum = 0; // 학생: FCFS
scheduler->queues[4].algorithm = "FCFS";
scheduler->current_time = 0;
}
// 프로세스를 적절한 큐에 삽입
void enqueue_process(MultilevelQueueScheduler* scheduler, Process* process) {
Queue* target_queue = &scheduler->queues[process->type];
if (target_queue->count < MAX_PROCESSES) {
target_queue->rear = (target_queue->rear + 1) % MAX_PROCESSES;
target_queue->processes[target_queue->rear] = process;
target_queue->count++;
printf("프로세스 P%d를 %s 큐에 추가\n",
process->pid, scheduler->queue_names[process->type]);
}
}
// 가장 높은 우선순위 큐에서 프로세스 선택
Process* select_next_process(MultilevelQueueScheduler* scheduler) {
for (int i = 0; i < MAX_QUEUES; i++) {
Queue* queue = &scheduler->queues[i];
if (queue->count > 0) {
// 큐에서 프로세스 제거
Process* process = queue->processes[queue->front];
queue->front = (queue->front + 1) % MAX_PROCESSES;
queue->count--;
return process;
}
}
return NULL; // 실행할 프로세스 없음
}
📊 다단계 큐 실행 예시¶
gantt
title 다단계 큐 스케줄링 실행 예시
dateFormat X
axisFormat %s
section 시스템 프로세스
P1 시스템 :0, 3
section 대화형 프로세스
P2 대화형 :3, 7
P3 대화형 :7, 10
section 배치 프로세스
P4 배치 :10, 18
⚖️ 다단계 큐 스케줄링 방식¶
flowchart TD
A[큐 간 스케줄링 방식] --> B[고정 우선순위]
A --> C[시분할]
B --> D[높은 우선순위 큐 우선 실행]
B --> E[기아 현상 가능]
C --> F[각 큐에 CPU 시간 할당]
C --> G[예: 80% 포어그라운드<br/>20% 백그라운드]
style B fill:#ffeb3b
style C fill:#4caf50,color:#fff
5.7.7 다단계 피드백 큐 (Multilevel Feedback Queue)¶
프로세스가 큐 간 이동이 가능한 고급 스케줄링 알고리즘:
🔄 다단계 피드백 큐 동작¶
stateDiagram-v2
Q0: 큐 0 (RR, q=8ms)
Q1: 큐 1 (RR, q=16ms)
Q2: 큐 2 (FCFS)
[*] --> Q0: 새 프로세스 도착
Q0 --> Q1: 시간 할당량 초과
Q1 --> Q2: 시간 할당량 초과
Q0 --> [*]: 프로세스 완료
Q1 --> [*]: 프로세스 완료
Q2 --> [*]: 프로세스 완료
Q2 --> Q0: I/O 완료 (우선순위 부스트)
Q1 --> Q0: I/O 완료 (우선순위 부스트)
📋 다단계 피드백 큐 규칙¶
flowchart TD
A[다단계 피드백 큐 규칙] --> B[규칙 1: Priority A > Priority B면 A 실행]
A --> C[규칙 2: Priority A = Priority B면 RR]
A --> D[규칙 3: 새 작업은 최고 우선순위로]
A --> E[규칙 4: 할당량 사용하면 우선순위 감소]
A --> F[규칙 5: 주기적 우선순위 부스트]
style A fill:#2196f3,color:#fff
style F fill:#4caf50,color:#fff
💻 다단계 피드백 큐 구현¶
import time
from collections import deque
from enum import Enum
class QueueLevel(Enum):
HIGH = 0 # 높은 우선순위 (짧은 시간 할당량)
MEDIUM = 1 # 중간 우선순위
LOW = 2 # 낮은 우선순위 (FCFS)
class Process:
def __init__(self, pid, burst_time, io_frequency=0):
self.pid = pid
self.original_burst_time = burst_time
self.remaining_time = burst_time
self.queue_level = QueueLevel.HIGH # 새 프로세스는 최고 우선순위
self.time_in_current_queue = 0
self.total_cpu_time = 0
self.io_frequency = io_frequency # I/O 빈도
self.last_io_time = 0
class MultilevelFeedbackQueue:
def __init__(self):
self.queues = {
QueueLevel.HIGH: deque(), # RR, q=8ms
QueueLevel.MEDIUM: deque(), # RR, q=16ms
QueueLevel.LOW: deque() # FCFS
}
self.time_quantum = {
QueueLevel.HIGH: 8,
QueueLevel.MEDIUM: 16,
QueueLevel.LOW: float('inf') # FCFS는 무제한
}
self.current_time = 0
self.boost_interval = 100 # 100ms마다 우선순위 부스트
self.last_boost_time = 0
def add_process(self, process):
"""새 프로세스를 최고 우선순위 큐에 추가"""
process.queue_level = QueueLevel.HIGH
self.queues[QueueLevel.HIGH].append(process)
print(f"시간 {self.current_time}: 프로세스 P{process.pid} HIGH 큐에 추가")
def move_process_down(self, process):
"""프로세스를 낮은 우선순위 큐로 이동"""
if process.queue_level == QueueLevel.HIGH:
process.queue_level = QueueLevel.MEDIUM
self.queues[QueueLevel.MEDIUM].append(process)
print(f" P{process.pid} → MEDIUM 큐로 이동")
elif process.queue_level == QueueLevel.MEDIUM:
process.queue_level = QueueLevel.LOW
self.queues[QueueLevel.LOW].append(process)
print(f" P{process.pid} → LOW 큐로 이동")
def priority_boost(self):
"""모든 프로세스를 최고 우선순위 큐로 이동 (기아 현상 방지)"""
print(f"시간 {self.current_time}: 우선순위 부스트 실행")
# MEDIUM과 LOW 큐의 모든 프로세스를 HIGH 큐로 이동
for level in [QueueLevel.MEDIUM, QueueLevel.LOW]:
while self.queues[level]:
process = self.queues[level].popleft()
process.queue_level = QueueLevel.HIGH
self.queues[QueueLevel.HIGH].append(process)
print(f" P{process.pid} → HIGH 큐로 부스트")
def get_next_process(self):
"""실행할 다음 프로세스 선택 (높은 우선순위부터)"""
for level in [QueueLevel.HIGH, QueueLevel.MEDIUM, QueueLevel.LOW]:
if self.queues[level]:
return self.queues[level].popleft()
return None
def schedule(self, processes):
"""다단계 피드백 큐 스케줄링 실행"""
# 모든 프로세스를 스케줄러에 추가
for process in processes:
self.add_process(process)
print("\n다단계 피드백 큐 스케줄링 시작")
print("=" * 60)
while any(self.queues.values()):
# 주기적 우선순위 부스트 확인
if (self.current_time - self.last_boost_time) >= self.boost_interval:
self.priority_boost()
self.last_boost_time = self.current_time
# 다음 프로세스 선택
current_process = self.get_next_process()
if not current_process:
break
# 현재 큐의 시간 할당량 가져오기
quantum = self.time_quantum[current_process.queue_level]
execution_time = min(current_process.remaining_time, quantum)
# 프로세스 실행
queue_name = current_process.queue_level.name
print(f"시간 {self.current_time}: P{current_process.pid} 실행 "
f"({queue_name} 큐, {execution_time}ms)")
self.current_time += execution_time
current_process.remaining_time -= execution_time
current_process.total_cpu_time += execution_time
# 프로세스 완료 확인
if current_process.remaining_time == 0:
print(f" P{current_process.pid} 완료 (총 실행시간: {current_process.total_cpu_time}ms)")
continue
# I/O 발생 시뮬레이션 (간헐적)
if (current_process.io_frequency > 0 and
current_process.total_cpu_time % current_process.io_frequency == 0):
print(f" P{current_process.pid} I/O 발생 → HIGH 큐로 이동")
current_process.queue_level = QueueLevel.HIGH
self.queues[QueueLevel.HIGH].append(current_process)
elif execution_time == quantum:
# 시간 할당량을 모두 사용한 경우 → 큐 레벨 하향
self.move_process_down(current_process)
else:
# 시간 할당량을 다 쓰지 않은 경우 → 같은 큐에 재삽입
self.queues[current_process.queue_level].append(current_process)
# 사용 예시
if __name__ == "__main__":
# 다양한 특성의 프로세스 생성
processes = [
Process(1, 50, io_frequency=20), # I/O 집약적
Process(2, 30, io_frequency=0), # CPU 집약적
Process(3, 40, io_frequency=15), # 혼합형
Process(4, 25, io_frequency=0), # CPU 집약적
]
scheduler = MultilevelFeedbackQueue()
scheduler.schedule(processes)
🎯 다단계 피드백 큐 최적화¶
graph TD
A[최적화 기법] --> B[적응적 시간 할당량]
A --> C[지능형 우선순위 부스트]
A --> D[프로세스 특성 학습]
B --> E[프로세스 행동 패턴에 따라<br/>할당량 동적 조정]
C --> F[기아 현상 감지 시에만<br/>선택적 부스트]
D --> G[I/O vs CPU 집약도<br/>자동 분류]
style B fill:#e3f2fd
style C fill:#e8f5e8
style D fill:#fff3e0
📈 성능 비교: 다단계 vs 단일 큐¶
graph TD
A[스케줄링 알고리즘 성능 비교]
A --> B[FCFS: 응답 6 / 처리율 7 / 공정성 5 / 적응성 3]
A --> C[Round Robin: 응답 8 / 처리율 6 / 공정성 9 / 적응성 4]
A --> D[Priority: 응답 7 / 처리율 8 / 공정성 6 / 적응성 5]
A --> E[MLFQ: 응답 9 / 처리율 8 / 공정성 8 / 적응성 9]
style A fill:#1976d2,color:#fff
style E fill:#c8e6c9
🔍 실제 시스템 적용 사례¶
| 운영체제 | 구현 방식 | 특징 |
|---|---|---|
| Linux CFS | 가상 런타임 기반 | Red-Black Tree 사용 |
| Windows | 32개 우선순위 레벨 | 동적 우선순위 조정 |
| macOS | Mach 기반 스케줄러 | 실시간 + 시분할 혼합 |
| Solaris | 다단계 피드백 | 공정 공유 스케줄러 |
5.8 다중처리기 스케줄링 (Multiprocessor Scheduling)¶
다중 CPU 환경에서의 복잡한 스케줄링 문제를 다룹니다.
5.8.1 다중처리기 스케줄링 접근법 (Approaches to Multiprocessor Scheduling)¶
여러 CPU가 있는 환경에서 사용하는 다양한 스케줄링 방법:
🏗️ 다중처리기 스케줄링 아키텍처¶
mindmap
root((다중처리기 스케줄링))
비대칭 다중처리
마스터-슬레이브 구조
단일 스케줄링 프로세서
구현 단순
병목 현상 가능
대칭 다중처리 SMP
각 프로세서 자체 스케줄링
확장성 우수
동기화 복잡
널리 사용됨
프로세서 집합
특정 프로세서 그룹 바인딩
NUMA 최적화
자원 격리
실시간 시스템 적합
💻 대칭 다중처리 (SMP) 구현¶
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// SMP 스케줄러 구조체
typedef struct {
int cpu_id;
pthread_mutex_t run_queue_lock;
struct process_list *run_queue;
int load; // 현재 부하
unsigned long total_runtime; // 총 실행 시간
int num_processes; // 프로세스 수
} cpu_scheduler_t;
// 전역 SMP 스케줄러 배열
cpu_scheduler_t *smp_schedulers;
int num_cpus;
// CPU별 스케줄러 초기화
void init_smp_scheduling(int cpu_count) {
num_cpus = cpu_count;
smp_schedulers = malloc(sizeof(cpu_scheduler_t) * num_cpus);
for (int i = 0; i < num_cpus; i++) {
smp_schedulers[i].cpu_id = i;
pthread_mutex_init(&smp_schedulers[i].run_queue_lock, NULL);
smp_schedulers[i].run_queue = NULL;
smp_schedulers[i].load = 0;
smp_schedulers[i].total_runtime = 0;
smp_schedulers[i].num_processes = 0;
printf("CPU %d 스케줄러 초기화 완료\n", i);
}
}
// 가장 부하가 적은 CPU 찾기
int find_least_loaded_cpu() {
int min_load = INT_MAX;
int selected_cpu = 0;
for (int i = 0; i < num_cpus; i++) {
if (smp_schedulers[i].load < min_load) {
min_load = smp_schedulers[i].load;
selected_cpu = i;
}
}
return selected_cpu;
}
// 프로세스를 특정 CPU에 할당
void assign_process_to_cpu(struct process *proc, int cpu_id) {
pthread_mutex_lock(&smp_schedulers[cpu_id].run_queue_lock);
// 런큐에 프로세스 추가
add_to_run_queue(&smp_schedulers[cpu_id].run_queue, proc);
smp_schedulers[cpu_id].num_processes++;
smp_schedulers[cpu_id].load += proc->priority;
printf("프로세스 P%d를 CPU %d에 할당 (현재 부하: %d)\n",
proc->pid, cpu_id, smp_schedulers[cpu_id].load);
pthread_mutex_unlock(&smp_schedulers[cpu_id].run_queue_lock);
}
📊 다중처리기 스케줄링 방식 비교¶
graph TD
A[다중처리기 스케줄링] --> B[비대칭 다중처리]
A --> C[대칭 다중처리 SMP]
A --> D[프로세서 집합]
B --> E[마스터 프로세서가<br/>모든 스케줄링 담당]
B --> F[구현 단순<br/>병목 현상]
C --> G[각 프로세서가<br/>독립적으로 스케줄링]
C --> H[확장성 좋음<br/>동기화 복잡]
D --> I[프로세스를 특정<br/>프로세서 그룹에 바인딩]
D --> J[NUMA 최적화<br/>실시간 보장]
style C fill:#4caf50,color:#fff
style H fill:#4caf50,color:#fff
⚠️ 다중처리기 스케줄링 도전과제¶
| 도전과제 | 설명 | 해결 방안 |
|---|---|---|
| 부하 균형 | CPU 간 작업 부하 불균형 | 동적 부하 분산 알고리즘 |
| 프로세서 친화성 | 캐시 지역성 유지 필요 | Soft/Hard Affinity 정책 |
| 캐시 일관성 | 다중 캐시 간 데이터 동기화 | 캐시 일관성 프로토콜 |
| 동기화 오버헤드 | 공유 자원 접근 동기화 | Lock-free 알고리즘 사용 |
5.8.2 프로세서 친화성 (Processor Affinity)¶
프로세스가 캐시 지역성으로 인해 동일한 프로세서에서 실행되기를 선호하는 특성:
🧠 캐시 지역성과 프로세서 친화성¶
graph TB
A[프로세스 P1] --> B[CPU 0]
B --> C[L1 캐시]
C --> D[캐시 적중률 높음]
A --> E[CPU 1로 이동]
E --> F[L1 캐시 미스]
F --> G[성능 저하]
H[해결책: 프로세서 친화성] --> I[Soft Affinity]
H --> J[Hard Affinity]
I --> K[OS가 가능한 한<br/>같은 CPU 사용]
J --> L[프로세스를 특정<br/>CPU에 명시적 바인딩]
style D fill:#c8e6c9
style G fill:#ffcdd2
style K fill:#e1f5fe
style L fill:#f3e5f5
💻 프로세서 친화성 설정 예제¶
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
// CPU 친화성 설정 함수
int set_cpu_affinity(pid_t pid, int cpu_num) {
cpu_set_t mask;
// CPU 마스크 초기화
CPU_ZERO(&mask);
CPU_SET(cpu_num, &mask); // 특정 CPU 설정
// 프로세스에 CPU 친화성 적용
if (sched_setaffinity(pid, sizeof(mask), &mask) == -1) {
perror("sched_setaffinity 실패");
return -1;
}
printf("프로세스 %d를 CPU %d에 바인딩했습니다.\n", pid, cpu_num);
return 0;
}
// 현재 CPU 친화성 확인
void check_cpu_affinity(pid_t pid) {
cpu_set_t mask;
if (sched_getaffinity(pid, sizeof(mask), &mask) == -1) {
perror("sched_getaffinity 실패");
return;
}
printf("프로세스 %d의 CPU 친화성:\n", pid);
for (int i = 0; i < CPU_SETSIZE; i++) {
if (CPU_ISSET(i, &mask)) {
printf(" CPU %d: 사용 가능\n", i);
}
}
}
// 다중 CPU에 친화성 설정
int set_multi_cpu_affinity(pid_t pid, int *cpu_list, int cpu_count) {
cpu_set_t mask;
CPU_ZERO(&mask);
for (int i = 0; i < cpu_count; i++) {
CPU_SET(cpu_list[i], &mask);
}
if (sched_setaffinity(pid, sizeof(mask), &mask) == -1) {
perror("다중 CPU 친화성 설정 실패");
return -1;
}
printf("프로세스 %d를 %d개 CPU에 바인딩했습니다.\n", pid, cpu_count);
return 0;
}
// 사용 예시
int main() {
pid_t current_pid = getpid();
printf("=== 프로세서 친화성 테스트 ===\n");
// 현재 친화성 확인
check_cpu_affinity(current_pid);
// CPU 0에 바인딩
set_cpu_affinity(current_pid, 0);
// 변경된 친화성 확인
check_cpu_affinity(current_pid);
// 다중 CPU 바인딩 (CPU 0, 1, 2)
int cpu_list[] = {0, 1, 2};
set_multi_cpu_affinity(current_pid, cpu_list, 3);
// 최종 친화성 확인
check_cpu_affinity(current_pid);
return 0;
}
📊 친화성 유형별 성능 비교¶
graph TD
A[프로세서 친화성별 성능 비교]
A --> B[친화성 없음: 캐시 30 / 스위치 60 / 지연 40 / 전체 45]
A --> C[Soft Affinity: 캐시 70 / 스위치 80 / 지연 70 / 전체 75]
A --> D[Hard Affinity: 캐시 90 / 스위치 70 / 지연 85 / 전체 85]
style A fill:#1565c0,color:#fff
style D fill:#c8e6c9
5.8.3 부하 균형 (Load Balancing)¶
프로세서 간 작업을 고르게 분산시키는 기법:
⚖️ 부하 균형 전략¶
flowchart TD
A[부하 균형 전략] --> B[Push 마이그레이션]
A --> C[Pull 마이그레이션]
A --> D[하이브리드 접근법]
B --> E[과부하 프로세서가<br/>작업을 다른 CPU로 이동]
B --> F[주기적 부하 모니터링<br/>임계값 초과 시 실행]
C --> G[유휴 프로세서가<br/>작업을 가져옴]
C --> H[CPU 사용률 낮을 때<br/>다른 CPU에서 작업 탈취]
D --> I[Push + Pull 결합]
D --> J[상황에 따라<br/>최적 전략 선택]
style D fill:#4caf50,color:#fff
style J fill:#4caf50,color:#fff
🔄 동적 부하 균형 알고리즘¶
graph LR
A[CPU 0<br/>부하: 90%<br/>🔴 과부하] --> |작업 마이그레이션| B[CPU 1<br/>부하: 30%<br/>🟢 여유]
C[CPU 2<br/>부하: 75%<br/>🟡 보통] --> |부하 모니터링| D[부하 균형기]
D --> A
D --> B
D --> C
D --> E[CPU 3<br/>부하: 10%<br/>🟢 유휴]
E --> |작업 탈취| A
style A fill:#ffcdd2
style B fill:#c8e6c9
style E fill:#c8e6c9
💻 부하 균형 구현 예제¶
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#define MAX_CPUS 8
#define LOAD_THRESHOLD_HIGH 80 // 높은 부하 임계값
#define LOAD_THRESHOLD_LOW 20 // 낮은 부하 임계값
typedef struct {
int cpu_id;
int current_load; // 현재 부하 (0-100)
int num_processes; // 프로세스 수
pthread_mutex_t lock;
time_t last_balance_time;
} cpu_info_t;
typedef struct {
cpu_info_t cpus[MAX_CPUS];
int num_cpus;
pthread_t balance_thread;
int balancing_active;
} load_balancer_t;
load_balancer_t global_balancer;
// CPU 부하 계산
int calculate_cpu_load(int cpu_id) {
// 실제로는 시스템 호출을 통해 계산
// 여기서는 시뮬레이션
return global_balancer.cpus[cpu_id].num_processes * 10 + (rand() % 20);
}
// 부하가 가장 높은 CPU 찾기
int find_most_loaded_cpu() {
int max_load = -1;
int selected_cpu = -1;
for (int i = 0; i < global_balancer.num_cpus; i++) {
if (global_balancer.cpus[i].current_load > max_load) {
max_load = global_balancer.cpus[i].current_load;
selected_cpu = i;
}
}
return selected_cpu;
}
// 부하가 가장 적은 CPU 찾기
int find_least_loaded_cpu() {
int min_load = 101;
int selected_cpu = -1;
for (int i = 0; i < global_balancer.num_cpus; i++) {
if (global_balancer.cpus[i].current_load < min_load) {
min_load = global_balancer.cpus[i].current_load;
selected_cpu = i;
}
}
return selected_cpu;
}
// 프로세스 마이그레이션 시뮬레이션
int migrate_process(int from_cpu, int to_cpu) {
pthread_mutex_lock(&global_balancer.cpus[from_cpu].lock);
pthread_mutex_lock(&global_balancer.cpus[to_cpu].lock);
if (global_balancer.cpus[from_cpu].num_processes > 0) {
global_balancer.cpus[from_cpu].num_processes--;
global_balancer.cpus[to_cpu].num_processes++;
printf("프로세스 마이그레이션: CPU %d → CPU %d\n", from_cpu, to_cpu);
printf(" CPU %d 부하: %d%% → ", from_cpu, global_balancer.cpus[from_cpu].current_load);
global_balancer.cpus[from_cpu].current_load = calculate_cpu_load(from_cpu);
printf("%d%%\n", global_balancer.cpus[from_cpu].current_load);
printf(" CPU %d 부하: %d%% → ", to_cpu, global_balancer.cpus[to_cpu].current_load);
global_balancer.cpus[to_cpu].current_load = calculate_cpu_load(to_cpu);
printf("%d%%\n", global_balancer.cpus[to_cpu].current_load);
pthread_mutex_unlock(&global_balancer.cpus[to_cpu].lock);
pthread_mutex_unlock(&global_balancer.cpus[from_cpu].lock);
return 1; // 성공
}
pthread_mutex_unlock(&global_balancer.cpus[to_cpu].lock);
pthread_mutex_unlock(&global_balancer.cpus[from_cpu].lock);
return 0; // 실패
}
// 부하 균형 수행
void perform_load_balancing() {
int most_loaded = find_most_loaded_cpu();
int least_loaded = find_least_loaded_cpu();
if (most_loaded == -1 || least_loaded == -1 || most_loaded == least_loaded) {
return;
}
int load_diff = global_balancer.cpus[most_loaded].current_load -
global_balancer.cpus[least_loaded].current_load;
// 부하 차이가 40% 이상일 때만 마이그레이션
if (load_diff > 40) {
printf("\n=== 부하 균형 실행 ===\n");
printf("최고 부하 CPU %d (%d%%), 최저 부하 CPU %d (%d%%)\n",
most_loaded, global_balancer.cpus[most_loaded].current_load,
least_loaded, global_balancer.cpus[least_loaded].current_load);
migrate_process(most_loaded, least_loaded);
}
}
// 부하 균형 스레드
void* load_balancer_thread(void* arg) {
while (global_balancer.balancing_active) {
// 모든 CPU 부하 업데이트
for (int i = 0; i < global_balancer.num_cpus; i++) {
global_balancer.cpus[i].current_load = calculate_cpu_load(i);
}
// 부하 상태 출력
printf("\n현재 CPU 부하 상태:\n");
for (int i = 0; i < global_balancer.num_cpus; i++) {
printf("CPU %d: %d%% (프로세스 %d개)\n",
i, global_balancer.cpus[i].current_load,
global_balancer.cpus[i].num_processes);
}
// 부하 균형 수행
perform_load_balancing();
sleep(2); // 2초마다 실행
}
return NULL;
}
5.9 실시간 CPU 스케줄링 (Real-Time CPU Scheduling)¶
실시간 시스템에서 요구되는 특수한 스케줄링 기법들을 다룹니다.
5.9.1 실시간 시스템 특성 (Real-Time System Characteristics)¶
🚀 실시간 시스템 분류¶
mindmap
root((실시간 시스템))
경성 실시간
Hard Real-Time
절대적 데드라인
실패 시 치명적 결과
예: 의료기기, 항공시스템
연성 실시간
Soft Real-Time
상대적 데드라인
지연 허용 가능
예: 멀티미디어, 게임
임계적 실시간
Critical Real-Time
극도로 짧은 응답시간
마이크로초 단위
예: 원자력 제어
💻 실시간 태스크 모델¶
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdbool.h>
// 실시간 태스크 구조체
typedef struct {
int task_id; // 태스크 ID
int period; // 주기 (ms)
int deadline; // 상대적 데드라인 (ms)
int computation_time; // 최악 실행 시간 (ms)
int priority; // 우선순위
int next_arrival; // 다음 도착 시간
int absolute_deadline; // 절대 데드라인
bool is_hard_deadline; // 경성/연성 데드라인 여부
int missed_deadlines; // 놓친 데드라인 수
double utilization; // 사용률 (C/P)
} RealTimeTask;
// 태스크 사용률 계산
double calculate_utilization(RealTimeTask* task) {
return (double)task->computation_time / task->period;
}
// 다음 인스턴스 도착 시간 설정
void set_next_arrival(RealTimeTask* task, int current_time) {
task->next_arrival = current_time + task->period;
task->absolute_deadline = task->next_arrival + task->deadline;
}
// 데드라인 체크
bool check_deadline_miss(RealTimeTask* task, int completion_time) {
if (completion_time > task->absolute_deadline) {
task->missed_deadlines++;
printf("⚠️ 태스크 T%d 데드라인 위반! (완료: %d, 데드라인: %d)\n",
task->task_id, completion_time, task->absolute_deadline);
return true;
}
return false;
}
// 실시간 태스크 생성 예시
RealTimeTask* create_rt_task(int id, int period, int deadline, int computation) {
RealTimeTask* task = malloc(sizeof(RealTimeTask));
task->task_id = id;
task->period = period;
task->deadline = deadline;
task->computation_time = computation;
task->next_arrival = 0;
task->absolute_deadline = deadline;
task->is_hard_deadline = true;
task->missed_deadlines = 0;
task->utilization = calculate_utilization(task);
printf("실시간 태스크 T%d 생성: P=%d, D=%d, C=%d, U=%.3f\n",
id, period, deadline, computation, task->utilization);
return task;
}
📊 실시간 시스템 요구사항¶
| 시스템 유형 | 응답 시간 | 데드라인 위반 | 예측 가능성 | 사용 사례 |
|---|---|---|---|---|
| 경성 실시간 | 태스크별 요구사항 | 위반을 허용하지 않는 설계 목표 | WCET·자원·스케줄가능성 증거 필요 | 안전 제어 계열 |
| 연성 실시간 | 서비스별 목표 | 제한된 위반 허용 | miss 비율·지연 분포로 검증 | 동영상 스트리밍, VoIP |
| 펌 실시간 | 서비스별 목표 | 늦은 결과의 가치가 없음 | 폐기·복구 정책으로 검증 | 거래·센서 처리 등 |
5.9.2 비율 단조 스케줄링 (Rate Monotonic Scheduling, RM)¶
주기가 짧을수록 높은 우선순위를 부여하는 고정 우선순위 알고리즘:
📐 RM 스케줄링 이론¶
graph TD
A[RM 스케줄링 원리] --> B[주기 ↓ = 우선순위 ↑]
A --> C[고정 우선순위 할당]
A --> D[선점형 스케줄링]
B --> E[T1: P=50ms → 우선순위 1]
B --> F[T2: P=100ms → 우선순위 2]
B --> G[T3: P=200ms → 우선순위 3]
style A fill:#2196f3,color:#fff
style E fill:#4caf50,color:#fff
style F fill:#ff9800,color:#fff
style G fill:#f44336,color:#fff
💻 RM 스케줄링 구현¶
import math
from dataclasses import dataclass
from typing import List
import matplotlib.pyplot as plt
@dataclass
class Task:
id: int
period: int # 주기
computation: int # 실행 시간
deadline: int # 데드라인 (일반적으로 period와 동일)
priority: int = 0 # 우선순위 (낮은 값 = 높은 우선순위)
class RateMonotonicScheduler:
def __init__(self, tasks: List[Task]):
self.tasks = sorted(tasks, key=lambda t: t.period) # 주기순 정렬
# 우선순위 할당 (주기가 짧을수록 높은 우선순위)
for i, task in enumerate(self.tasks):
task.priority = i
print("=== Rate Monotonic 우선순위 할당 ===")
for task in self.tasks:
utilization = task.computation / task.period
print(f"T{task.id}: P={task.period}, C={task.computation}, "
f"우선순위={task.priority}, U={utilization:.3f}")
def schedulability_test(self) -> bool:
"""RM 스케줄가능성 테스트"""
n = len(self.tasks)
total_utilization = sum(t.computation / t.period for t in self.tasks)
# Liu & Layland 조건
bound = n * (2**(1/n) - 1)
print(f"\n=== 스케줄가능성 분석 ===")
print(f"총 사용률: {total_utilization:.3f}")
print(f"RM 상한: {bound:.3f}")
if total_utilization <= bound:
print("✅ RM 스케줄가능 (Liu & Layland 조건 만족)")
return True
elif total_utilization <= 1.0:
print("⚠️ Liu & Layland 조건 불만족하지만 사용률 ≤ 1")
print(" 정확한 분석을 위해 응답시간 분석 필요")
return self.response_time_analysis()
else:
print("❌ 사용률 > 1, 스케줄 불가능")
return False
def response_time_analysis(self) -> bool:
"""응답시간 분석을 통한 정확한 스케줄가능성 테스트"""
print("\n=== 응답시간 분석 ===")
for task in self.tasks:
response_time = self.calculate_response_time(task)
if response_time > task.deadline:
print(f"❌ T{task.id} 응답시간 {response_time} > 데드라인 {task.deadline}")
return False
else:
print(f"✅ T{task.id} 응답시간 {response_time} ≤ 데드라인 {task.deadline}")
return True
def calculate_response_time(self, target_task: Task) -> int:
"""특정 태스크의 최악 응답시간 계산"""
response_time = target_task.computation
while True:
interference = 0
# 높은 우선순위 태스크들의 간섭 계산
for task in self.tasks:
if task.priority < target_task.priority: # 더 높은 우선순위
interference += math.ceil(response_time / task.period) * task.computation
new_response_time = target_task.computation + interference
if new_response_time == response_time:
break
response_time = new_response_time
# 무한루프 방지
if response_time > target_task.deadline * 2:
return float('inf')
return response_time
def simulate_execution(self, simulation_time: int):
"""RM 스케줄링 시뮬레이션"""
print(f"\n=== {simulation_time}ms 시뮬레이션 ===")
# 태스크 인스턴스 생성
task_instances = []
for task in self.tasks:
for arrival_time in range(0, simulation_time, task.period):
instance = {
'task_id': task.id,
'priority': task.priority,
'arrival_time': arrival_time,
'deadline': arrival_time + task.deadline,
'remaining_time': task.computation,
'start_time': None,
'completion_time': None
}
task_instances.append(instance)
# 시간별 스케줄링
current_time = 0
ready_queue = []
execution_trace = []
while current_time < simulation_time:
# 도착한 태스크들을 준비 큐에 추가
new_arrivals = [inst for inst in task_instances
if inst['arrival_time'] == current_time]
ready_queue.extend(new_arrivals)
if ready_queue:
# 우선순위가 가장 높은 태스크 선택
ready_queue.sort(key=lambda x: x['priority'])
current_task = ready_queue[0]
if current_task['start_time'] is None:
current_task['start_time'] = current_time
# 1ms 실행
current_task['remaining_time'] -= 1
execution_trace.append((current_time, current_task['task_id']))
if current_task['remaining_time'] == 0:
current_task['completion_time'] = current_time + 1
ready_queue.remove(current_task)
# 데드라인 체크
if current_task['completion_time'] > current_task['deadline']:
print(f"⚠️ T{current_task['task_id']} 데드라인 위반: "
f"완료={current_task['completion_time']}, "
f"데드라인={current_task['deadline']}")
current_time += 1
return execution_trace
# 사용 예시
if __name__ == "__main__":
# 실시간 태스크 세트 정의
tasks = [
Task(id=1, period=50, computation=12, deadline=50),
Task(id=2, period=100, computation=25, deadline=100),
Task(id=3, period=200, computation=40, deadline=200),
]
scheduler = RateMonotonicScheduler(tasks)
# 스케줄가능성 테스트
if scheduler.schedulability_test():
# 시뮬레이션 실행
trace = scheduler.simulate_execution(200)
# 간단한 스케줄 출력
print("\n=== 실행 추적 (처음 20ms) ===")
for i, (time, task_id) in enumerate(trace[:20]):
print(f"시간 {time}: T{task_id}")
📈 RM 스케줄가능성 분석¶
graph LR
A[태스크 수 n] --> B[n=1: 1.000]
A --> C[n=2: 0.828]
A --> D[n=3: 0.779]
A --> E[n=4: 0.756]
A --> F[n=5: 0.743]
A --> G[n=6: 0.735]
A --> H[n=7: 0.729]
A --> I[n=8: 0.724]
A --> J[n→∞: 0.693]
style A fill:#1565c0,color:#fff
style J fill:#ffe0b2
수식:
- n개 태스크의 RM 상한: \(U_{RM}(n) = n(2^{1/n} - 1)\)
- 무한개 태스크일 때: \(U_{RM}(\infty) = \ln(2) \approx 0.693\)
5.9.3 최단 데드라인 우선 (Earliest Deadline First, EDF)¶
절대 데드라인이 가장 가까운 태스크를 우선 실행하는 동적 우선순위 알고리즘:
아래 사용률 판정과 최적성 설명은 독립적·선점 가능한 주기 태스크, 단일 프로세서, deadline=period, 무시 가능한 스케줄링 비용 같은 고전적 전제를 사용합니다. 임의 deadline, 공유 자원, blocking, multicore에서는 response-time/demand-bound 분석이 추가로 필요합니다.
🎯 EDF 스케줄링 특성¶
mindmap
root((EDF 스케줄링))
특성
동적 우선순위
데드라인 기반 선택
선점형 스케줄링
고전적 단일 CPU 모델에서 최적
장점
implicit-deadline 모델에서 U≤1 검사
제한된 단일 프로세서 모델
구현 상대적 단순
단점
우선순위 계산 오버헤드
도미노 효과 가능
예측성 부족
💻 EDF 스케줄링 구현¶
import heapq
from dataclasses import dataclass, field
from typing import List, Tuple
@dataclass
class EDFTask:
task_id: int
period: int
computation: int
deadline: int
arrival_time: int = 0
absolute_deadline: int = field(init=False)
remaining_time: int = field(init=False)
def __post_init__(self):
self.absolute_deadline = self.arrival_time + self.deadline
self.remaining_time = self.computation
def __lt__(self, other):
# 힙에서 사용하기 위한 비교 함수 (데드라인이 빠른 것이 우선)
return self.absolute_deadline < other.absolute_deadline
class EDFScheduler:
def __init__(self):
self.ready_queue = [] # 최소 힙
self.current_time = 0
self.execution_trace = []
def add_task(self, task: EDFTask):
"""준비 큐에 태스크 추가"""
heapq.heappush(self.ready_queue, task)
print(f"시간 {self.current_time}: T{task.task_id} 도착 "
f"(절대 데드라인: {task.absolute_deadline})")
def schedule_next(self) -> EDFTask:
"""다음 실행할 태스크 선택 (가장 이른 데드라인)"""
if self.ready_queue:
return heapq.heappop(self.ready_queue)
return None
def execute_task(self, task: EDFTask, execution_time: int):
"""태스크 실행"""
actual_execution = min(execution_time, task.remaining_time)
for i in range(actual_execution):
self.execution_trace.append((self.current_time + i, task.task_id))
task.remaining_time -= actual_execution
self.current_time += actual_execution
print(f"시간 {self.current_time-actual_execution}-{self.current_time}: "
f"T{task.task_id} 실행 (잔여: {task.remaining_time})")
# 태스크가 완료되지 않았으면 다시 큐에 삽입
if task.remaining_time > 0:
heapq.heappush(self.ready_queue, task)
def check_deadline_violations(self):
"""데드라인 위반 체크"""
violations = 0
for task in self.ready_queue:
if self.current_time > task.absolute_deadline:
violations += 1
print(f"❌ T{task.task_id} 데드라인 위반! "
f"현재시간: {self.current_time}, "
f"데드라인: {task.absolute_deadline}")
return violations
def simulate(self, task_set: List[Tuple], simulation_time: int):
"""EDF 스케줄링 시뮬레이션"""
print("=== EDF 스케줄링 시뮬레이션 ===")
# 모든 태스크 인스턴스 생성
all_instances = []
for task_id, period, computation, deadline in task_set:
for arrival in range(0, simulation_time, period):
instance = EDFTask(
task_id=task_id,
period=period,
computation=computation,
deadline=deadline,
arrival_time=arrival
)
all_instances.append((arrival, instance))
# 도착 시간순 정렬
all_instances.sort(key=lambda x: x[0])
instance_index = 0
while self.current_time < simulation_time:
# 현재 시간에 도착하는 태스크들 추가
while (instance_index < len(all_instances) and
all_instances[instance_index][0] <= self.current_time):
_, task_instance = all_instances[instance_index]
self.add_task(task_instance)
instance_index += 1
# 다음 태스크 스케줄링
current_task = self.schedule_next()
if current_task:
# 다음 도착 태스크까지의 시간 또는 현재 태스크 완료까지의 시간
next_arrival_time = simulation_time
if instance_index < len(all_instances):
next_arrival_time = all_instances[instance_index][0]
available_time = next_arrival_time - self.current_time
execution_time = min(available_time, current_task.remaining_time)
self.execute_task(current_task, execution_time)
# 완료된 태스크 확인
if current_task.remaining_time == 0:
completion_time = self.current_time
if completion_time <= current_task.absolute_deadline:
print(f"✅ T{current_task.task_id} 완료 "
f"(완료: {completion_time}, 데드라인: {current_task.absolute_deadline})")
else:
print(f"❌ T{current_task.task_id} 데드라인 위반 "
f"(완료: {completion_time}, 데드라인: {current_task.absolute_deadline})")
else:
# 실행할 태스크가 없으면 다음 도착까지 대기
if instance_index < len(all_instances):
self.current_time = all_instances[instance_index][0]
else:
break
def edf_utilization_test(task_set: List[Tuple]) -> bool:
"""EDF 사용률 테스트"""
total_utilization = sum(computation/period for _, period, computation, _ in task_set)
print(f"총 사용률: {total_utilization:.3f}")
if total_utilization <= 1.0:
print("✅ EDF 스케줄가능 (사용률 ≤ 1)")
return True
else:
print("❌ EDF 스케줄 불가능 (사용률 > 1)")
return False
# 사용 예시
if __name__ == "__main__":
# 태스크 세트: (task_id, period, computation, deadline)
task_set = [
(1, 20, 3, 20), # T1: P=20, C=3, D=20
(2, 50, 15, 50), # T2: P=50, C=15, D=50
(3, 100, 20, 100) # T3: P=100, C=20, D=100
]
# 사용률 테스트
if edf_utilization_test(task_set):
# EDF 스케줄링 시뮬레이션
scheduler = EDFScheduler()
scheduler.simulate(task_set, 100)
# 실행 추적 출력
print("\n=== 실행 추적 (처음 30ms) ===")
for i, (time, task_id) in enumerate(scheduler.execution_trace[:30]):
print(f"시간 {time}: T{task_id}")
📊 EDF vs RM 성능 비교¶
gantt
title EDF vs RM 스케줄링 비교 (동일 태스크 세트)
dateFormat X
axisFormat %s
section RM 스케줄링
T1 (P=20) :0, 3
T2 (P=50) :3, 8
T1 (P=20) :20, 23
section EDF 스케줄링
T1 (D=20) :0, 3
T2 (D=25) :3, 8
T3 (D=30) :8, 13
🔍 EDF 최적성 증명¶
graph TD
A[EDF 최적성] --> B[교환 논리 Exchange Argument]
B --> C[다른 알고리즘의 스케줄]
C --> D[데드라인 순서가 틀린 두 태스크]
D --> E[순서 교환]
E --> F[데드라인 위반 감소 또는 동일]
F --> G[EDF가 최적]
style A fill:#4caf50,color:#fff
style G fill:#4caf50,color:#fff
정리: 위의 implicit-deadline 단일 프로세서 모델에서는 총 사용률이 1 이하일 때 EDF로 스케줄할 수 있습니다. EDF가 유일한 알고리즘인 것은 아니며 전제가 달라지면 사용률 검사만으로 충분하지 않습니다.
💡 EDF vs RM 비교¶
| 측면 | Rate Monotonic (RM) | Earliest Deadline First (EDF) |
|---|---|---|
| 우선순위 | 고정 (주기 기반) | 동적 (데드라인 기반) |
| 사용률 판정 | 약 69.3%는 고전적 충분조건의 점근값이며 필요조건이 아님 | implicit-deadline 단일 CPU 모델에서 U≤1 |
| 구현 복잡도 | 낮음 | 중간 |
| 런타임 오버헤드 | 낮음 | 높음 |
| 예측 가능성 | 높음 | 낮음 |
| 과부하 상황 | 우아한 성능 저하 | 도미노 효과 |
| 실제 사용 | 많음 | 제한적 |
5.10 Operating Systems Examples¶
5.10.1 Linux Scheduling¶
Linux fair-class scheduler의 역사적 CFS 모델:
- CFS는 virtual runtime과 red-black tree를 사용한 대표 모델입니다.
- 최신 Linux는 공정 클래스의 선택 로직에 EEVDF를 도입한 릴리스가 있으므로 대상 커널 버전의
kernel/sched/fair.c와 문서를 확인합니다. - 시간 slice와 선점 동작은 tunable, runnable task 수와 커널 버전에 따라 달라집니다.
// Simplified Linux CFS concept
struct task_struct {
u64 vruntime; // Virtual runtime
int nice; // Nice value (-20 to +19)
struct sched_entity se;
};
// Virtual runtime calculation
vruntime += delta_exec * (NICE_0_LOAD / weight);
5.10.2 Windows Scheduling¶
Windows 우선순위 모델의 대표 설명:
- 전통적으로 0~31 범위를 설명에 사용하지만 boost, quantum, processor group과 버전별 정책은 해당 Windows Internals/ETW 자료로 확인합니다.
5.10.3 Solaris Scheduling¶
Solaris time-sharing class의 대표 설명:
- priority 범위와 dispatch table은 Solaris 릴리스·class 설정에 의존하므로 역사적 예시로 다룹니다.
5.11 Practice Problems¶
Problem 1: FCFS vs SJF Comparison¶
Given the following processes:
| Process | Arrival Time | Burst Time |
|---|---|---|
| P1 | 0 | 8 |
| P2 | 1 | 4 |
| P3 | 2 | 9 |
| P4 | 3 | 5 |
Calculate average turnaround time and waiting time for: a) FCFS scheduling b) Non-preemptive SJF scheduling
Solution:
a) FCFS Scheduling:
Execution order: P1 → P2 → P3 → P4
Timeline: 0-8-12-21-26
P1: Turnaround = 8-0 = 8, Waiting = 8-8 = 0
P2: Turnaround = 12-1 = 11, Waiting = 11-4 = 7
P3: Turnaround = 21-2 = 19, Waiting = 19-9 = 10
P4: Turnaround = 26-3 = 23, Waiting = 23-5 = 18
Average Turnaround Time = (8+11+19+23)/4 = 15.25
Average Waiting Time = (0+7+10+18)/4 = 8.75
b) Non-preemptive SJF:
At time 0: P1 starts (only available process)
At time 8: Available processes: P2(4), P3(9), P4(5) → select P2
At time 12: Available processes: P3(9), P4(5) → select P4
At time 17: Available processes: P3(9) → select P3
Execution order: P1 → P2 → P4 → P3
Timeline: 0-8-12-17-26
P1: Turnaround = 8-0 = 8, Waiting = 8-8 = 0
P2: Turnaround = 12-1 = 11, Waiting = 11-4 = 7
P4: Turnaround = 17-3 = 14, Waiting = 14-5 = 9
P3: Turnaround = 26-2 = 24, Waiting = 24-9 = 15
Average Turnaround Time = (8+11+14+24)/4 = 14.25
Average Waiting Time = (0+7+9+15)/4 = 7.75
Problem 2: Round Robin Analysis¶
For the same processes with time quantum = 3:
Solution:
gantt
title Round Robin (q=3) Execution
dateFormat X
axisFormat %s
section CPU Schedule
P1 :0, 3
P2 :3, 6
P1 :6, 9
P3 :9, 12
P4 :12, 15
P1 :15, 17
P3 :17, 20
P4 :20, 22
P3 :22, 25
Problem 3: Priority Scheduling with Aging¶
Implement an aging mechanism to prevent starvation in priority scheduling.
Solution:
#define AGING_FACTOR 2
#define MAX_PRIORITY 10
void age_processes(Process processes[], int n) {
for (int i = 0; i < n; i++) {
if (processes[i].state == READY) {
processes[i].wait_time++;
// Increase priority every AGING_FACTOR time units
if (processes[i].wait_time % AGING_FACTOR == 0) {
if (processes[i].priority < MAX_PRIORITY) {
processes[i].priority++;
}
}
}
}
}
5.12 Summary¶
Key Concepts Learned¶
-
CPU Scheduling Fundamentals
-
CPU-I/O burst cycles
- Process state transitions
-
Scheduling decision points
-
Scheduling Algorithms
-
FCFS: Simple but can cause convoy effect
- SJF: Minimizes average waiting time only under the stated single-CPU/burst assumptions
- Priority: Flexible but can cause starvation
-
Round Robin: Fair time sharing with quantum overhead
-
Performance Metrics
-
CPU utilization and throughput (system-oriented)
-
Response time, waiting time, turnaround time (user-oriented)
-
Advanced Topics
- Multiprocessor scheduling challenges
- Real-time scheduling requirements
- Modern OS implementations
Algorithm Comparison¶
| Algorithm | Preemptive | Starvation | Overhead | Best For |
|---|---|---|---|---|
| FCFS | No | No | Low | Batch systems |
| SJF | Optional | Possible | Medium | Known burst times |
| Priority | Optional | Yes | Medium | Mixed workloads |
| Round Robin | Yes | No | High | Interactive systems |
| MLQ | Yes | Possible | High | Classified processes |
Design Trade-offs¶
graph TD
A[Scheduling Goals] --> B[Maximize Throughput]
A --> C[Minimize Response Time]
A --> D[Ensure Fairness]
A --> E[Prevent Starvation]
B -.->|Conflicts| C
C -.->|Conflicts| D
D -.->|Conflicts| B
F[Solution: Adaptive Algorithms] --> G[Multilevel Feedback]
F --> H[Linux CFS]
F --> I[Windows Priority Boost]
Future Directions¶
- Energy-aware scheduling: Balancing performance with power consumption
- NUMA-aware scheduling: Optimizing for Non-Uniform Memory Access
- Container scheduling: Managing containerized applications
- Machine learning: Predictive scheduling based on workload patterns
Related Topics¶
- Chapter 3: Processes and Process Management
- Chapter 4: Threads and Thread Scheduling
- Chapter 6: Process Synchronization
- Chapter 8: Deadlocks
- Chapter 9: Memory Management
Further Reading¶
- Silberschatz, A., Galvin, P. B., & Gagne, G. "Operating System Concepts"
- Tanenbaum, A. S. "Modern Operating Systems"
- Linux kernel documentation: scheduler/
- Windows Internals: Process and Thread Management
This chapter provides a comprehensive overview of CPU scheduling, from basic concepts to advanced multiprocessor and real-time scheduling. Understanding these concepts is crucial for system performance optimization and designing efficient operating systems.