OS & Concurrency: Scheduling và Execution Flow
5. Quan hệ OS Thread ↔ Physical Thread
OS Thread là "công việc cần chạy"
Physical Thread là "máy móc thực thi"
Kernel Scheduler gán OS Thread lên Physical Thread
HARDWARE LAYER
┌──────────────────────────────────────────┐
│ CPU Package │
│ ┌─────────┐ ┌─────────┐ │
│ │ Core 0 │ │ Core 1 │ ... │
│ │ [HT0] │ │ [HT0] │ │
│ │ [HT1] │ │ [HT1] │ │
│ └────┬────┘ └────┬────┘ │
└───────┼────────────┼────────────────────┘
│
▼ OS / KERNEL LAYER
┌───────────────────────────────────────────┐
│ Kernel Scheduler │
│ Run Queue: [T3][T7][T1][T9]... │
│ Wait Queue: [T2][T5][T8]... │
└───────────────────────────────────────────┘
Quan hệ một-một tại một thời điểm: 1 OS Thread chạy trên 1 Physical Thread, 1 Physical Thread chạy 1 OS Thread. Nhưng kernel liên tục luân phiên hàng nghìn OS Threads lên ít Physical Threads — đây là bản chất của multitasking.
Thread Affinity — Gắn cứng thread vào core
// Linux: gắn thread chỉ chạy trên Core 2 và Core 3
cpu_set_t cpuset;
CPU_SET(2, &cpuset);
CPU_SET(3, &cpuset);
pthread_setaffinity_np(thread, sizeof(cpuset), &cpuset);
Khi dùng affinity, L1/L2 cache của core luôn "nóng" vì thread không bị migrate sang core khác. Hữu ích cho: real-time systems cần latency cực thấp, game engines (render thread riêng core), databases (tách I/O thread và compute thread).
6. Kernel Scheduler hoạt động như thế nào
CFS — Completely Fair Scheduler (Linux)
Linux dùng CFS từ kernel 2.6.23. Nguyên tắc: mỗi thread được dùng CPU công bằng theo đúng tỉ lệ priority — thread nào "thiếu" CPU nhất thì được chạy trước.
Virtual Runtime (vruntime)
Mỗi OS Thread có vruntime = tổng thời gian đã dùng CPU (điều chỉnh theo priority). Scheduler luôn chọn thread có vruntime nhỏ nhất để chạy tiếp.
Ví dụ:
Thread A: vruntime = 10ms (đã chạy nhiều)
Thread B: vruntime = 3ms ← được chọn ✓
Thread C: vruntime = 7ms
Sau khi B chạy 4ms → vruntime B = 7ms → B và C bằng nhau
Cách này đảm bảo không có thread nào bị starvation — thread ít được chạy sẽ có vruntime thấp và được ưu tiên.
Red-Black Tree — Cấu trúc dữ liệu của run queue
CFS lưu run queue trong Red-Black Tree (cây nhị phân tự cân bằng), sorted theo vruntime:
[7ms - Thread C]
/ \
[3ms - B] [10ms - A]
→ Thread vruntime nhỏ nhất = nút tận cùng trái
→ Pick O(log n) — cực nhanh dù hàng nghìn threads
Khi nào Scheduler chạy?
Trigger 1 — Timer Interrupt (quan trọng nhất)
Hardware timer ngắt CPU mỗi ~1-10ms
→ Kiểm tra thread hiện tại đã chạy đủ chưa?
→ Nếu rồi → preempt → schedule thread khác
Trigger 2 — Thread tự block
Thread gọi: sleep(), read(), recv(), mutex.lock()...
→ Tự rời CPU → Scheduler chạy thread khác ngay lập tức
Trigger 3 — Thread mới được tạo
→ Xem xét có nên preempt thread hiện tại không
Trigger 4 — I/O hoàn thành / Signal
Thread BLOCKED → chuyển sang READY
→ Có thể preempt thread hiện tại nếu mới hơn và important hơn
7. How Work Gets Done — Từ code đến kết quả
CPU Pipeline
Mỗi CPU instruction đi qua pipeline 4 giai đoạn:
result = a + b
Assembly: ADD rax, rbx
CPU Pipeline:
Fetch → Decode → Execute → Writeback
(lấy (hiểu (ALU (ghi
lệnh) lệnh) tính) kết quả)
Kết quả đi: Register → L1 Cache → L2 Cache → L3 Cache → RAM
Modern CPU có multiple pipeline stages và out-of-order execution — thực tế phức tạp hơn nhiều, nhưng đây là mental model cơ bản.
User Mode vs Kernel Mode
Code bạn viết chạy ở User Mode — không được đụng hardware trực tiếp. Mọi thứ liên quan đến hardware (network, disk, screen) phải đi qua syscall để vào Kernel Mode.
┌─────────────────────────────────────────────┐
│ Application (User Mode) │ ← code bạn viết
└──────────────────┬──────────────────────────┘
│ syscall (open, read, write, send...)
│ ~200-400ns overhead mỗi lần
┌──────────────────▼──────────────────────────┐
│ Linux Kernel (Kernel Mode) │
│ Scheduler | Memory Manager | Network | FS │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ Physical Hardware: CPU, RAM, NIC, Disk │
└─────────────────────────────────────────────┘
Syscall overhead (~200-400ns) là lý do Go và Node.js cẩn thận tránh syscall không cần thiết khi xử lý I/O.
Vòng đời một web request
Theo dõi một request từ đầu đến cuối để thấy tất cả các tầng phối hợp:
1. Browser gửi request
→ OS tạo OS Thread
→ Thread gọi send() syscall → vào Kernel Mode
→ Kernel xử lý network stack → gửi packet
→ Thread bị BLOCKED (chờ response) — CPU được giải phóng!
2. Server response về
→ Hardware interrupt từ NIC
→ Kernel đánh thức thread → READY
→ Scheduler gán Core khi có Core rảnh
3. Thread nhận data → xử lý
→ CPU chạy thật sự (RUNNING state)
→ Render kết quả ra màn hình
CPU Idle — CPU làm gì lúc nghỉ?
Khi không có gì để chạy, kernel chạy "idle thread":
while (true) {
HLT; // instruction: nghỉ đến interrupt tiếp theo
}
CPU có C-states để tiết kiệm điện khi idle:
C1: ~1µs để thức dậy (giảm clock)
C3: ~50µs để thức dậy (giảm voltage)
C6: ~200µs để thức dậy (gần như tắt core)
Đó là lý do laptop mát hơn và pin lâu hơn khi idle — CPU thực sự ở trạng thái tiêu thụ rất ít điện.