Mini-challenge — mổ xẻ cây tiến trình bằng ps, pstree, kill
Tự dùng ps/pstree/kill: tìm quan hệ cha-con từ PID 1 xuống shell của bạn, tự tạo một zombie rồi diệt nó, và thử SIGTERM vs SIGKILL.
TL;DR: Đủ lý thuyết — giờ cầm công cụ thật. Bài này cho bạn ba nhiệm vụ trên máy Linux của chính mình: (1) dùng pstree/ps truy vết chuỗi cha-con từ init/systemd (PID 1) xuống đúng cái shell bạn đang gõ; (2) cố tình tạo một zombie rồi quan sát nó bằng ps (trạng thái Z, nhãn <defunct>) và dọn nó; (3) gửi SIGTERM rồi SIGKILL lên một tiến trình thật để thấy tận mắt khác biệt "xin dừng" và "giết cứng". Xong bài, bạn không chỉ biết về tiến trình — bạn đọc được cây tiến trình đang sống trên máy mình.
🎯 Đề bài
Bạn cần một terminal trên Linux (hoặc WSL2 / macOS — lệnh gần giống, pstree có thể cần cài). Ba nhiệm vụ độc lập:
Nhiệm vụ 1 — Truy vết cây tiến trình. Từ terminal của bạn, tìm chuỗi cha-con đầy đủ từ PID 1 (init/systemd) đi xuống cho tới chính tiến trình shell bạn đang dùng. Với mỗi mắt xích, ghi lại PID và tên tiến trình. Trả lời: shell của bạn có PPID là gì? Đi ngược lên nữa tới đâu thì gặp PID 1?
Nhiệm vụ 2 — Tạo và dọn một zombie. Viết một chương trình (bash hoặc C) fork ra con, con thoát ngay, còn cha không wait và ngủ một lúc. Trong lúc đó, dùng ps tìm con ở trạng thái Z. Sau đó dọn zombie — bằng đúng cách đã học ở bài 03.
Nhiệm vụ 3 — SIGTERM vs SIGKILL. Chạy một tiến trình đơn giản (ví dụ sleep 1000), rồi lần lượt thử dừng nó bằng SIGTERM và bằng SIGKILL. Sau đó viết một tiến trình phớt lờ SIGTERM và xác nhận rằng chỉ SIGKILL giết được nó.
Đây là bài independent: cố làm cả ba nhiệm vụ trước khi mở phần Lời giải. Bạn đã có đủ công cụ từ bốn bài trước.
🔍 Phân tích I-P-O
| Mô tả | |
|---|---|
| Input | Máy Linux đang chạy, quyền chạy ps/pstree/kill, gcc (cho phần C) |
| Process | Quan sát (đọc trạng thái tiến trình) + tác động (gửi signal, tạo/dọn tiến trình) |
| Output | Chuỗi cha-con tới PID 1; một zombie quan sát được rồi dọn; khác biệt SIGTERM/SIGKILL xác nhận bằng mắt |
Điều kiện làm đúng:
- Không cần
sudocho các tiến trình của chính bạn. - Cẩn thận: chỉ
killcác tiến trình bạn tự tạo (đừng kill PID lạ, nhất là số nhỏ). - Mở hai terminal: một để chạy tiến trình thí nghiệm, một để
ps/killquan sát.
📦 Concept mapping
Bài này tổng hợp trực tiếp cả bốn bài của module:
| Kiến thức cần | Bài gốc | Dùng ở đây |
|---|---|---|
| PID, PPID, cây cha-con | Bài 01 — Tiến trình & PCB | Truy vết chuỗi PPID lên tới PID 1 |
| fork tạo con, wait thu xác | Bài 02 — fork, exec, wait | Viết chương trình fork ra con |
Zombie (Z, <defunct>), reap qua cha | Bài 03 — Zombie & orphan | Tạo zombie, nhận diện, dọn bằng cách xử lý cha |
| SIGTERM vs SIGKILL, bắt được hay không | Bài 04 — Signal | Gửi hai signal, quan sát khác biệt |
▶️ Starter
Nhiệm vụ 1 — vài lệnh để bắt đầu:
# Xem cay tien trinh co PID, tu goc xuong
pstree -p
# Shell hien tai cua ban co PID nao?
echo "Shell PID = $$"
# Xem PID + PPID + lenh cua mot tien trinh cu the
ps -o pid,ppid,comm -p $$
Nhiệm vụ 2 — khung tạo zombie bằng bash (điền phần còn thiếu):
# zombie.sh -- cha tao con, con chet, cha khong wait ngay
# TODO: cho con thoat ngay, cha ngu 60s de quan sat zombie
( sleep 0 ) & # tien trinh con chay roi thoat gan nhu tuc thi
CHILD=$!
echo "Con PID = $CHILD, dang ngu de ban quan sat..."
# TODO: them lenh giu cha song ma khong wait con
Bash tương tác thường tự wait (reap) job nền của nó ngay khi job chết, nên bạn có thể không thấy trạng thái Z với khung bash này. Nếu vậy, dùng bản C ở phần Lời giải (zombie.c) — nó kiểm soát chính xác việc cha không wait, đảm bảo quan sát được zombie.
Nhiệm vụ 3 — tiến trình để thí nghiệm signal:
# Terminal 1: chay mot tien trinh de kill
sleep 1000 &
echo "sleep PID = $!"
# TODO (Terminal 2): gui SIGTERM roi SIGKILL vao PID nay
💡 Gợi ý
Gợi ý 1 (truy vết): pstree -p -s <PID> in nhánh từ PID đó ngược lên tới gốc — nhanh hơn đọc cả cây. Thử pstree -p -s $$.
Gợi ý 2 (zombie): trong bash, một job con đã chết mà chưa được wait là zombie. Nếu bash tự động reap quá nhanh, dùng bản C ở Lời giải (kiểm soát chính xác hơn). Tìm zombie: ps -el | grep ' Z ' hoặc ps aux | grep defunct.
Gợi ý 3 (dọn zombie): nhớ bài 03 — không kill được zombie. Zombie biến mất khi cha wait nó, hoặc khi cha chết (init reap). Vậy cách dọn nhanh nhất: kết thúc tiến trình cha.
Gợi ý 4 (signal): kill -TERM <pid> (hay kill <pid>) gửi SIGTERM; kill -KILL <pid> (hay kill -9) gửi SIGKILL. Với tiến trình phớt lờ SIGTERM, quan sát: sau kill -TERM nó vẫn còn trong ps; sau kill -9 nó biến mất.
✅ Lời giải
Nhiệm vụ 1 — Truy vết cây tiến trình
$ echo $$
5321 # PID cua shell hien tai (vi du bash)
$ pstree -p -s 5321
systemd(1)---systemd(1234)---gnome-terminal(4180)---bash(5321)
^PID 1 ^user session ^terminal emulator ^shell cua ban
Đọc từ phải sang: bash (PID 5321) có cha là gnome-terminal (4180), cha nó là systemd --user (1234), rồi lên tới systemd (PID 1) — tổ tiên của mọi tiến trình. Kiểm chứng từng mắt xích bằng ps -o pid,ppid,comm -p <pid>:
$ ps -o pid,ppid,comm -p 5321
PID PPID COMMAND
5321 4180 bash # PPID cua bash = 4180 (terminal)
Mọi tiến trình đều truy được về PID 1 — đó là ý nghĩa "cây tiến trình": một cây có gốc duy nhất là init/systemd.
Nhiệm vụ 2 — Tạo và dọn zombie
Bản C kiểm soát chính xác (bash đôi khi reap ngầm):
// zombie.c
#include <stdio.h>
#include <unistd.h>
int main(void) {
pid_t pid = fork();
if (pid == 0) {
_exit(0); // con thoat ngay -> thanh zombie
}
printf("Cha PID=%d, con zombie PID=%d\n", getpid(), pid);
sleep(60); // cha KHONG wait -> con ket lai zombie 60s
return 0;
}
# Terminal 1:
$ gcc zombie.c -o zombie && ./zombie
Cha PID=6001, con zombie PID=6002
# Terminal 2 (trong 60 giay):
$ ps -o pid,ppid,stat,comm -p 6002
PID PPID STAT COMMAND
6002 6001 Z zombie # STAT = Z, con la zombie cua cha 6001
$ ps aux | grep defunct
user 6002 ... Z ... [zombie] <defunct> # nhan <defunct>
Dọn zombie — không kill zombie (6002) được; kill cha (6001):
# Terminal 2:
$ kill -9 6002 # VO TAC DUNG: zombie da chet roi
$ kill 6001 # kill CHA -> con thanh orphan cua init -> init reap ngay
# Kiem tra: 6002 da bien mat khoi ps
Khi cha 6001 chết, zombie 6002 được init (PID 1) nhận nuôi và reap tức thì — đúng cơ chế bài 03.
Nhiệm vụ 3 — SIGTERM vs SIGKILL
Tiến trình thường (sleep) chết với cả hai:
$ sleep 1000 &
[1] 6100
$ kill -TERM 6100 # SIGTERM -> sleep khong bat, dung default Term -> chet
[1]+ Terminated sleep 1000
Giờ một tiến trình phớt lờ SIGTERM — chỉ SIGKILL giết được:
// stubborn.c -- bo qua SIGTERM, chi SIGKILL moi giet duoc
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main(void) {
signal(SIGTERM, SIG_IGN); // bo qua SIGTERM
printf("PID=%d, thu 'kill %d' -- toi se lo di\n", getpid(), getpid());
while (1) pause(); // ngu cho signal
return 0;
}
# Terminal 1:
$ gcc stubborn.c -o stubborn && ./stubborn
PID=6200, thu 'kill 6200' -- toi se lo di
# Terminal 2:
$ kill 6200 # SIGTERM -> bi bo qua, tien trinh VAN SONG
$ ps -p 6200 # van con day
$ kill -9 6200 # SIGKILL -> khong the bo qua -> chet ngay
$ ps -p 6200 # da bien mat
Đây là bằng chứng sống cho luận điểm bài 04: SIGTERM bắt/bỏ qua được, SIGKILL thì không — luôn là van cuối cùng.
🎓 Mở rộng
- Quan sát orphan reparenting: sửa
zombie.ccho consleep(30)còn cha_exit(0)ngay. Trong 30s, chạyps -o pid,ppid,comm -p <child>— bạn sẽ thấy PPID của con đổi thành1(hoặc PID của subreaper gần nhất trên hệ systemd) khi init nhận nuôi. Đây là orphan tự lành ở bài 03. - Đếm zombie hệ thống:
ps -el | awk '$2 == "Z"'liệt kê mọi zombie đang có. Trên máy khoẻ, con số này gần 0. - Graceful shutdown thật: viết lại
stubborn.cnhưng bắtSIGTERMđể in "cleaning up..." rồi thoát (pattern bài 04). Gửikill <pid>và xem nó dừng sạch thay vì bị bỏ qua. /proclà cửa sổ vào PCB: chạycat /proc/<pid>/statuscho một tiến trình của bạn — bạn đang đọc chính các field PCB (State, PPid, VmRSS...) mà bài 01 mô tả.
✨ Điều bạn vừa làm được
Bạn vừa chuyển từ đọc về tiến trình sang thao tác trên chúng:
- Đọc cây tiến trình thật: truy vết chuỗi cha-con từ shell của bạn lên tới PID 1 — thấy tận mắt "mọi tiến trình có một tổ tiên chung là init".
- Tạo và dọn zombie: dựng đúng điều kiện sinh zombie (con chết, cha không wait), nhận diện bằng
Z/<defunct>, và dọn bằng cách kill cha — không phí công kill xác. - Phân biệt SIGTERM/SIGKILL bằng thực nghiệm: chứng minh rằng một tiến trình phớt lờ được
SIGTERMnhưng không thể chốngSIGKILL.
Kỹ năng này — đọc trạng thái tiến trình và tác động đúng cách — là thứ bạn dùng mỗi lần debug một service treo, một container không chịu tắt, hay một máy đầy zombie trong production.
Bài tiếp theo: Tổng kết module — Tiến trình
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