Tiến trình trong Linux: ps, top, htop, kill, tín hiệu và nice
Xem tiến trình Linux bằng ps aux (ảnh chụp tại một thời điểm), top và htop (danh sách cập nhật liên tục kèm tải hệ thống). Kết thúc tiến trình bằng kill PID, mặc định gửi SIGTERM để tiến trình tự thoát êm; kill -9 (SIGKILL) chỉ dùng khi tiến trình không phản hồi. Mức ưu tiên được đặt bằng nice khi khởi chạy và đổi bằng renice cho tiến trình đang chạy, trong khoảng từ -20 đến 19.
Mọi chương trình đang chạy trong Linux đều là một tiến trình, có số PID riêng, chủ sở hữu, trạng thái và mức ưu tiên. Có thể xem tiến trình bằng ps, top và htop, kết thúc chúng bằng kill, và thay đổi mức ưu tiên bằng nice và renice. Dưới đây giải thích cách đọc đầu ra của các lệnh này, nên gửi tín hiệu nào trong từng trường hợp, và vì sao kill -9 không nên là phản xạ đầu tiên.
Tiến trình và PID là gì
Tiến trình là một chương trình đang thực thi: mã lệnh, bộ nhớ, các file đang mở và một tập thuộc tính. Kernel cấp cho mỗi tiến trình một định danh PID duy nhất, đồng thời ghi nhớ PPID, tức số hiệu của tiến trình cha đã khởi chạy nó. Tiến trình đầu tiên của hệ thống có PID 1, thường là systemd, và nó trở thành «tổ tiên» của mọi tiến trình khác.
Một tiến trình mới ra đời qua hai bước. Trước hết, tiến trình cha gọi fork() để tạo bản sao của chính nó; sau đó bản sao gọi execve() để thay mã của mình bằng mã của chương trình mới. Khi bạn gõ ls trong terminal, bash làm đúng như vậy rồi chờ tiến trình con kết thúc qua wait().
$ echo $$ # PID của shell hiện tại
4182
$ ps -o pid,ppid,cmd -p $$
PID PPID CMD
4182 4170 -bash
Xem tiến trình bằng lệnh ps
ps chỉ chụp một ảnh tại thời điểm chạy: hiển thị các tiến trình đúng tại thời điểm đó rồi thoát. Khi không có tham số, nó chỉ in các tiến trình của terminal hiện tại, nên trong thực tế hầu như luôn dùng một trong hai cách sau.
$ ps aux | head -5
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.3 168540 12780 ? Ss 09:12 0:03 /sbin/init
root 2 0.0 0.0 0 0 ? S 09:12 0:00 [kthreadd]
www-data 1532 1.2 2.1 412308 86420 ? S 09:15 1:47 php-fpm: pool www
postgres 1610 0.4 3.8 221004 154312 ? Ss 09:15 0:31 postgres: checkpointer
Ở đây a là tiến trình của mọi người dùng, u là định dạng chi tiết kèm chủ sở hữu, x là gồm cả tiến trình không gắn với terminal (daemon). Cú pháp kiểu System V là ps -ef: nó hiển thị PPID, tiện khi cần biết ai đã khởi chạy ai.
| Cột | Ý nghĩa |
|---|---|
PID | định danh tiến trình |
%CPU, %MEM | tỷ lệ sử dụng bộ xử lý và bộ nhớ vật lý |
VSZ | bộ nhớ ảo tính bằng KB (kể cả phần chưa nạp vào RAM) |
RSS | bộ nhớ vật lý thực sự đang chiếm, tính bằng KB |
STAT | trạng thái tiến trình (R, S, D, Z, T) và các cờ |
TIME | tổng thời gian CPU đã dùng, không phải thời gian đã chạy |
Một số mẹo hữu ích với ps
# top 10 theo bộ nhớ
ps aux --sort=-%mem | head -11
# top 10 theo CPU
ps aux --sort=-%cpu | head -11
# chọn các cột tùy ý
ps -eo pid,ppid,user,ni,stat,etime,cmd
# cây tiến trình
ps -ef --forest
pstree -p
# tìm PID theo tên
pgrep -a nginx
pidof nginx
Lỗi kinh điển là tìm tiến trình bằng ps aux | grep nginx rồi thấy chính lệnh grep trong kết quả. Hãy dùng pgrep hoặc mẹo grep [n]ginx, vì mẫu này không khớp với chính dòng lệnh của nó.
Trạng thái tiến trình: R, S, D, Z, T
Ký tự trong cột STAT không phải là thông tin thừa mà phản ánh trạng thái thực của tiến trình. Dựa vào nó để hiểu tải hệ thống và xác định liệu kill có tác dụng hay không.
| Mã | Trạng thái | Ý nghĩa thực tế |
|---|---|---|
R | Running | đang chạy hoặc đang xếp hàng chờ CPU |
S | Interruptible sleep | đang chờ một sự kiện (mạng, nhập liệu, bộ hẹn giờ); là trạng thái thường gặp nhất của đa số tiến trình |
D | Uninterruptible sleep | đang chờ I/O bên trong kernel, không nhận được tín hiệu |
Z | Zombie | đã kết thúc nhưng tiến trình cha chưa thu nhận mã thoát |
T | Stopped | bị tạm dừng bởi SIGSTOP hoặc Ctrl+Z |
Các ký tự phụ phía sau cũng hữu ích: s — leader của phiên, + — tiến trình nền trước trên terminal, < — ưu tiên cao, N — ưu tiên thấp, l — đa luồng.
Tiến trình ở trạng thái D
Tiến trình ở D đang nằm trong một lời gọi hệ thống và chờ, chẳng hạn, phản hồi từ ổ đĩa hoặc từ NFS đang treo. Nó không nhận bất kỳ tín hiệu nào, kể cả SIGKILL. Vì vậy kill -9 vô tác dụng ở đây: cần tìm nguyên nhân phải chờ, xem iostat, dmesg và tình trạng của kho lưu trữ mạng.
Tiến trình zombie
Zombie đã chết rồi: nó không dùng CPU hay bộ nhớ, chỉ chiếm một dòng trong bảng tiến trình. Không thể giết nó vì nó đã kết thúc. Nó sẽ biến mất khi tiến trình cha gọi wait(), hoặc khi chính tiến trình cha kết thúc và zombie được PID 1 nhận làm con nuôi.
# tìm zombie và tiến trình cha của chúng
ps -eo pid,ppid,stat,cmd | awk '$3 ~ /Z/'
Một zombie đơn lẻ không gây hại. Nếu có hàng trăm và số lượng tiếp tục tăng, đó là lỗi trong chương trình cha: cần sửa hoặc khởi động lại nó.
top: tiến trình và tải hệ thống theo thời gian thực
top hiển thị cùng danh sách, nhưng cập nhật vài giây một lần và có thêm phần tóm tắt hệ thống. Đây là lệnh đầu tiên được chạy khi «máy chủ chậm».
top - 14:21:07 up 12 days, 3:02, 2 users, load average: 1.84, 1.20, 0.95
Tasks: 213 total, 2 running, 211 sleeping, 0 stopped, 0 zombie
%Cpu(s): 23.1 us, 4.2 sy, 0.0 ni, 70.3 id, 1.9 wa, 0.0 hi, 0.5 si, 0.0 st
MiB Mem : 7821.4 total, 412.6 free, 3105.8 used, 4303.0 buff/cache
MiB Swap: 2048.0 total, 2048.0 free, 0.0 used. 4398.2 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1532 www-data 20 0 412308 86420 14212 R 38.2 1.1 1:47.31 php-fpm
1610 postgres 20 0 221004 154312 148900 S 6.0 1.9 0:31.08 postgres
Quan trọng nhất là dòng %Cpu(s): us — mã người dùng, sy — kernel, id — nhàn rỗi, wa — chờ đĩa. Giá trị wa cao trong khi CPU còn rảnh là dấu hiệu rõ ràng rằng nút thắt nằm ở I/O.
Phím tắt trong top
| Phím | Tác dụng |
|---|---|
P / M | sắp xếp theo CPU / theo bộ nhớ |
1 | hiển thị riêng từng nhân CPU |
c | hiển thị đầy đủ dòng lệnh |
u | lọc theo người dùng |
k | gửi tín hiệu tới tiến trình |
r | đổi nice của tiến trình |
H | hiển thị các luồng |
q | thoát |
Với script và log có chế độ batch: top -b -n 1 | head -20 in một ảnh chụp mà không cần tương tác. Để theo dõi một tiến trình cụ thể dùng top -p 1532.
Load Average là hàng đợi, không phải phần trăm
Ba số load average là độ dài trung bình của hàng đợi trong 1, 5 và 15 phút. Trong Linux, hàng đợi này gồm các tiến trình ở trạng thái R và D, còn trạng thái ngủ bình thường S thì không. Muốn đánh giá LA cần so với số nhân (nproc): với 4 nhân, giá trị 4 là tải tối đa, còn 12 nghĩa là hàng đợi gấp ba lần khả năng xử lý.
Nếu LA cao mà CPU vẫn nhàn rỗi, hãy tìm các tiến trình ở D: ps -eo stat,pid,cmd | grep '^D'. Gần như luôn là đĩa chậm hoặc kho lưu trữ mạng.
htop: lựa chọn thay thế top dễ dùng hơn
htop hiển thị các thông tin tương tự nhưng có thanh chỉ thị màu theo từng nhân, cuộn trang, hỗ trợ chuột và cây tiến trình. Thường cần cài thêm: sudo apt install htop hoặc sudo dnf install htop.
F3hoặc/— tìm tiến trình theo tên;F4— bộ lọc, chỉ giữ lại các kết quả khớp;F5hoặct— cây «cha — con»;F6— chọn cột sắp xếp;F7/F8— giảm hoặc tăng nice;F9— menu tín hiệu cho tiến trình được chọn;Space— đánh dấu nhiều tiến trình để thao tác hàng loạt.
Trên máy chủ không cho phép cài thêm phần mềm, top là đủ: mọi thao tác chính đều có sẵn trong nó.
Cách kết thúc tiến trình: kill và tín hiệu
Lệnh kill không thực sự «giết» tiến trình mà gửi cho nó một tín hiệu. Điều gì xảy ra tiếp theo phụ thuộc vào tín hiệu và cách chương trình xử lý nó.
| Tín hiệu | Số | Mục đích |
|---|---|---|
SIGHUP | 1 | mất kết nối terminal; nhiều daemon tải lại cấu hình khi nhận tín hiệu này |
SIGINT | 2 | ngắt, tương đương Ctrl+C |
SIGKILL | 9 | kết thúc cưỡng bức do kernel thực hiện, không thể bắt được |
SIGTERM | 15 | yêu cầu kết thúc một cách êm đẹp; mặc định của kill |
SIGSTOP | 19 | tạm dừng, không thể bắt được |
SIGCONT | 18 | tiếp tục tiến trình đang tạm dừng |
SIGTSTP | 20 | tạm dừng từ terminal, Ctrl+Z |
Số hiệu ở trên áp dụng cho x86 và ARM; danh sách đầy đủ hiển thị bằng kill -l. Viết tên tín hiệu an toàn hơn viết số.
kill 1532 # SIGTERM
kill -TERM 1532 # giống hệt, viết rõ tên
kill -HUP $(pidof nginx) # tải lại cấu hình
kill -STOP 1532; kill -CONT 1532
kill -0 1532 && echo "tiến trình còn sống"
Tín hiệu 0 không gửi gì cả, chỉ kiểm tra xem tiến trình có tồn tại và bạn có quyền gửi tín hiệu tới nó hay không, rất tiện trong script.
Vì sao không nên bắt đầu bằng kill -9
SIGTERM cho chương trình cơ hội đóng file, ghi nốt bộ đệm, xóa file lock và kết thúc giao dịch đúng cách. SIGKILL cắt ngang tức thì: dữ liệu đang ghi bị mất, file tạm còn lại, còn cơ sở dữ liệu có thể phải khôi phục khi khởi động lại.
Trình tự đúng là:
- Gửi
kill PID(SIGTERM). - Chờ 5–30 giây, kiểm tra bằng
kill -0 PID. - Chỉ khi tiến trình vẫn còn sống và không ở trạng thái
Dthì mới dùngkill -9 PID.
Với dịch vụ chạy dưới systemd, tốt nhất không thao tác PID thủ công: systemctl stop tự gửi SIGTERM, chờ hết thời gian quy định rồi mới gửi SIGKILL.
pkill và killall: theo tên
pkill -f "python worker.py" # theo toàn bộ dòng lệnh
pkill -u alice # mọi tiến trình của người dùng
killall -TERM node # mọi tiến trình có tên node
pgrep -l -f worker # kiểm tra trước những gì sẽ bị khớp
Trước khi chạy pkill hàng loạt, hãy luôn chạy pgrep với cùng mẫu: -f khớp với bất kỳ phần nào của dòng lệnh nên dễ bắt nhầm tiến trình khác.
Chỉ có thể gửi tín hiệu tới tiến trình của chính mình; với tiến trình của người khác cần sudo. Thông báo Operation not permitted chính là trường hợp này.
Mức ưu tiên tiến trình: nice và renice
Giá trị nice gợi ý cho bộ lập lịch biết tiến trình nào nên nhường CPU. Phạm vi từ -20 (ưu tiên cao nhất) đến 19 (nhường tất cả), mặc định là 0. Trong top, giá trị này hiển thị ở cột NI, còn PR là mức ưu tiên nội bộ được suy ra từ nó.
# chạy một tác vụ nặng với mức ưu tiên thấp
nice -n 10 tar czf backup.tar.gz /var/www
# hạ mức ưu tiên của tiến trình đang chạy
renice -n 15 -p 1532
# nâng mức ưu tiên (chỉ root)
sudo renice -n -5 -p 1610
# mọi tiến trình của người dùng
sudo renice -n 10 -u alice
Người dùng thường chỉ có thể hạ mức ưu tiên các tiến trình của chính mình; giá trị âm chỉ dành cho root. Cần hiểu giới hạn của nice: nó chỉ ảnh hưởng đến CPU. Nếu tác vụ làm nghẽn hệ thống qua đĩa, dùng ionice -c3 lệnh — lớp idle, chỉ nhận đĩa khi không ai khác cần.
Tiến trình nền và công việc của shell
Có thể chạy lệnh ở nền bằng cách thêm &, hoặc tạm dừng bằng Ctrl+Z. Shell giữ danh sách các công việc và quản lý chúng bằng các lệnh tích hợp.
$ long_task &
[1] 5120
$ jobs
[1]+ Running long_task &
$ fg %1 # đưa lên nền trước
^Z # tạm dừng (SIGTSTP)
$ bg %1 # tiếp tục ở nền
Khi đóng terminal, shell gửi SIGHUP cho các công việc và chúng sẽ kết thúc. Để tiến trình sống sót sau khi thoát, hãy chạy bằng nohup lệnh &, tách khỏi shell bằng disown, hoặc dùng tmux. Với công việc chạy lâu dài, nên khai báo nó thành một dịch vụ systemd.
Các tình huống chẩn đoán thường gặp
Ai đang ngốn CPU
top -o %CPU # hoặc nhấn P trong top
ps -eo pid,user,%cpu,etime,cmd --sort=-%cpu | head
Nếu một tiến trình ngốn CPU mà không đem lại lợi ích, hãy hạ mức ưu tiên của nó bằng renice trước — cách này an toàn và cho bạn thời gian tìm hiểu.
Ai đang chiếm cổng
sudo ss -ltnp | grep :8080
sudo lsof -i :8080
Đầu ra sẽ có PID; từ PID đó có thể tra trong /proc/PID/ để tìm file thực thi (exe), thư mục làm việc (cwd) và tham số (cmdline).
Tiến trình biến mất bất ngờ
Nguyên nhân thường gặp là OOM Killer, bộ phận kernel giết tiến trình khi hết bộ nhớ. Kiểm tra bằng dmesg -T | grep -i oom hoặc journalctl -k | grep -i "killed process".
Tổng kết
Để làm việc với tiến trình trong Linux, chỉ cần một bộ lệnh nhỏ: ps aux để chụp ảnh, top và htop để theo dõi, kill với đúng tín hiệu để kết thúc, và nice/renice để đặt ưu tiên. Hãy đọc trạng thái tiến trình trước khi hành động: zombie và tiến trình ở D không chữa được bằng tín hiệu. Và bắt đầu với SIGTERM, chỉ dùng kill -9 khi không còn lựa chọn nào khác. Tiến trình, bộ nhớ, Load Average và các lời gọi hệ thống được giải thích chi tiết hơn trong khóa học Linux.
Câu hỏi thường gặp
ps aux khác ps -ef ở điểm nào?
Đó là hai cú pháp của cùng một lệnh: aux theo kiểu BSD với các cột %CPU, %MEM và STAT, còn -ef theo kiểu System V với cột PPID. Trong cả hai trường hợp, tập tiến trình hiển thị là như nhau: toàn bộ tiến trình của hệ thống.
Làm sao biết hệ thống đang chạy bao nhiêu tiến trình?
Cách đơn giản nhất là xem dòng Tasks trong top, hoặc chạy ps -e --no-headers | wc -l. Số luồng được hiển thị bằng ps -eLf --no-headers | wc -l.
Vì sao tiến trình vẫn không dừng dù đã kill -9?
Hoặc nó đang ở trạng thái D và chờ I/O trong kernel, hoặc nó là zombie đã kết thúc rồi. Trường hợp đầu cần tìm vấn đề ở đĩa hoặc kho lưu trữ mạng; trường hợp sau cần sửa hoặc khởi động lại tiến trình cha.
Điều gì xảy ra nếu kill tiến trình PID 1?
Kernel bảo vệ init: các tín hiệu thông thường, kể cả SIGKILL từ người dùng, không được chuyển tới nó nếu nó không tự xử lý. Nếu init vẫn kết thúc, hệ thống sẽ rơi vào kernel panic.
Làm sao giới hạn bộ nhớ mà một tiến trình được dùng?
Với dịch vụ systemd, đặt MemoryMax= trong file unit; với lần chạy tạm thời, dùng systemd-run --scope -p MemoryMax=500M lệnh. Cách này dựa trên cgroups và đáng tin cậy hơn ulimit -v.
Chủ đề này được giảng chi tiết kèm thực hành trong khóa học “Linux: с нуля до уверенного администрирования”.
Đến khóa học