Tổng kết module Tiến trình — cheat sheet & self-assessment
Cheat sheet vòng đời tiến trình (PCB, fork/exec/wait, zombie/orphan, signal), glossary, pitfall tổng hợp và self-assessment đối chiếu learning outcomes.
TL;DR: Module 2 bóc vòng đời một tiến trình từ lúc sinh tới lúc chết. Bạn phân biệt được chương trình (file tĩnh) với tiến trình (một lần chạy, có PCB riêng); trace được bộ ba fork → exec → wait mà mọi shell dùng; hiểu vì sao xuất hiện zombie (con chết, cha chưa wait) và orphan (cha chết trước, init nhận nuôi); và so sánh được signal — SIGTERM xin dừng (cleanup được) vs SIGKILL giết cứng (không cãi). Đây là trang để bookmark.
Đã đi qua những gì
Module mở đầu bằng một phân biệt tưởng hiển nhiên mà nhiều người mơ hồ: chương trình là file thực thi nằm im trên đĩa, còn tiến trình là chương trình đó đang chạy — có bộ nhớ riêng, register, program counter, và tài nguyên OS cấp. Một chương trình sinh nhiều tiến trình độc lập; để tạm dừng một tiến trình rồi chạy tiếp đúng chỗ, OS lưu toàn bộ trạng thái của nó trong PCB (Process Control Block).
Rồi bạn dựng vòng đời bằng ba system call cổ điển: fork nhân đôi tiến trình (copy-on-write nên rẻ) và trả về hai giá trị để cha-con phân vai; exec thay toàn bộ ruột tiến trình bằng chương trình mới (giữ PID, thành công thì không trả về); wait cho cha thu exit status và dọn xác con. Bạn thấy vì sao shell chạy mọi lệnh theo đúng bộ ba này — để giữ chính nó sống mà vẫn chạy được chương trình khác.
Hai trạng thái "hậu sự" tiếp theo: zombie (con đã chết nhưng cha chưa wait, kernel giữ PID + exit status) và orphan (cha chết trước con, init/systemd PID 1 nhận nuôi + reap). Bạn học cách nhận diện zombie (Z, <defunct>) và nghịch lý "giết cha để dọn con đã chết".
Cuối cùng là signal — cách OS gõ cửa một tiến trình. Bạn so sánh SIGTERM (xin dừng, bắt được, cho cleanup), SIGKILL và SIGSTOP (không thể catch/block/ignore), SIGINT (Ctrl+C); và nắm pattern graceful shutdown mà docker stop dùng (SIGTERM → chờ 10s → SIGKILL). Mini-challenge ghép tất cả: bạn tự cầm ps/pstree/kill mổ xẻ cây tiến trình thật trên máy mình.
graph LR ROOT["Tien trinh"] ROOT --> A["Tien trinh va PCB"] ROOT --> B["Vong doi fork/exec/wait"] ROOT --> C["Zombie va orphan"] ROOT --> D["Signal"] A --> A1["Chuong trinh tinh vs tien trinh dang chay"] A --> A2["PID, PPID, khong gian dia chi rieng"] A --> A3["PCB luu register, PC, trang thai, tai nguyen"] A --> A4["Tam dung roi chay tiep dung cho"] B --> B1["fork nhan doi, tra 2 gia tri, copy-on-write"] B --> B2["exec thay ruot, giu PID, khong tra ve"] B --> B3["exit tra status, wait thu hoach"] B --> B4["Shell fork-exec-wait giu minh song"] C --> C1["Zombie: con chet, cha chua wait"] C --> C2["Chiem PID, khong ton RAM/CPU"] C --> C3["Orphan: cha chet truoc, init nhan nuoi"] C --> C4["Kill cha de reap zombie"] D --> D1["SIGTERM xin dung, cleanup duoc"] D --> D2["SIGKILL/SIGSTOP khong bat duoc"] D --> D3["SIGINT = Ctrl+C"] D --> D4["Graceful shutdown, docker stop 10s"]
🗺️ Cheat sheet
| Khái niệm | Cốt lõi | Pitfall / khi nào dùng |
|---|---|---|
| Chương trình vs tiến trình | File tĩnh trên đĩa vs một lần chạy có bộ nhớ riêng | Chạy 1 chương trình 2 lần = 2 tiến trình, không chia sẻ biến |
| PCB | Cấu trúc kernel lưu register, PC, trạng thái, bộ nhớ, file mở | Nằm trong bộ nhớ kernel, không phải vùng user |
| PID / PPID | Số định danh tiến trình / của cha | PID được tái sử dụng sau khi tiến trình chết |
fork() | Nhân đôi tiến trình; con là bản sao (copy-on-write) | Trả 2 giá trị: PID con cho cha, 0 cho con; cả hai chạy tiếp |
exec() | Thay toàn bộ code/data, giữ PID | Thành công thì KHÔNG trả về; code sau chỉ chạy khi exec lỗi |
wait() / waitpid() | Cha chờ con + thu exit status + dọn xác | Không wait → zombie; WNOHANG để reap không block |
| Zombie | Con chết, cha chưa wait; giữ PID + exit status | Không kill được; dọn bằng cách cha wait hoặc kill cha |
| Orphan | Cha chết trước con; init nhận nuôi | Tự lành — init reap khi con chết |
SIGTERM (15) | Xin dừng lịch sự, bắt được | kill <pid> mặc định gửi cái này; cho cleanup |
SIGKILL (9) | Giết cứng, không bắt/chặn được | kill -9; van cuối cùng; mất cleanup |
SIGSTOP (19) | Đóng băng, không bắt được | Tạm dừng, không kết thúc |
SIGINT (2) | Ctrl+C tới foreground, bắt được | Chương trình có thể bỏ qua → Ctrl+C không ăn |
| Graceful shutdown | Bắt SIGTERM, cleanup, thoát | Handler chỉ đặt cờ (async-signal-safe); main cleanup |
📖 Glossary module
| Thuật ngữ | Định nghĩa 1 câu |
|---|---|
| Tiến trình (process) | Một lần chạy của chương trình, có không gian địa chỉ + tài nguyên riêng |
| Chương trình (program) | File thực thi tĩnh trên đĩa — dữ liệu, chưa chạy |
| PCB (Process Control Block) | Cấu trúc kernel lưu toàn bộ trạng thái một tiến trình để quản lý nó |
| PID / PPID | Process ID / Parent Process ID — số định danh tiến trình và cha nó |
| Không gian địa chỉ (address space) | Vùng bộ nhớ ảo riêng của mỗi tiến trình, cô lập với tiến trình khác |
fork() | System call nhân đôi tiến trình gọi, tạo tiến trình con |
| Copy-on-write | Kỹ thuật cho cha-con chung trang bộ nhớ tới khi một bên ghi mới chép riêng |
exec() | System call thay process image bằng chương trình mới, giữ PID |
exit() / exit status | Kết thúc tiến trình, trả một số nguyên báo thành/bại cho cha |
wait() / waitpid() | System call cha dùng để chờ con kết thúc và thu exit status |
| Zombie | Tiến trình đã chết nhưng cha chưa wait — giữ PID + exit status |
| Orphan | Tiến trình con còn sống mà cha đã chết — được init nhận nuôi |
| init / systemd (PID 1) | Tiến trình đầu tiên lúc boot, tổ tiên mọi tiến trình user-space, reap orphan |
| Reap | Thao tác cha (hoặc init) wait để giải phóng ô bảng tiến trình của con đã chết |
| Signal | Thông báo bất đồng bộ kernel gửi tới tiến trình về một sự kiện |
| Handler | Hàm tiến trình đăng ký để phản ứng khi một signal tới |
| Default action | Hành vi mặc định khi signal tới mà không có handler (Term/Stop/Ign/Core) |
| SIGTERM / SIGKILL / SIGINT / SIGSTOP | Signal: xin dừng / giết cứng / Ctrl+C / đóng băng |
| Graceful shutdown | Bắt SIGTERM để dừng có trật tự: hoàn tất việc dở, đóng tài nguyên, thoát |
⚠️ Pitfall tổng hợp
1. Tưởng chạy chương trình 2 lần thì chia sẻ biến:
static int count = 0; // moi tien trinh co ban sao RIENG -- khong chia se
Mỗi lần chạy là một tiến trình với không gian địa chỉ riêng. Muốn chia sẻ dữ liệu phải dùng IPC (Module 4).
2. Tưởng code sau exec (thành công) sẽ chạy:
execlp("ls", "ls", (char *) NULL);
printf("Done\n"); // SAI: khong bao gio chay neu exec thanh cong
perror("exec failed"); // DUNG: chi chay khi exec LOI
3. Quên rằng cả cha và con chạy tiếp sau fork:
fork();
printf("Hi\n"); // in HAI lan -- phai re nhanh theo pid == 0 vs pid > 0
4. Kill zombie để dọn nó:
kill -9 <zombie_pid> # VO TAC DUNG -- zombie da chet
kill <parent_pid> # DUNG -- kill cha, init reap zombie
5. Làm việc nặng trong signal handler:
void handler(int sig) {
save_to_db(); // RUI RO -- khong async-signal-safe
}
void handler2(int sig) {
stop_flag = 1; // DUNG -- chi dat co, main cleanup sau
}
6. Cố bắt SIGKILL để chống bị giết:
signal(SIGKILL, h); // VO ICH -- SIGKILL khong the bat; gan cleanup vao SIGTERM
✅ Self-assessment
Bạn đã đạt module này nếu trả lời được:
- Explain được tiến trình khác chương trình thế nào và OS lưu những gì trong PCB để tạm dừng rồi chạy tiếp một tiến trình — nêu được ít nhất 3 nhóm thông tin trong PCB và vì sao nhờ nó mà tạm dừng/chạy tiếp không mất dấu.
- Nếu chưa: đọc lại bài 01 — Tiến trình & PCB mục 3–4.
- Trace được vòng đời tiến trình qua
fork,exec,wait,exit— kể cả nhánh zombie và orphan.- Nếu chưa: đọc lại bài 02 — fork, exec, wait mục 5 và bài 03 — Zombie & orphan mục 2–4.
- Compare được
SIGTERM,SIGKILL,SIGINT,SIGSTOPvà cách tiến trình phản ứng — kể cả pattern graceful shutdown vớiSIGTERM.- Nếu chưa: đọc lại bài 04 — Signal mục 2–4.
- Diagnose được cây tiến trình thật bằng
ps,pstree,kill— truy vết cha-con tới PID 1, tạo và dọn một zombie.- Nếu chưa: làm lại bài 05 — Mini-challenge cả ba nhiệm vụ.
🚀 What's next
Bạn đã nắm một tiến trình đơn lẻ sinh ra, sống, và chết thế nào. Nhưng một tiến trình thường có nhiều luồng thực thi bên trong, và OS phải chia CPU cho hàng trăm tiến trình cùng lúc.
Module tiếp theo — Thread & lập lịch trả lời: thread khác process ở đâu (chia sẻ không gian địa chỉ vs cô lập); context switch tốn gì thật sự (register, cache, TLB); scheduler chia CPU ra sao (time slice, priority); và vì sao workload CPU-bound và I/O-bound cần chiến lược khác nhau. Đây là bước từ "một tiến trình" sang "nhiều thứ chạy đồng thời".
Bắt đầu: Module 3 — Thread & lập lịch: tổng quan
Khám phá thêm: Tất cả khoá học
📚 Tài liệu mở rộng
- OSTEP — "Operating Systems: Three Easy Pieces" (Arpaci-Dusseau), miễn phí tại https://pages.cs.wisc.edu/~remzi/OSTEP/ — chương "The Abstraction: The Process" (
cpu-intro.pdf) và "Process API" (cpu-api.pdf) là nền chuẩn cho cả module này. Đọc để thấy vì sao Unix táchfork/exec. - man7.org — Linux man-pages — nguồn chính chủ cho từng system call: fork(2), execve(2), wait(2), signal(7), ps(1). Tra khi cần chi tiết chính xác về tham số, giá trị trả về, edge case.
- "Advanced Programming in the UNIX Environment" (Stevens & Rago) — chương 8 (Process Control) và 10 (Signals) đào sâu hơn man page, với ví dụ C production-grade. Sách kinh điển cho lập trình hệ thống Unix.
- Docker docs — docker stop — xác nhận hành vi SIGTERM → grace period → SIGKILL; đọc kèm tài liệu Kubernetes về
terminationGracePeriodSecondsđể thấy cùng pattern ở tầng orchestration.
Bài tiếp theo: Module 3 — Thread & lập lịch: tổng quan
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên