Bài 40.426 phút đọc
wait, zombie, và orphan
Sau bài này bạn sẽ làm được
- Đọc mã thoát bằng các macro WIF
- Giải thích zombie sinh ra thế nào và ba cách tránh
- Giải thích orphan được nhận nuôi bởi PID 1
- Dùng WNOHANG để chờ không chặn
Một tiến trình con kết thúc không biến mất ngay: nó để lại mã thoát chờ cha thu về. Nếu cha không thu, con thành zombie. Nếu cha chết trước, con thành orphan. Hai trạng thái này là chỗ nhiều server thật rò rỉ.
#wait và mã thoát
Đọc mã thoát của con
#include <sys/wait.h>
pid_t wait(int *status); /* cho BAT KY con nao */
pid_t waitpid(pid_t pid, int *status, int flags); /* cho con CU THE */
/* status khong phai la ma thoat truc tiep; no dong goi nhieu
thong tin. Dung cac MACRO de doc: */
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status))
printf("Thoat binh thuong, ma %d\n", WEXITSTATUS(status));
if (WIFSIGNALED(status))
printf("Bi giet boi tin hieu %d\n", WTERMSIG(status));
if (WIFSTOPPED(status))
printf("Bi dung boi tin hieu %d\n", WSTOPSIG(status));| Macro | Nghĩa |
|---|---|
| WIFEXITED(status) | Con thoát bình thường (return hoặc exit) |
| WEXITSTATUS(status) | Mã thoát, chỉ dùng khi WIFEXITED đúng |
| WIFSIGNALED(status) | Con bị một tín hiệu giết |
| WTERMSIG(status) | Số tín hiệu đã giết con |
| WIFSTOPPED(status) | Con bị dừng, không phải chết |
terminal
./wait_demo
con 1 thoat binh thuong, ma 42 con 2 bi giet boi tin hieu 11 (SIGSEGV)
#Zombie
Zombie process
Một tiến trình con đã kết thúc nhưng cha chưa gọi
wait. Nhân giữ lại một mục nhỏ trong bảng tiến trình chứa mã thoát, chờ cha thu về. Mục đó là zombie: không chạy, không tốn bộ nhớ, nhưng chiếm một ô trong bảng tiến trình.Tạo zombie có chủ ý
if (fork() == 0) {
_exit(0); /* con ket thuc NGAY */
}
sleep(100); /* cha khong wait -> con thanh <defunct> */
/* Trong 100 giay nay, con da chet nhung cha chua wait, nen con
la ZOMBIE. */terminal
./tao_zombie &
[1] 25610
ps aux | grep defunct
an 25611 0.0 0.0 0 0 Z [tao_zombie] <defunct>
#Orphan
Orphan process
Một tiến trình con mà cha chết trước. Nhân giao con cho tiến trình
init hoặc systemd (PID 1) nhận nuôi. Con vẫn chạy bình thường; PPID của nó đổi thành 1. Đây là cách tạo một daemon.Tạo orphan
pid_t pid = fork();
if (pid == 0) {
/* con: ngu mot chut de cha kip chet truoc */
printf("con: PID=%d, PPID luc dau=%d\n", getpid(), getppid());
sleep(2);
printf("con: PPID gio=%d (da doi thanh 1)\n", getppid());
_exit(0);
} else {
/* cha: thoat NGAY, bo con lai */
printf("cha: PID=%d, thoat ngay\n", getpid());
_exit(0);
}terminal
./tao_orphan
cha: PID=25701, thoat ngay con: PID=25702, PPID luc dau=25701 con: PPID gio=1 (da doi thanh 1)
| Zombie | Orphan | |
|---|---|---|
| Ai chết trước | Con | Cha |
| Con còn chạy không | Không, đã chết | Có, vẫn chạy |
| Vấn đề hay cơ chế | Vấn đề: cạn bảng tiến trình | Cơ chế: tạo daemon |
| Ai dọn | Cha phải wait | PID 1 tự dọn |
| PPID | Của cha thật | Đổi thành 1 |
#Chờ không chặn
WNOHANG: kiểm tra mà không đợi
/* wait va waitpid CHAN cho toi khi con xong. Doi khi ban muon
KIEM TRA con xong chua ma khong doi. Dung co WNOHANG: */
pid_t p = waitpid(pid, &status, WNOHANG);
if (p == 0) {
/* con CHUA xong, waitpid tra ve 0 ngay lap tuc */
} else if (p == pid) {
/* con da xong, status co ket qua */
} else if (p < 0) {
/* loi, hoac khong con con nao */
}Chờ chặn trong vòng lặp phục vụ
for (;;) {
int fd = accept(sv, ...);
if (fork() == 0) { phuc_vu(fd); _exit(0); }
close(fd);
wait(NULL); /* CHAN: server dung cho toi khi con xong
-> khong phuc vu duoc client tiep theo */
}
WNOHANG
signal(SIGCHLD, reap); /* don zombie tu dong, xem tren */
for (;;) {
int fd = accept(sv, ...);
if (fork() == 0) { phuc_vu(fd); _exit(0); }
close(fd);
/* KHONG wait o day: server quay lai accept ngay.
reap() lo viec thu hoach con qua SIGCHLD. */
}
Tự làm thử
- Viết chương trình mà con thoát với mã 42 và cha in mã đó bằng
WEXITSTATUS. - Cho con gọi
abortvà dùngWIFSIGNALEDđể phát hiện nó bị tín hiệu giết. - Tạo zombie có chủ ý, quan sát bằng
ps, rồi sửa bằng cả ba cách. - Tạo orphan và quan sát PPID đổi thành 1.
- Dùng
WNOHANGtrong vòng lặp làm việc khác, thu hoạch con khi rảnh. - Viết một server nhỏ fork mỗi client và dùng
reapđể không tạo zombie sau nghìn kết nối.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Đọc mã thoát bằng macro; luôn kiểm tra
WIFEXITEDtrước khi đọcWEXITSTATUS. - Zombie: con chết, cha chưa
wait; tích tụ làm cạn bảng tiến trình. Tránh bằngwait,SIG_IGN, hoặcreap. - Orphan: cha chết trước, PID 1 nhận nuôi và tự dọn; là cơ chế tạo daemon.
WNOHANGcho phép kiểm tra con xong chưa mà không đợi, dùng trong vòng lặp phục vụ.- Server không nên
waitchặn trong vòng lặp; dùngreapquaSIGCHLDđể quay lại phục vụ ngay.