CHƯƠNG 04 · PROCESS · ~110 phút

CPU
Scheduling

Khi nhiều process cùng cần CPU, OS phải chọn ai chạy tiếp theo. Đây là chương về thuật toán scheduling — từ FCFS đơn giản đến CFS của Linux hiện đại. Hiểu chương này, bạn trả lời được: tại sao terminal vẫn responsive khi build dự án lớn?

4.1 Bài toán scheduling

Máy bạn có 4 CPU core nhưng đang chạy hàng trăm process (Chrome, VS Code, system services...). Tại mọi thời điểm, mỗi core chỉ chạy 1 thread. Vậy ai chạy, ai chờ?

Scheduler là phần của kernel quyết định: tại mỗi thời điểm, thread/process nào sẽ được CPU. Nó chạy hàng nghìn lần mỗi giây, mỗi lần đưa ra quyết định trong micro giây.

Phân loại theo loại workload

  • CPU-bound (compute): dành phần lớn thời gian tính toán, ít I/O. Vd: render video, train ML.
  • I/O-bound: dành nhiều thời gian đợi I/O (disk, network, user input). Vd: database, web server, text editor.

Scheduler hiện đại phải cân bằng: ưu tiên I/O-bound (để app responsive) nhưng không bỏ đói CPU-bound.

4.2 Tiêu chí đánh giá scheduler

MetricĐịnh nghĩaMục tiêu
CPU Utilization% thời gian CPU bậnCàng cao càng tốt (tới ~85%)
ThroughputSố process xong mỗi đơn vị thời gianCàng cao càng tốt
Turnaround timeTổng thời gian từ submit đến xong = wait + runCàng thấp càng tốt
Waiting timeThời gian process đợi trong ready queueCàng thấp càng tốt
Response timeThời gian từ submit đến lần đầu được CPUCàng thấp càng tốt (cho UI)
FairnessMọi process được phục vụ công bằngQuan trọng — tránh starvation

Các metric này thường mâu thuẫn nhau: tối ưu throughput có thể hi sinh response time. Chọn scheduler là chọn trade-off.

4.3 Preemptive vs Non-preemptive

Non-preemptive

  • Process giữ CPU đến khi tự nguyện nhường (block I/O hoặc terminate)
  • Đơn giản, không cần đồng bộ phức tạp
  • Vấn đề: 1 process nặng có thể "hog" CPU, làm cả hệ thống lag
  • Vd: cooperative multitasking (Mac OS Classic)

Preemptive

  • OS có thể "cướp" CPU bất cứ lúc nào (timer interrupt)
  • Tốt cho responsiveness, fairness
  • Cần thiết cho mọi OS hiện đại
  • Vd: Linux, macOS, Windows

Cơ chế preemption: hardware timer phát interrupt mỗi vài ms. Kernel handle interrupt → chạy scheduler → có thể đổi process.

4.4 FCFS — First-Come, First-Served

Đơn giản nhất: process đến trước được phục vụ trước. Dùng FIFO queue.

Ví dụ minh hoạ

Process  Burst time
  P1        24
  P2         3
  P3         3

Order: P1 → P2 → P3
Gantt: |---------- P1 ----------|P2|P3|
       0                       24  27 30

Waiting time:
  P1: 0
  P2: 24
  P3: 27
Avg waiting: (0+24+27)/3 = 17

Vấn đề: Convoy Effect

Nếu P1 chạy lâu (24), P2 và P3 phải đợi rất lâu — dù chúng nhanh. Toàn bộ "đoàn xe" mắc kẹt sau 1 xe chậm. Nếu thay đổi thứ tự: P2 → P3 → P1 → avg waiting = (0+3+6)/3 = 3 (giảm 5.7×).

  • Ưu: đơn giản, fair theo thứ tự đến
  • Nhược: convoy effect, response time tệ
  • Preemptive? Không

4.5 SJF — Shortest Job First (và SRTF)

Chọn process có burst time ngắn nhất chạy trước.

Process  Burst time
  P1        24
  P2         3
  P3         3

Order: P2 → P3 → P1
Gantt: |P2|P3|--------- P1 ---------|
       0  3  6                      30

Waiting time:
  P2: 0, P3: 3, P1: 6
Avg waiting: (0+3+6)/3 = 3

SJF tối ưu lý thuyết — cho average waiting time thấp nhất. Nhưng có 2 vấn đề thực tế:

  1. Không biết burst time tương lai! Phải dự đoán dựa trên lịch sử (exponential averaging).
  2. Starvation: nếu liên tục có process ngắn đến, process dài có thể không bao giờ chạy.

SRTF — Shortest Remaining Time First (preemptive SJF)

Preemptive version: process mới đến có remaining time ngắn hơn process hiện tại → preempt ngay.

4.6 Priority Scheduling + Aging

Mỗi process gắn 1 số priority. Scheduler chọn process có priority cao nhất.

Process  Burst  Priority (smaller = higher)
  P1       10      3
  P2        1      1   ← highest
  P3        2      4
  P4        1      5
  P5        5      2

Order: P2 → P5 → P1 → P3 → P4
Gantt: |P2|----- P5 -----|------ P1 ------|--P3--|P4|

Starvation và Aging

Process priority thấp có thể không bao giờ chạy nếu liên tục có process priority cao. Starvation.

Aging: tăng priority dần theo thời gian process đợi. Sau đủ lâu, dù priority ban đầu thấp, nó sẽ leo lên cao và được chạy.

Linux nice value

# Tạo process với priority thấp (nice +10, "tử tế hơn")
nice -n 10 ./long_running_task

# Đổi priority của process đang chạy
renice -n 5 -p 1234

# Xem nice value
ps -eo pid,ni,comm | head

Linux nice: -20 (cao nhất) đến +19 (thấp nhất). User thường: 0 đến +19. Cần root để đặt nice âm.

4.7 Round Robin (RR) — fair time slicing

Mỗi process được CPU 1 khoảng time quantum (vd 10ms). Hết quantum, OS preempt và chuyển sang process tiếp theo trong queue (FIFO). Process bị preempt được đẩy về cuối queue.

Process  Burst time      Quantum = 4ms
  P1        24
  P2         3
  P3         3

Gantt:
|P1|P2|P3|P1|P1|P1|P1|P1|
0  4  7 10 14 18 22 26 30

Waiting time:
  P1: (10-4) = 6  (chỉ tính lần đầu đợi)
  P2: 4
  P3: 7
Avg: (6+4+7)/3 = 5.67

Time quantum — chọn thế nào?

  • Quá nhỏ (vd 1ms): nhiều context switch → overhead cao, throughput thấp
  • Quá lớn (vd 1s): RR thoái hoá thành FCFS, response time tệ
  • Vừa phải (10-100ms): cân bằng — Linux mặc định ~6ms với CFS
  • Ưu: fair, response time tốt
  • Nhược: không tối ưu turnaround/waiting; mọi process được đối xử như nhau (không xét priority hay loại workload)

4.8 MLFQ — Multi-Level Feedback Queue

Thuật toán "tự học" — kết hợp nhiều RR queue với priority khác nhau, và tự điều chỉnh priority dựa trên hành vi process.

Q0 (highest, quantum 4ms): [P1] [P2] Q1 (mid, quantum 8ms): [P3] Q2 (lowest, quantum 16ms): [P4] [P5]

Quy tắc MLFQ

  1. Process mới → vào queue cao nhất (Q0)
  2. Scheduler chọn process từ queue cao nhất không rỗng
  3. Process dùng hết quantum mà chưa xong → đẩy xuống queue thấp hơn (CPU-bound)
  4. Process tự nguyện block I/O trước khi hết quantum → giữ priority (I/O-bound)
  5. Định kỳ "boost" mọi process lên Q0 (tránh starvation)

Hiệu ứng: I/O-bound process (text editor, terminal) ở Q cao → response nhanh. CPU-bound process (build, render) "rơi" xuống Q thấp → vẫn chạy nhưng không chiếm hết CPU.

MLFQ là tổ tiên của các scheduler hiện đại. Windows NT, BSD đều dùng biến thể MLFQ.

4.9 Linux CFS — Completely Fair Scheduler

Linux từ 2.6.23 (2007) dùng CFS. Ý tưởng: mô phỏng "ideal multitasking" — nếu có N process, mỗi process đáng lẽ phải nhận 1/N CPU time. CFS theo dõi vruntime (virtual runtime) của mỗi process, chọn process có vruntime nhỏ nhất để chạy.

Cấu trúc dữ liệu

CFS dùng Red-Black Tree sắp xếp process theo vruntime. Lấy min = O(log n).

Cách vruntime tăng

Process chạy 10ms thực tế → vruntime tăng 10ms × hệ số (phụ thuộc nice value). Nice cao (priority thấp) → vruntime tăng nhanh hơn → ít được chạy.

Các tunable

# Xem các CFS parameter
cat /proc/sys/kernel/sched_min_granularity_ns       # min slice ~1.5ms
cat /proc/sys/kernel/sched_latency_ns               # target latency ~6ms
cat /proc/sys/kernel/sched_wakeup_granularity_ns

# Xem scheduling stats của 1 process
cat /proc/$$/sched | head
📐 CFS không phải MLFQ

CFS không có "queue" theo priority. Tất cả process trong 1 cây cân bằng theo vruntime. Priority chỉ ảnh hưởng tốc độ vruntime tăng. Đơn giản nhưng cực hiệu quả.

Real-time scheduler (SCHED_FIFO, SCHED_RR)

Cho process cần độ trễ thấp (audio, robot control), Linux có scheduling class riêng: SCHED_FIFO (FCFS, không preempt cho đến khi block) và SCHED_RR (RR với time quantum). Cần root: chrt -f 99 ./realtime_app.

Bài tập

Bài 1 — Tính avg waiting time

4 process: P1=8, P2=4, P3=9, P4=5 (burst time, đến cùng lúc t=0). Tính avg waiting cho FCFS, SJF, RR (quantum=4).

Bài 2 — Convoy effect

Trong FCFS, "convoy effect" là gì? Cho ví dụ cụ thể (2 process). Cách khắc phục?

Bài 3 — Quan sát Linux scheduler

Chạy top -d 0.5, để 1 minute. Quan sát process nào chạy nhiều CPU. Với nice -n 19 yes > /dev/null &, so sánh CPU% với yes > /dev/null & (nice 0).

Bài 4 — RR quantum tradeoff

Quantum 1ms vs 100ms vs 1s — lợi/hại từng cái? Đa số OS chọn quantum bao nhiêu?

Bài 5 — Tại sao cần aging?

Cho ví dụ pure priority scheduling gây starvation. Aging giải quyết thế nào?

Bài 6 — CFS và nice

Trong CFS, process A có nice -5 và process B có nice 5. Nếu cả 2 cùng CPU-bound, CPU time tỉ lệ như nào? Hint: nice diff 1 = ~10% CPU diff theo Linux source.

🧪 Quiz cuối chương

Câu 1. Convoy effect xảy ra ở scheduler nào?

  • SJF
  • FCFS — process dài chặn các process ngắn phía sau
  • Round Robin
  • CFS

Đáp án: FCFS. Trong FCFS, mọi process sau bị đợi process dài chạy xong, dù chính chúng rất ngắn.

Câu 2. SJF tối ưu cho metric nào?

  • Average waiting time (lý thuyết tối ưu)
  • Fairness
  • Response time
  • Throughput

Đáp án: avg waiting time. SJF chứng minh được tối ưu lý thuyết. Nhược: không biết burst trước, có starvation.

Câu 3. Aging được dùng để giải quyết vấn đề nào?

  • Memory leak
  • Deadlock
  • Starvation — process priority thấp không bao giờ được chạy
  • Race condition

Đáp án: Starvation. Tăng priority dần theo thời gian đợi → cuối cùng cũng được chạy.

Câu 4. Round Robin với quantum quá NHỎ thì sao?

  • Nhanh hơn
  • Fair hơn
  • Quá nhiều context switch — overhead cao, throughput giảm
  • Process dài chạy nhanh hơn

Đáp án: nhiều context switch. Mỗi switch tốn ~vài microsecond. Quantum 0.1ms → 90% thời gian là switch.

Câu 5. Linux dùng scheduler chính nào?

  • MLFQ
  • CFS (Completely Fair Scheduler) — Red-Black Tree theo vruntime
  • Round Robin pure
  • FCFS

Đáp án: CFS. Linux từ 2.6.23 (2007). Cấu trúc Red-Black tree, lấy min vruntime.

Câu 6. Trong CFS, process có nice value cao (vd +19) sẽ?

  • Nhận ít CPU hơn — vruntime tăng nhanh hơn → bị "đẩy" trong cây
  • Nhận nhiều CPU hơn
  • Bị kill
  • Không có ảnh hưởng

Đáp án: ít CPU hơn. Nice cao = priority thấp = "tử tế" với process khác.

Câu 7. Preemptive scheduling cần phần cứng gì?

  • RAM lớn
  • CPU đa nhân
  • Hardware timer phát interrupt định kỳ
  • SSD

Đáp án: timer interrupt. Không có timer, process tự nguyện trả CPU mới được chạy → cooperative.

Câu 8. MLFQ "tự học" gì?

  • Burst time tương lai của process
  • Process là CPU-bound hay I/O-bound, từ đó adjust priority
  • Số core CPU
  • Nice value tối ưu

Đáp án: CPU-bound vs I/O-bound. Process dùng hết quantum → CPU-bound → tụt priority. Process tự block I/O sớm → I/O-bound → giữ priority cao.

Tổng kết chương 4

  • ✅ Scheduler = bộ não chọn process nào dùng CPU; chạy hàng nghìn lần/giây
  • ✅ 6 metric đánh giá: utilization, throughput, turnaround, waiting, response, fairness — thường mâu thuẫn
  • ✅ Preemptive (cần timer interrupt) là chuẩn của OS hiện đại
  • FCFS: convoy effect; SJF: optimal lý thuyết, có starvation
  • Priority + Aging: tránh starvation
  • Round Robin: fair với time quantum, response tốt
  • MLFQ: tự học CPU-bound vs I/O-bound
  • Linux CFS: vruntime + Red-Black tree, không có "queue", priority qua nice
← Chương trước Chương 03: Thread Chương kế tiếp Chương 05: IPC →