Hệ điều hành & Tiến trình/Tổng kết module Tiến trình — cheat sheet & self-assessment
14/28
Bài 14 / 28~6 phútTiến trình — sinh ra, sống, chếtMiễn phí lượt xem

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 forkexecwait 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 signalSIGTERM 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), SIGKILLSIGSTOP (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ệmCốt lõiPitfall / khi nào dùng
Chương trình vs tiến trìnhFile tĩnh trên đĩa vs một lần chạy có bộ nhớ riêngChạy 1 chương trình 2 lần = 2 tiến trình, không chia sẻ biến
PCBCấ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 / PPIDSố định danh tiến trình / của chaPID đượ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ữ PIDThà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ácKhông wait → zombie; WNOHANG để reap không block
ZombieCon chết, cha chưa wait; giữ PID + exit statusKhông kill được; dọn bằng cách cha wait hoặc kill cha
OrphanCha chết trước con; init nhận nuôiTự lành — init reap khi con chết
SIGTERM (15)Xin dừng lịch sự, bắt đượckill <pid> mặc định gửi cái này; cho cleanup
SIGKILL (9)Giết cứng, không bắt/chặn đượckill -9; van cuối cùng; mất cleanup
SIGSTOP (19)Đóng băng, không bắt đượcTạm dừng, không kết thúc
SIGINT (2)Ctrl+C tới foreground, bắt đượcChương trình có thể bỏ qua → Ctrl+C không ăn
Graceful shutdownBắt SIGTERM, cleanup, thoátHandler 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 / PPIDProcess 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-writeKỹ 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 statusKế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
ZombieTiến trình đã chết nhưng cha chưa wait — giữ PID + exit status
OrphanTiế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
ReapThao tác cha (hoặc init) wait để giải phóng ô bảng tiến trình của con đã chết
SignalThông báo bất đồng bộ kernel gửi tới tiến trình về một sự kiện
HandlerHàm tiến trình đăng ký để phản ứng khi một signal tới
Default actionHành vi mặc định khi signal tới mà không có handler (Term/Stop/Ign/Core)
SIGTERM / SIGKILL / SIGINT / SIGSTOPSignal: xin dừng / giết cứng / Ctrl+C / đóng băng
Graceful shutdownBắ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.
  • Trace được vòng đời tiến trình qua fork, exec, wait, exit — kể cả nhánh zombie và orphan.
  • Compare được SIGTERM, SIGKILL, SIGINT, SIGSTOP và cách tiến trình phản ứng — kể cả pattern graceful shutdown với SIGTERM.
  • 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.

🚀 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ách fork/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

Đặt 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