Bỏ qua điều hướng, tới nội dung chính
Học C
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));
MacroNghĩ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)
ZombieOrphan
Ai chết trướcConCha
Con còn chạy khôngKhông, đã chếtCó, vẫn chạy
Vấn đề hay cơ chếVấn đề: cạn bảng tiến trìnhCơ chế: tạo daemon
Ai dọnCha phải waitPID 1 tự dọn
PPIDCủ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ử

  1. Viết chương trình mà con thoát với mã 42 và cha in mã đó bằng WEXITSTATUS.
  2. Cho con gọi abort và dùng WIFSIGNALED để phát hiện nó bị tín hiệu giết.
  3. Tạo zombie có chủ ý, quan sát bằng ps, rồi sửa bằng cả ba cách.
  4. Tạo orphan và quan sát PPID đổi thành 1.
  5. Dùng WNOHANG trong vòng lặp làm việc khác, thu hoạch con khi rảnh.
  6. 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 WIFEXITED trước khi đọc WEXITSTATUS.
  • Zombie: con chết, cha chưa wait; tích tụ làm cạn bảng tiến trình. Tránh bằng wait, SIG_IGN, hoặc reap.
  • Orphan: cha chết trước, PID 1 nhận nuôi và tự dọn; là cơ chế tạo daemon.
  • WNOHANG cho 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 wait chặn trong vòng lặp; dùng reap qua SIGCHLD để quay lại phục vụ ngay.