Hướng dẫn

Tiến trình trong Linux: ps, top, htop, kill, tín hiệu và nice

Cập nhật: · Skillaria.Top

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, %MEMtỷ lệ sử dụng bộ xử lý và bộ nhớ vật lý
VSZbộ nhớ ảo tính bằng KB (kể cả phần chưa nạp vào RAM)
RSSbộ nhớ vật lý thực sự đang chiếm, tính bằng KB
STATtrạng thái tiến trình (R, S, D, Z, T) và các cờ
TIMEtổ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ế
RRunningđang chạy hoặc đang xếp hàng chờ CPU
SInterruptible 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
DUninterruptible sleepđang chờ I/O bên trong kernel, không nhận được tín hiệu
ZZombieđã kết thúc nhưng tiến trình cha chưa thu nhận mã thoát
TStoppedbị 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ímTác dụng
P / Msắp xếp theo CPU / theo bộ nhớ
1hiển thị riêng từng nhân CPU
chiển thị đầy đủ dòng lệnh
ulọc theo người dùng
kgửi tín hiệu tới tiến trình
rđổi nice của tiến trình
Hhiển thị các luồng
qthoá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.

  • F3 hoặ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;
  • F5 hoặc t — 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ệuSốMục đích
SIGHUP1mấ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
SIGINT2ngắt, tương đương Ctrl+C
SIGKILL9kết thúc cưỡng bức do kernel thực hiện, không thể bắt được
SIGTERM15yêu cầu kết thúc một cách êm đẹp; mặc định của kill
SIGSTOP19tạm dừng, không thể bắt được
SIGCONT18tiếp tục tiến trình đang tạm dừng
SIGTSTP20tạ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à:

  1. Gửi kill PID (SIGTERM).
  2. Chờ 5–30 giây, kiểm tra bằng kill -0 PID.
  3. Chỉ khi tiến trình vẫn còn sống và không ở trạng thái D thì mới dùng kill -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.

Học chủ đề này qua video

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