Chương 06 — Process & Job control

Mỗi lệnh bạn gõ là một process. Chương này dạy bạn nhìn thấy chúng, điều khiển chúng, dừng chúng đúng cách — và chạy nhiều việc song song mà không cần mở hàng tá tab Terminal.

1. Process là gì?

Khi bạn gõ ls, hệ điều hành tạo ra một process để chạy chương trình ls. Mỗi process có một số định danh duy nhất gọi là PID (Process ID). Khi process kết thúc, PID đó được giải phóng và có thể tái sử dụng cho process khác.

Parent & Child — quan hệ cây

Process không tự nhiên xuất hiện. Một process được sinh ra bởi một parent process thông qua hai bước cốt lõi của Unix:

Vì vậy mọi process đều có PPID (Parent PID). Toàn hệ thống là một cây process, gốc là launchd (macOS) hoặc systemd (Linux) với PID = 1.

Foreground vs Background

LoạiĐặc điểmVí dụ
ForegroundChiếm terminal, bạn phải đợi nó xong mới gõ tiếp đượcls, vim file.txt
BackgroundChạy ngầm, terminal trả prompt ngay để gõ lệnh khácsleep 100 &
📘 Mỗi terminal tab = một shell process
Khi bạn mở tab Terminal mới, macOS tạo một process zsh (hoặc bash). Mọi lệnh bạn gõ trong tab đó đều là child của shell này. Đóng tab → shell chết → mọi child cũng chết theo (trừ khi bạn dùng nohup / disown).

2. ps — list process

Lệnh ps in ra danh sách process đang chạy. Nó có nhiều phong cách flag (BSD vs SysV) hơi rối, nhưng 2 cách phổ biến nhất:

$ ps                  # chỉ process của shell hiện tại — ngắn
$ ps aux              # BSD style — TẤT CẢ process của mọi user (phổ biến nhất)
$ ps -ef              # SysV style — tương tự, hiển thị PPID rõ hơn

Đọc cột của ps aux

USER       PID  %CPU  %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
huy        523   2.1   0.4  4789012  65432   ??  S    Mon09AM   0:12.45 /Applications/Firefox.app/...
huy       1042   0.0   0.1   408765   1234   s001 S+   10:33AM   0:00.04 -zsh
huy       2891  88.7   3.2  1234567 524288   ??  R    11:02AM   2:14.33 node server.js
CộtÝ nghĩa
USERChủ process
PIDProcess ID — dùng để kill
%CPU% CPU đang dùng (snapshot)
%MEM% RAM đang dùng
STATTrạng thái: R=Running, S=Sleeping, Z=Zombie, T=Stopped
COMMANDLệnh đầy đủ đã chạy

Kết hợp với grep — siêu hữu ích

$ ps aux | grep node       # tìm mọi process có chữ "node"
$ ps aux | grep -i chrome   # không phân biệt hoa thường
$ ps aux | grep -v grep      # bỏ đi dòng grep tự match chính nó

Mánh kinh điển: ps aux | grep [n]ode — dấu ngoặc [] khiến chính grep không match chính nó (vì pattern [n]ode không khớp với chuỗi [n]ode trong dòng grep).

3. top, htop — xem realtime

ps chỉ là snapshot 1 thời điểm. Để xem realtime, dùng top (có sẵn) hoặc htop (đẹp hơn nhiều, phải cài).

top — có sẵn trên macOS

$ top                    # mở giao diện, q để thoát
$ top -o cpu               # sort theo CPU (macOS)
$ top -o mem               # sort theo memory

Trong top đang chạy, gõ phím:

htop — phiên bản đẹp & tương tác

$ brew install htop      # cài 1 lần
$ htop                   # mở

htop có màu, scroll được, click chuột được, kill process bằng phím F9. Một khi đã thử là không muốn quay lại top.

So sánh ngắn

Tiêu chítophtop
Có sẵnCó (mặc định)Phải brew install
Màu sắcĐơn sắcCó màu rõ ràng
Scroll danh sáchKhôngCó (mũi tên / chuột)
Kill processk rồi PIDF9 chọn signal
Dễ đọc cho beginnerTrung bìnhTốt hơn nhiều

Đọc 3 dòng đầu của top

Processes: 512 total, 3 running, 509 sleeping, 2451 threads
Load Avg: 1.85, 2.12, 1.94    CPU usage: 12.4% user, 5.3% sys, 82.3% idle
PhysMem: 14G used (3G wired), 2G unused.

Usecase thật: server bỗng nhiên chậm → htop để xem process nào ngốn CPU/RAM, load avg có cao không. Đây là phản xạ debug đầu tiên.

4. kill — gửi signal

kill không chỉ "giết". Tên đầy đủ là send signal — gửi tín hiệu cho process. Tùy signal, process sẽ phản ứng khác nhau.

$ kill 2891              # gửi SIGTERM (mặc định) — yêu cầu thoát lịch sự
$ kill -15 2891          # tương đương: -15 = TERM
$ kill -9 2891           # SIGKILL — bắt buộc chết, không cứu vãn
$ kill -l                # liệt kê mọi signal

Bảng signal phổ biến

SốTênÝ nghĩaProcess có "bắt" được không?
1SIGHUPHangup — terminal đóng. Nhiều daemon dùng để reload config.
2SIGINTInterrupt — gửi khi bạn bấm Ctrl-C
9SIGKILLGiết ngay lập tức, không thương lượngKHÔNG
15SIGTERMYêu cầu thoát lịch sự (mặc định của kill)
18SIGCONTTiếp tục process đang stop
19SIGSTOPTạm dừng process (như Ctrl-Z nhưng không chặn được)KHÔNG
⚠️ kill -9 = súng lục chứ không phải dao mổ
SIGKILL giết process tức thời, không cho nó dọn dẹp: file đang ghi dở có thể hỏng, lock chưa giải phóng, transaction database chưa commit. Luôn thử kill PID (TERM) trước. Chỉ -9 khi process treo cứng và TERM không có tác dụng.

pkill & killall — kill theo tên

$ pkill node              # kill mọi process có tên chứa "node"
$ pkill -f "npm run dev"   # match cả command-line đầy đủ
$ killall Chrome           # kill mọi process tên CHÍNH XÁC là Chrome
$ killall -9 node           # force kill
💡 pkill vs killall — khác nhau ở đâu?
pkill match một phần tên (substring) — pkill node giết cả node-server. killall đòi tên khớp chính xác. Trên macOS, killall còn dùng để restart service hệ thống: killall Finder, killall Dock.

Workflow điển hình: app treo cần kill

$ ps aux | grep node          # 1. tìm PID
huy  2891  88.7  3.2  ...  node server.js
$ kill 2891                     # 2. thử lịch sự trước
$ ps aux | grep 2891           # 3. còn không?
$ kill -9 2891                  # 4. nếu còn, mới ép

5. Job control trong shell

Shell (zsh, bash) cho bạn quản lý nhiều job trong cùng một tab terminal. Đây là trick năng suất rất lớn.

Các thao tác cơ bản

Thao tácPhím / lệnhTác dụng
Chạy nềncmd &Khởi chạy trực tiếp ở background
SuspendCtrl-ZTạm dừng job đang foreground
List jobsjobsXem danh sách job của shell hiện tại
Backgroundbg %1Cho job đang stop chạy tiếp ở nền
Foregroundfg %1Mang job về foreground
Kill jobkill %1Giết job số 1 (không cần PID)

Kịch bản đầy đủ

$ sleep 100 &             # chạy ngầm ngay từ đầu
[1] 4521
$ sleep 200                # chạy foreground — terminal bị "khóa"
^Z                          # bấm Ctrl-Z → suspend
[2]+  Stopped    sleep 200
$ jobs
[1]-  Running    sleep 100 &
[2]+  Stopped    sleep 200
$ bg %2                   # đẩy job 2 vào nền chạy tiếp
$ jobs
[1]-  Running    sleep 100 &
[2]+  Running    sleep 200 &
$ fg %1                   # kéo job 1 lên foreground
$ kill %2                 # giết job 2 — dùng %N thay PID
📘 %1, %2 là job number, KHÔNG phải PID
Job number do shell quản lý, chỉ tồn tại trong shell hiện tại. PID là toàn hệ thống. Để xem cả hai cùng lúc: jobs -l.

6. nohup, disown — chạy lâu, đóng terminal vẫn sống

Mặc định, khi bạn đóng terminal, shell gửi SIGHUP cho mọi child → chúng chết. Vấn đề: bạn muốn chạy build dài 2 tiếng, không lẽ phải bật terminal cả đêm?

nohup — "no hangup"

$ nohup ./long-build.sh &
[1] 6123
nohup: ignoring input and appending output to 'nohup.out'
$ exit                       # đóng terminal — process vẫn sống tiếp

nohup bọc lệnh sao cho nó bỏ qua SIGHUP. Output được redirect vào file nohup.out (cùng folder).

disown — đã chạy rồi mới quyết định "thả"

$ ./long-build.sh &            # đã chạy nền nhưng chưa nohup
[1] 6789
$ disown -h %1                  # tách job 1 ra, không nhận SIGHUP nữa
$ exit                         # đóng terminal — job vẫn sống

Khi nào dùng cái nào?

Tình huốngDùng
Biết trước sẽ chạy lâu, muốn redirect output sạchnohup cmd &
Đã lỡ chạy, giờ mới muốn đóng terminaldisown -h %1
Cần tương tác trở lại sau khi đóng & mở terminal mớitmux (Chương 9)
💡 Production thực tế: dùng tmux
nohup & disown là giải pháp cổ điển. Nhưng usecase thực sự — chạy server, theo dõi log, gắn lại session từ máy khác — thì tmux mạnh hơn rất nhiều. Bạn sẽ học chi tiết ở Chương 9 (Tmux & SSH).

7. Thử ngay trong terminal giả lập

Một số kịch bản nên gõ tay qua một lượt:

  1. Xem process đang chạy: ps aux | head
  2. Tìm process tên "node": ps aux | grep node
  3. Khởi động sleep nền: sleep 60 &
  4. Liệt kê job: jobs
  5. Mang về foreground: fg %1
  6. Suspend bằng Ctrl-Z, sau đó bg %1
  7. Giết job: kill %1
you@mac — chuong6 practice
you@mac ~ $

8. Bài tập về nhà

Trên Terminal thật, làm tuần tự:

  1. Chạy ps aux rồi đếm có bao nhiêu process tổng cộng (gợi ý: pipe sang wc -l).
  2. Tìm 5 process tốn CPU nhất bằng ps aux | sort -k3 -nr | head -5. Đọc cột %CPUCOMMAND.
  3. Mở top hoặc htop, nhận diện process đang ngốn CPU/RAM nhiều nhất, ghi PID lại, rồi kill PID (chọn process vô hại, vd một tab Chrome bạn không cần).
  4. Chạy sleep 300 & 3 lần liên tiếp. Gõ jobs — bạn có 3 job. Dùng kill %2 để giết job số 2.
  5. Chạy sleep 200 ở foreground. Bấm Ctrl-Z. Gõ jobs, sau đó bg %1. Cuối cùng fg %1 để mang trở lại.
  6. Mở 1 tab terminal mới, chạy nohup sleep 600 &, đóng tab. Mở tab khác, dùng ps aux | grep sleep để kiểm tra process sleep 600 còn sống. Sau đó kill nó.
  7. Phân biệt: gửi kill -15kill -9 cho 2 lệnh sleep 100 & khác nhau. Xem có khác biệt gì không (gợi ý: cả hai đều chết — nhưng với app phức tạp, khác biệt rất lớn).
  8. Dùng pkill -f sleep để giết mọi process có "sleep" trong command line. So với killall sleep, kết quả có khác?
  9. Bonus: viết 1 dòng tìm và in ra PID của process Firefox (nếu có): ps aux | grep -i firefox | grep -v grep | awk '{print $2}' — đoán xem mỗi đoạn làm gì.

9. Quiz — Kiểm tra kiến thức

10. Tổng kết

Chương 7 ta sẽ học Shell scripting — gói mọi mẹo CLI bạn vừa học vào file .sh tự động chạy: vòng lặp, biến, hàm, điều kiện. Đây là bước biến bạn từ "user CLI" thành "tự viết tool".

Trước ← Chương 05: Tìm kiếm & lọc Tiếp theo Chương 07: Shell scripting →