Skip to content

Process Management & Signals — Quản Lý Tiến Trình Linux

Bạn deploy một bản cập nhật lúc 4 giờ chiều. 20 phút sau, monitoring báo memory tăng liên tục. Bạn SSH vào server, chạy htop, thấy hàng trăm process <defunct> đang nuốt tài nguyên. Đồng nghiệp hét qua Slack: "Kill -9 hết đi!". Bạn biết đó là sai — nhưng bạn có biết tại sao nó sai không?

Bài này dạy bạn hiểu process từ gốc: process là gì, kernel quản lý chúng ra sao, signal hoạt động thế nào, và tại sao kill -9 thường gây hại nhiều hơn lợi. Đây là kiến thức nền tảng mà mọi kỹ sư phải nắm vững trước khi đụng đến systemd, Docker, hay Kubernetes.

🎯 Mục tiêu

  • Hiểu process, PID, PPID và mối quan hệ parent-child trong process tree
  • Nắm vững 6 signal quan trọng nhất: SIGTERM, SIGKILL, SIGHUP, SIGINT, SIGSTOP, SIGCONT
  • Biết dùng ps, top, htop/proc filesystem để inspect process
  • Phân tích và xử lý được zombie process incident trong production
  • Tránh anti-pattern phổ biến nhất: kill -9 reflex

Process — Đơn Vị Thực Thi Của Linux

Mỗi chương trình đang chạy trên Linux là một process (tiến trình). Khi bạn gõ python myapp.py, kernel tạo ra một process mới với:

  • PID (Process ID) — số định danh duy nhất trong hệ thống tại thời điểm đó
  • PPID (Parent Process ID) — PID của process cha đã tạo ra nó
  • UID — user nào sở hữu process này
  • State — trạng thái hiện tại (Running, Sleeping, Zombie, Stopped...)

Process tree — cây gia phả của mọi tiến trình

Toàn bộ process trên Linux đều bắt nguồn từ PID 1 — tiến trình đầu tiên mà kernel khởi động (trước đây là init, ngày nay là systemd). Mọi process khác đều là con, cháu, chắt... của PID 1.

systemd (PID 1)
├── sshd (PID 500)
│   └── bash (PID 1200)
│       └── python myapp.py (PID 1201)
├── nginx (PID 600)
│   ├── nginx worker (PID 601)
│   └── nginx worker (PID 602)
└── postgres (PID 700)
    ├── postgres writer (PID 701)
    └── postgres worker (PID 702)

Điểm then chốt: khi một parent process bị kill, kernel phải quyết định số phận của tất cả child process. Đây chính là gốc rễ của nhiều production incident — và cũng là lý do systemd quan trọng (bài sau).

Xem process tree và thông tin chi tiết

bash
# Xem process tree dạng cây — thấy ngay quan hệ parent-child
pstree -p
# systemd(1)─┬─sshd(500)───bash(1200)───python(1201)
#             ├─nginx(600)─┬─nginx(601)
#             │            └─nginx(602)
#             └─postgres(700)─┬─postgres(701)
#                             └─postgres(702)

# List tất cả process với chi tiết (user, CPU, memory, command)
ps aux | head -20
# USER       PID %CPU %MEM    VSZ   RSS TTY  STAT START   TIME COMMAND
# root         1  0.0  0.3 169396 13200 ?    Ss   09:00   0:02 /usr/lib/systemd/systemd
# root       500  0.0  0.1  15432  5400 ?    Ss   09:00   0:00 /usr/sbin/sshd -D
# www-data   600  0.0  0.2  55124  8640 ?    S    09:00   0:01 nginx: master process
# deploy    1201  2.3  1.5 312400 61200 ?    Sl   14:00   0:45 python myapp.py

# Tìm process cụ thể
ps aux | grep nginx
# www-data   600  0.0  0.2  55124  8640 ?    S  09:00  0:01 nginx: master process
# www-data   601  0.1  0.3  56200  12800 ?   S  09:00  0:12 nginx: worker process
# www-data   602  0.1  0.3  56100  12600 ?   S  09:00  0:11 nginx: worker process

# Real-time monitoring
top       # có sẵn trên mọi Linux — đủ dùng cho troubleshooting nhanh
htop      # giao diện đẹp hơn, dễ dùng hơn (cài: sudo apt install htop)

Đọc thông tin process từ /proc

Mỗi process có một thư mục riêng trong /proc/PID/. Đây là "cửa sổ" mà kernel mở cho bạn nhìn vào bên trong process:

bash
# Thông tin tổng quan: tên, state, memory, threads
cat /proc/1201/status | head -10
# Name:    python
# Umask:   0022
# State:   S (sleeping)
# Tgid:    1201
# Pid:     1201
# PPid:    1200
# Threads: 4
# VmRSS:   61200 kB

# Full command line đã dùng để khởi chạy process
cat /proc/1201/cmdline | tr '\0' ' '
# python myapp.py --port 8080 --workers 4

# Danh sách file descriptor đang mở (socket, file, pipe)
ls -la /proc/1201/fd/ | head -10
# lrwx------ 1 deploy deploy 64 ... 0 -> /dev/pts/0
# lrwx------ 1 deploy deploy 64 ... 1 -> /dev/pts/0
# lrwx------ 1 deploy deploy 64 ... 2 -> /dev/pts/0
# lr-x------ 1 deploy deploy 64 ... 3 -> /home/deploy/myapp/config.yaml
# lrwx------ 1 deploy deploy 64 ... 4 -> socket:[45678]

💡 Pro Tip — Khi nào dùng ps vs top vs htop

ps aux — snapshot tĩnh, dùng khi cần pipe qua grep, awk, hoặc lưu vào log. top — real-time, có sẵn mọi nơi, dùng khi SSH vào server lạ. htop — real-time với giao diện tốt hơn, dùng khi có quyền cài package. Trong production incident, bắt đầu với top vì nó luôn có sẵn.


Signals — Ngôn Ngữ Giao Tiếp Với Process

Signal là cách kernel và các process "nói chuyện" với nhau. Khi bạn nhấn Ctrl+C trong terminal, bạn đang gửi signal SIGINT tới process đang chạy. Khi bạn gõ kill PID, bạn đang gửi signal SIGTERM.

Hiểu signal là hiểu cách Linux dừng, tạm dừng, và reload process — kỹ năng sống còn khi vận hành server.

6 signal mà mọi kỹ sư phải thuộc

SignalSốHành viUse Case
SIGTERM15Graceful shutdownMặc định khi gõ kill PID — process có cơ hội dọn dẹp
SIGKILL9Force kill ngay lập tứcPhương án cuối cùng — process KHÔNG THỂ bắt hoặc bỏ qua signal này
SIGHUP1Reload configNginx, HAProxy, syslog reload mà không cần restart
SIGINT2InterruptCtrl+C trong terminal
SIGSTOP19Pause (đóng băng) processTạm dừng để debug — process KHÔNG THỂ bỏ qua signal này
SIGCONT18Resume processTiếp tục chạy sau khi bị SIGSTOP

SIGTERM vs SIGKILL — mental model quan trọng nhất

Đây là sự khác biệt mà rất nhiều kỹ sư hiểu sai:

SIGTERM (kill PID):
  Process nhận signal
  → Chạy cleanup handlers (atexit, signal handlers)
  → Đóng kết nối database đang mở
  → Flush log buffer ra file
  → Trả HTTP response cho in-flight requests
  → Giải phóng lock files
  → Exit gracefully
  
SIGKILL (kill -9 PID):
  Kernel GIẾT process NGAY LẬP TỨC
  → Không chạy cleanup handlers
  → Kết nối DB bị bỏ rơi (connection leak)
  → Log chưa flush bị mất
  → In-flight requests mất tín hiệu
  → Lock files không được xóa
  → Temp files nằm lại trên disk
  → CÓ THỂ gây data corruption

🔥 KHÔNG BAO GIỜ dùng kill -9 làm bước đầu tiên

Luôn bắt đầu với kill PID (SIGTERM). Đợi 10-30 giây. Kiểm tra process còn sống không bằng ps -p PID. Nếu vẫn không chết, lúc đó mới dùng kill -9 PID. Dùng kill -9 ngay từ đầu giống như rút phích điện để tắt máy tính — đôi khi bạn buộc phải làm, nhưng không bao giờ nên là lựa chọn đầu tiên.

Gửi signal trong thực tế

bash
# Bước 1: Graceful stop (SIGTERM — mặc định)
kill 1201

# Bước 2: Kiểm tra process còn sống không
ps -p 1201
# Nếu không có output → process đã thoát thành công

# Bước 3: CHỈ khi SIGTERM thất bại sau 30 giây
kill -9 1201

# Reload config nginx mà KHÔNG cần restart
# (SIGHUP → nginx đọc lại config, tạo worker mới, graceful shutdown worker cũ)
kill -HUP $(cat /var/run/nginx.pid)
# Hoặc dùng systemctl (cách chuẩn hơn):
systemctl reload nginx

# Tạm dừng process để debug
kill -STOP 1201   # Process đóng băng
# ... kiểm tra memory, connections, v.v. ...
kill -CONT 1201   # Process tiếp tục chạy

# Gửi signal tới tất cả process của một user
killall -u deploy -SIGTERM
# Hoặc theo tên process
pkill -f "python myapp.py"

⚠️ Cạm bẫy

Sai lầm 1: Luôn dùng kill -9 vì "nhanh hơn"

bash
# ❌ Phản xạ nguy hiểm
kill -9 $(pgrep python)

Hậu quả thật sự:

  • Database transaction đang ghi dở → data corruption
  • Connection pool không được trả lại → các process khác hết connection
  • File lock không được giải phóng → process mới không start được
  • PID file cũ còn nằm lại → systemd nghĩ service vẫn đang chạy
bash
# ✅ Workflow đúng
kill $(pgrep -f "python myapp.py")
sleep 15
if ps -p $(pgrep -f "python myapp.py") > /dev/null 2>&1; then
    echo "SIGTERM failed, escalating to SIGKILL..."
    kill -9 $(pgrep -f "python myapp.py")
fi

Sai lầm 2: Không hiểu tại sao graceful shutdown quan trọng

Khi deploy phiên bản mới, orchestrator (systemd, Docker, K8s) gửi SIGTERM cho process cũ. Nếu app không handle SIGTERM → in-flight request bị cắt ngang → user thấy lỗi 502. Kỹ sư giỏi viết signal handler cho app của mình:

python
# Python — handle SIGTERM gracefully
import signal
import sys

def graceful_shutdown(signum, frame):
    print("Received SIGTERM, cleaning up...")
    # Đóng DB connections
    db.close()
    # Flush logs
    logger.flush()
    # Hoàn thành in-flight requests (trong web framework)
    server.shutdown(timeout=30)
    sys.exit(0)

signal.signal(signal.SIGTERM, graceful_shutdown)

/proc — Cửa Sổ Nhìn Vào Process

/proc không phải thư mục thông thường trên disk — nó là virtual filesystem mà kernel tạo ra trong RAM. Mỗi file trong /proc là một "giao diện" để bạn đọc thông tin trực tiếp từ kernel mà không cần cài bất kỳ tool nào.

Đây là X-ray machine của system administrator.

Thông tin hệ thống

bash
# CPU — model, cores, tốc độ
cat /proc/cpuinfo | grep "model name" | head -1
# model name : Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz

# Số CPU cores
grep -c "processor" /proc/cpuinfo
# 4

# Memory — tổng, free, buffers, cached
cat /proc/meminfo | head -5
# MemTotal:        8167848 kB
# MemFree:          524288 kB
# MemAvailable:    4096000 kB
# Buffers:          312400 kB
# Cached:          3200000 kB

# Load average — system đang bận cỡ nào
cat /proc/loadavg
# 0.45 0.38 0.32 1/245 1201
# (1 min) (5 min) (15 min) (running/total) (last PID)

# Uptime (giây)
cat /proc/uptime
# 864000.45 3200000.12
# (system uptime) (idle time across all cores)

Thông tin theo process

bash
# Tên, state, memory, threads của process cụ thể
cat /proc/1201/status | grep -E "^(Name|State|PPid|VmRSS|Threads)"
# Name:    python
# State:   S (sleeping)
# PPid:    1200
# VmRSS:   61200 kB    ← physical memory thực tế đang dùng
# Threads: 4

# Biến môi trường của process (hữu ích khi debug config)
cat /proc/1201/environ | tr '\0' '\n' | head -5
# HOME=/home/deploy
# PATH=/usr/local/bin:/usr/bin:/bin
# DATABASE_URL=postgres://localhost/mydb
# WORKER_COUNT=4
# LOG_LEVEL=info

# File descriptor đang mở — xem process đang giữ những file/socket nào
ls -la /proc/1201/fd/ | wc -l
# 47     ← process đang mở 47 file descriptors

# Network connections của process
cat /proc/1201/net/tcp | head -5
# (output ở dạng hex — dùng ss hoặc netstat sẽ dễ đọc hơn)

# Readable hơn:
ss -tnp | grep 1201
# ESTAB  0  0  10.0.0.5:8080  10.0.0.100:54321  users:(("python",pid=1201,fd=4))

💡 Dùng /proc thay monitoring tool

Dùng /proc/PID/status để kiểm tra memory usage của process mà KHÔNG cần cài monitoring tool. VmRSS (Resident Set Size) cho biết actual physical memory đang dùng — đây là con số bạn quan tâm nhất khi debug memory issue. VmSize là virtual memory (thường lớn hơn nhiều nhưng ít ý nghĩa hơn).


🔥 Production Incident — "Zombie Processes Ăn Hết Resource"

🔥 Real Production Incident

Tình huống

Thứ Tư, 14:30. Monitoring alert: server memory 95%. API response time tăng từ 200ms lên 3 giây. Team SSH vào server.

Điều tra

bash
# Bước 1: Kiểm tra top processes
htop
# → Không có process nào dùng nhiều CPU hay memory bất thường
# → Nhưng total process count: 847 (bình thường: ~250)

# Bước 2: Tìm thủ phạm
ps aux | grep defunct
# deploy  1234  0.0  0.0      0     0 ?   Z  14:00  0:00 [worker] <defunct>
# deploy  1235  0.0  0.0      0     0 ?   Z  14:01  0:00 [worker] <defunct>
# deploy  1236  0.0  0.0      0     0 ?   Z  14:01  0:00 [worker] <defunct>
# ... hàng trăm dòng tương tự
# State 'Z' = Zombie

# Bước 3: Đếm zombie
ps aux | awk '$8 ~ /Z/' | wc -l
# 583

Phản xạ sai (rất phổ biến)

bash
# ❌ Đồng nghiệp Junior chạy:
kill -9 1234 1235 1236 ...
# Kết quả: Không có gì xảy ra. Zombie vẫn còn.
# 5 phút sau: Thêm zombie mới xuất hiện.

Tại sao kill -9 không hoạt động?

Zombie process đã chết rồi. Chúng không còn code đang chạy, không chiếm CPU, không chiếm memory thật sự. Chúng chỉ là entry trong process table — một dòng metadata mà kernel giữ lại, chờ parent process gọi wait() để thu thập exit status.

Bạn không thể kill thứ đã chết. Đó là lý do kill -9 vô dụng với zombie.

Nguyên nhân gốc

bash
# Bước 4: Tìm parent process
ps -o ppid= -p 1234
# 1100

# Xem parent là gì
ps -p 1100 -o pid,ppid,user,command
# PID  PPID USER    COMMAND
# 1100 1    deploy  python task_scheduler.py

# Bước 5: Đọc code task_scheduler.py
# → Parent dùng subprocess.Popen() để spawn worker
# → Nhưng KHÔNG BAO GIỜ gọi .wait() hoặc .communicate()
# → Child hoàn thành → trở thành zombie → parent tiếp tục spawn thêm

Root cause code

python
# ❌ Code gây zombie — thiếu wait()
import subprocess

def run_workers():
    while True:
        tasks = get_pending_tasks()
        for task in tasks:
            # Spawn worker nhưng KHÔNG chờ kết quả
            subprocess.Popen(["python", "worker.py", task.id])
            # → worker hoàn thành → trở thành zombie
        time.sleep(60)

Fix đúng

bash
# Bước 6: Fix ngay trên production — restart parent
kill 1100   # SIGTERM parent → systemd restart nó
# Parent mới khởi động → zombie cũ được kernel dọn (re-parent về PID 1)

# Kiểm tra zombie đã biến mất
sleep 5
ps aux | awk '$8 ~ /Z/' | wc -l
# 0  ✅
python
# ✅ Code đúng — properly handle child process lifecycle
import subprocess

def run_workers():
    while True:
        tasks = get_pending_tasks()
        processes = []
        for task in tasks:
            proc = subprocess.Popen(["python", "worker.py", task.id])
            processes.append(proc)

        # Chờ TẤT CẢ workers hoàn thành
        for proc in processes:
            proc.wait()  # Thu thập exit status → không còn zombie

        time.sleep(60)

# Hoặc đơn giản hơn — dùng subprocess.run() (blocking, tự wait)
def run_worker_simple(task_id):
    result = subprocess.run(
        ["python", "worker.py", task_id],
        capture_output=True,
        timeout=300,  # 5 phút timeout
    )
    if result.returncode != 0:
        logger.error(f"Worker failed: {result.stderr}")

Bài học rút ra

  1. Zombie ≠ process đang chạy. Chúng là metadata chờ parent thu thập — kill -9 vô dụng.
  2. Fix zombie = fix parent, không phải fix child.
  3. Luôn wait() trên child process — trong Python, Go, Node, hay bất kỳ ngôn ngữ nào.
  4. Nếu parent không fix được ngay, restart parent để kernel re-parent zombie về PID 1 (systemd sẽ dọn).

Background Processes & Job Control

Khi bạn chạy lệnh qua SSH, process đó gắn với terminal session của bạn. Nếu bạn đóng SSH → process chết. Hiểu job control giúp bạn chạy task dài mà không sợ mất khi mất kết nối.

bash
# Chạy process ở background (thêm & ở cuối)
python process_reports.py &
# [1] 1500     ← job number 1, PID 1500

# Liệt kê background jobs
jobs -l
# [1]+ 1500 Running    python process_reports.py &

# Đưa job về foreground
fg %1

# Ctrl+Z → pause process (gửi SIGTSTP)
# Sau đó đưa nó ra background tiếp tục chạy
bg %1

# Chạy task dài mà không sợ mất khi đóng SSH
nohup python long_task.py > output.log 2>&1 &
# nohup: bỏ qua SIGHUP (signal gửi khi terminal đóng)
# > output.log: redirect stdout
# 2>&1: redirect stderr vào stdout
# &: chạy background

# Kiểm tra task đang chạy sau khi reconnect SSH
ps aux | grep long_task.py
# deploy  1600  5.2  2.1 ... python long_task.py
tail -f output.log   # theo dõi output real-time

💡 Screen/Tmux — giải pháp tốt hơn nohup

Trong thực tế production, dùng screen hoặc tmux thay vì nohup. Chúng cho phép bạn attach lại vào session sau khi reconnect SSH — xem output, gửi input, thậm chí chia terminal. Cài đặt: sudo apt install tmux. Cheat sheet: tmux new -s deploy → chạy lệnh → Ctrl+B, D để detach → tmux attach -t deploy để quay lại.


Bài tập nhanh

Bài 1: Đọc ps output — tìm zombie

Cho output sau:

USER       PID %CPU %MEM    VSZ   RSS TTY  STAT START   TIME COMMAND
root         1  0.0  0.3 169396 13200 ?    Ss   09:00   0:02 /usr/lib/systemd/systemd
deploy    2100  2.3  1.5 312400 61200 ?    Sl   14:00   0:45 python task_scheduler.py
deploy    2201  0.0  0.0      0     0 ?    Z    14:05   0:00 [worker] <defunct>
deploy    2202  0.0  0.0      0     0 ?    Z    14:06   0:00 [worker] <defunct>
deploy    2203  0.0  0.1  42000  4200 ?    S    14:07   0:02 python worker.py task-99
www-data  3000  0.1  0.2  55124  8640 ?    S    09:00   0:01 nginx: master process
www-data  3001  0.5  0.4  58000 16400 ?    S    09:00   0:30 nginx: worker process

Câu hỏi:

  1. Process nào là zombie? → PID 2201 và 2202 (STAT = Z, hiển thị <defunct>)
  2. Parent của zombie là ai? → Chạy ps -o ppid= -p 2201 — nhiều khả năng là PID 2100 (task_scheduler.py)
  3. kill -9 2201 có hiệu quả không? → Không. Zombie đã chết, phải fix parent.
  4. Cách xử lý đúng? → kill 2100 (SIGTERM parent) → systemd restart → zombie được dọn.

Bài 2: Chọn đúng signal

Tình huốngSignal đúng
Reload config nginx mà không gián đoạn trafficSIGHUP (kill -HUP <nginx_pid>)
Dừng một Python script đang stuck trong infinite loopSIGTERM (kill <pid>), đợi 30s, rồi SIGKILL nếu cần
Force-kill process hoàn toàn không phản hồi SIGTERMSIGKILL (kill -9 <pid>) — last resort
Tạm dừng process để kiểm tra memory snapshotSIGSTOP (kill -STOP <pid>), sau đó SIGCONT để tiếp tục

Spot the Bug 🐛

Đoạn script deploy sau có lỗi gì?

bash
#!/bin/bash
# deploy.sh — restart application

echo "Deploying new version..."
kill -9 $(cat /var/run/myapp.pid)
sleep 2
python /opt/myapp/main.py &
echo $! > /var/run/myapp.pid
echo "Deploy complete!"

Bugs:

  1. kill -9 ngay lập tức — không cho app cơ hội dọn dẹp (đóng DB, flush log, hoàn thành request). Phải dùng kill (SIGTERM) trước.
  2. sleep 2 quá ngắn — SIGTERM cần thời gian để app cleanup. Phải đợi đủ lâu (10-30s) và kiểm tra process thật sự đã thoát.
  3. Không kiểm tra process đã thoát — nếu process cũ chưa chết mà process mới đã start → port conflict, hai instance chạy song song.
  4. Không dùng systemd — quản lý PID file thủ công rất dễ sai. Hãy dùng systemd service (bài sau).

Script đúng hơn:

bash
#!/bin/bash
set -euo pipefail

PID_FILE="/var/run/myapp.pid"
APP_PID=$(cat "$PID_FILE" 2>/dev/null || echo "")

if [ -n "$APP_PID" ] && ps -p "$APP_PID" > /dev/null 2>&1; then
    echo "Sending SIGTERM to PID $APP_PID..."
    kill "$APP_PID"

    # Đợi tối đa 30 giây cho graceful shutdown
    for i in $(seq 1 30); do
        if ! ps -p "$APP_PID" > /dev/null 2>&1; then
            echo "Process exited gracefully after ${i}s"
            break
        fi
        sleep 1
    done

    # Escalate nếu vẫn chưa chết
    if ps -p "$APP_PID" > /dev/null 2>&1; then
        echo "SIGTERM failed, sending SIGKILL..."
        kill -9 "$APP_PID"
        sleep 1
    fi
fi

echo "Starting new version..."
python /opt/myapp/main.py &
echo $! > "$PID_FILE"
echo "Deploy complete! PID: $(cat $PID_FILE)"

Production Anti-Pattern — "The kill -9 Reflex"

⚠️ Cạm bẫy

Đây là anti-pattern phổ biến nhất trong quản lý process. Quy trình sai:

bash
# ❌ "Nó chậm quá, kill cho nhanh"
kill -9 $(pgrep -f "python myapp.py")

Hậu quả thực tế:

  • Database transaction đang ghi dở → data bị corrupt hoặc row bị lock vĩnh viễn
  • Connection pool không được trả lại → app mới start bị "too many connections"
  • In-flight API response mất → client nhận 502 Bad Gateway hoặc timeout
  • Lock file /var/run/myapp.pid không bị xóa → systemd nghĩ app còn chạy → không restart được
  • Temp file trên disk không bị dọn → disk đầy dần theo thời gian

Quy trình đúng — luôn là SIGTERM trước:

SIGTERM → đợi 30s → kiểm tra → (nếu vẫn sống) → SIGKILL

Trong Kubernetes, đây chính xác là điều terminationGracePeriodSeconds làm. Default: 30 giây SIGTERM, sau đó mới SIGKILL. Nếu bạn hiểu signal, bạn hiểu K8s pod lifecycle.


Best Practices Checklist

✅ Checklist triển khai

Process Management:

  • [ ] Biết phân biệt PID, PPID, và cách đọc process tree
  • [ ] Dùng ps aux cho snapshot, top/htop cho real-time monitoring
  • [ ] Biết đọc /proc/PID/status để kiểm tra memory (VmRSS), state, threads
  • [ ] Kiểm tra file descriptors đang mở bằng /proc/PID/fd/ khi debug connection leak

Signal Handling:

  • [ ] SIGTERM (graceful) → đợi → SIGKILL (force) — luôn theo thứ tự này
  • [ ] Dùng SIGHUP cho reload config (nginx, HAProxy)
  • [ ] Viết signal handler (SIGTERM) cho app của bạn — đừng phụ thuộc vào default behavior
  • [ ] Không bao giờ kill -9 làm bước đầu tiên

Zombie Prevention:

  • [ ] Luôn wait() trên child process trong code (subprocess.run, child_process.exec)
  • [ ] Fix zombie bằng cách fix parent, không phải kill zombie
  • [ ] Monitor process count — zombie tăng đột biến = bug trong parent

Job Control:

  • [ ] Dùng nohup hoặc tmux/screen cho task dài qua SSH
  • [ ] Không chạy production service bằng & — dùng systemd (bài sau)

🧠 Quiz

Câu 1: Khi gõ kill 1234 (không có flag), signal nào được gửi?

  • [ ] A. SIGKILL (9) — force kill ngay lập tức
  • [x] B. SIGTERM (15) — yêu cầu graceful shutdown
  • [ ] C. SIGHUP (1) — reload configuration
  • [ ] D. SIGINT (2) — interrupt giống Ctrl+C

💡 Giải thích: kill mặc định gửi SIGTERM (15). Process nhận signal này có cơ hội chạy cleanup handlers trước khi thoát. Chỉ khi thêm -9 mới gửi SIGKILL.

Câu 2: Zombie process là gì?

  • [ ] A. Process chiếm quá nhiều CPU và memory
  • [ ] B. Process đang chạy nhưng không phản hồi
  • [x] C. Process đã kết thúc nhưng parent chưa gọi wait() để thu thập exit status
  • [ ] D. Process bị kernel pause vì hết tài nguyên

💡 Giải thích: Zombie (state Z) là process ĐÃ CHẾT nhưng vẫn còn entry trong process table. Chúng chờ parent gọi wait(). kill -9 không có tác dụng vì chúng đã chết rồi — fix bằng cách sửa hoặc restart parent.

Câu 3: Bạn muốn reload config nginx mà KHÔNG gián đoạn traffic. Cách nào đúng?

  • [ ] A. kill -9 $(cat /var/run/nginx.pid) rồi start lại
  • [ ] B. systemctl stop nginx && systemctl start nginx
  • [x] C. kill -HUP $(cat /var/run/nginx.pid) hoặc systemctl reload nginx
  • [ ] D. kill $(cat /var/run/nginx.pid) rồi start lại

💡 Giải thích: SIGHUP (signal 1) yêu cầu nginx đọc lại config file, tạo worker mới với config mới, và gracefully shutdown worker cũ. Không có downtime. systemctl reload cũng gửi SIGHUP bên dưới.

Câu 4: Thứ tự đúng khi cần dừng một process không phản hồi?

  • [ ] A. SIGKILL → SIGTERM → SIGHUP
  • [ ] B. SIGKILL ngay lập tức vì nhanh nhất
  • [x] C. SIGTERM → đợi 10-30s → kiểm tra → SIGKILL nếu cần
  • [ ] D. SIGHUP → SIGTERM → SIGKILL

💡 Giải thích: Luôn SIGTERM trước để cho process cơ hội cleanup. Đợi đủ thời gian. Chỉ escalate lên SIGKILL khi SIGTERM thất bại. Đây cũng là quy trình mà Kubernetes áp dụng (terminationGracePeriodSeconds).