Using lsof for tracing and analyzing performance on a Linux system - Sử dụng công cụ dòng lệnh lsof cho việc truy xuất và phân tích hiệu năng hệ thống Linux
lsof (Liệt kê các tệp đang mở - List Open Files) là công cụ dòng lệnh trên Linux để hiển thị tất cả tệp, socket, pipe, hoặc thiết bị đang được mở bởi các tiến trình, lsof rất hữu ích cho việc:
- Gỡ lỗi ứng dụng.
- Kiểm tra cổng mạng.
- Phát hiện file bị khóa, v.v.
Vì lsof làm việc chặt chẽ với kernel, nên cần phải được cấu hình đúng với hiệu điều hành muốn sử dụng. Mọi thao tác trong Linux (đọc/ghi tệp, kết nối mạng, thậm chí thư mục) đều được xem là "tệp mở" và lsof giúp theo dõi chúng.
| Tùy chọn | Ý nghĩa |
|---|---|
-i |
Liệt kê các kết nối mạng (socket) đang mở (VD: lsof -i :80). |
-p |
Hiển thị tệp mở bởi tiến trình có PID chỉ định (VD: lsof -p 1234). |
-u |
Liệt kê tệp mở bởi người dùng cụ thể (VD lsof -u root). |
-c |
Lọc các tiến trình theo tên command (process name) (VD: lsof -c firefox). |
+D |
Hiển thị tệp mở trong thư mục cụ thể (VD: lsof +D /var/log). |
-t |
Chỉ xuất PID của tiến trình. |
-n |
Không phân giải hostname. |
-P |
Không phân giải service name. |
-
Cài đặt từ trình quản lý gói (package manager)
Hệ điều hành Lệnh cài Debian / Ubuntu sudo apt install lsofRHEL / CentOS sudo yum install lsofArch Linux sudo pacman -Syu lsofNixOS nix-env -i lsof -
Tự build
lsoftừ mã nguồn – nếu bản cài đặt sẵn bị lỗi hoặc thiếu tính năng- Legacy build system.
- Autotools-based build system.
Bước 1: Cài đặt các gói cần thiết
sudo apt update
sudo apt install gcc make wget tarBước 2: Tải mã nguồn lsof
mkdir ~/lsof-build
cd ~/lsof-build
wget https://github.com/lsof-org/lsof/archive/refs/tags/4.99.4.tar.gz
tar -xzf 4.99.4.tar.gz
cd lsof-4.99.4Bước 3. Biên dịch, cài đặt và kiểm tra phiên bản
./Configure linux
make
sudo make install./lsof -v
#hoặc
sudo cp ./lsof /usr/bin/
sudo chmod 755 /usr/bin/lsof
lsof -vTóm tắt ý nghĩa các cột kết quả khi thực hiện các ví dụ thực tế:
| Cột | Ý nghĩa |
|---|---|
COMMAND |
Tên tiến trình đang mở file. |
PID |
Process ID của tiến trình. |
USER |
User sở hữu tiến trình. |
FD |
File descriptor: số FD, tiến trình đang làm gì. |
TYPE |
Loại tài nguyên mà FD đang tham chiếu. |
DEVICE |
Major/minor number của thiết bị chứa file (ổ đĩa). |
SIZE/OFF |
Kích thước file hoặc offset (0 nghĩa là file trống). |
NODE |
Inode của file trên filesystem. |
NAME |
Đường dẫn tuyệt đối của file. |
-
Trường hợp 1: phân tích file đang được mở/ghi
- Tạo file và mở
touch /tmp/test.log #tạo file trống tail -f /tmp/test.log & #tail: theo dõi thời gian thực, &: chạy ngầm
- Dùng lsof tìm process đang giữ file
sudo lsof /tmp/test.log
Ta thấy:
-
COMMAND: tiến trình tail đang mở file test.log.
-
PID: mã định danh tiến trình là 22519.
-
USER: truongvo đang thực thi.
-
FD: đại diện cho tài nguyên mở (file, socket, device) mà process có thể truy cập. Mỗi FD được kernel quản lý và ánh xạ một tài nguyên cụ thể. File Descriptor thông thường số thứ tự + quyền truy cập hoặc FD đặc biệt (ví dụ như cwd, txt, mem,… Trong ví dụ FD có số thứ tự là
3,r(read-only) - tiến trình đang đọc file. Theo thiết kế Unix (1970s) thì mọi tiến trình cần 3 luồng I/O cơ bản để process có thể giao tiếp với user (terminal):- 0u (stdin): nhận input (bàn phím,…), u = read + write.
- 1u (stdout): xuất output (màn hình,….).
- 2u (stderr): xuất thông báo lỗi.
Do đó bắt đầu từ 3 trở đi mới dành cho tài nguyên do người dùng mở. Ví dụ như hình bên dưới (sudo lsof -p $$ là để xem PID của shell terminal hiện tại), một process sẽ luôn có 3 FD cơ bản và những FD mà process đó cần sử dụng:
- cwd: thư mục làm việc hiện tại /home/truongvo
- rtd: thư mục gốc là /
- txt: file đang thực thi từ /usr/bin/bash
- mem: file được nạp vào các bộ nhớ
-
TYPE: REG là regular file thường. Ngoài ra còn có các loại như:
- DIR: biết process đang ở thư mục nào.
- CHR: biết process đang dùng terminal.
- IPv4/IPv6: biết process đang nghe port gì.
- …
-
DEVICE: 8, 2 là Major/Minor của thiết bị chứa file đó, số driver ổ cứng là 8 và phân vùng thứ 2 trên ổ cứng.
-
SIZE/OFF: nếu TYPE là REG thì hiển thị kích thước file, còn TYPE là loại khác thì hiển thị Offset - vị trí đang đọc/ghi file. Trên ví dụ thì size là 0 vì file .log trống.
-
NODE: Inode number duy nhất cho mỗi file, 524361 đại diện cho ‘địa chỉ” của metadata (thông tin mô tả của file như là: quyền truy cập, chủ sở hữu, kích thước file, thời gian tạo/sửa đổi,…):
Có thể sử dụng Inode để tìm khi không nhớ tên file:
- NAME: đường dẫn tuyệt đối của file là
/tmp/test.log
-
Trường hợp 2: file đã bị gỡ nhưng vẫn đang được mở
Tình huống thông thường khi tạo rồi xóa 1 file log thì file hoàn toàn mất:
echo "init log" > app.logNhưng vẫn có trường hợp dù đã xóa file nhưng file vẫn đang được mở như sau:
Tạo log và script python để giữ file .log:
nano app.pyimport time
log = open("app.log", "a")
try:
while True:
log.write("Logging...\n")
log.flush()
time.sleep(5)
except KeyboardInterrupt:
log.close()python3 app.py & #Chạy ngầm
jobs #Kiểm tra python có chạy không
rm app.log #Remove log file
sudo lsof | grep app.log Dù đã remove file .log nhưng khi kiểm tra lại bằng lsof thì log file vẫn còn được giữ bởi process:
5760là PID của script.- FD: 3w write đang ghi vào. TYPE là REG nên là SIZE và size đang tăng dần do đang được ghi
(deleted)nghĩa là file đã xóa, nhưng process vẫn giữ file .log không những tốn tài nguyên CPU mà còn đang chiếm thêm bộ nhớ do size tăng.
Nếu muốn thật sự remove log file thì thực hiện kill process và kiểm tra lại lsof thì process không còn giữ file nữa:
- Trường hợp 3: kiểm tra cổng mạng đang bị chiếm dụng
Cú pháp lsof giúp lọc chi tiết các kết nối mạng (TCP/UDP), IP, cổng, hostname, phiên bản.
sudo lsof -i[version][protocol][@hostname|address][service:port]sudo lsof -i #Liệt kê các kết nối mạng- COMMAND: systemd-r là dịch vụ truy vấn DNS nội bộ (port 53), avahi-daemon multicast DNS (tự động phát hiện thiết bị cùng mạng LAN).
- FD: firefox mở ra nhiều FD để kết nối HTTPS, truy cập file cache, pipe để giữa các tiến trình con.
- TYPE: IPv4/IPv6.
- DEVICE: trường hợp này không còn là device driver (major/minor) mà là socket ID (kernel cấp để phân biệt socket).
- SIZE/OFF: luôn là 0t0 (0: size; textual 0: offset = 0) vì socket không đọc/ghi giống như file.
- NODE: loại kết nối socket TCP/UDP.
- NAME: (ESTABLISH) là trạng thái bắt tay 3 bước TCP thành công và sẵn sàng truyền dữ liệu giữ 2 bên, (LISTEN) là server chờ kết nối từ client.
sudo lsof -i4 #Liệt kê kết nối IPv4 (hoặc IPv6)sudo lsof -iTCP #Liệt kê kết nối TCP (hoặc UDP)sudo lsof -i :443 #Liệt kê cổng 443 (HTTPS)Một số kết hợp lọc chi tiết hơn:
sudo lsof -i4 -sTCP:LISTEN #Liệt kê kết nối IPv4 dùng TCP và đang dùng socket LISTENsudo lsof -i4 -nP #Liệt kê kết nối IPv4 và không resolve hostname (n), port service (P)Tình huống dùng lsof để khắc phục sự cố:
- Ngăn chặn kết nối không đáng tin:
Không phân giải hostname, port service để xem chính xác các địa chỉ IP thật, ví dụ như hệ thống bị nhiễm malware và thực hiện 1 kết nối ngược từ nạn nhân đến máy chủ hacker nhằm vượt qua tường lửa qua đó hacker có thể điều khiển được hệ thống bị malware, để khắc phục có thể dùng:
sudo lsof -i nP | grep ESTABLISHEDĐể kiểm tra các kết nối đến các địa chi IP nếu thấy IP bất thường, không đáng tin thì lập tức kill để ngắt kết nối, ngăn chặn kết nối xấu.
- Giả lập tình huống chiếm dụng port 5000 làm cho không thể tạo 1 kết nối khác với port 5000:
Tạo 1 python chiếm port 5000 trên 1 terminal, mở 1 terminal khác để giả lập 1 chương trình khác muốn dùng port 5000 nhưng “OSError: [Errno 98] Address already in use” port 5000 đã bị chiếm dụng nên không thể tạo kết nối. Để khắc phục cần liệt kê sudo lsof -i :500 để xem PID nào chiếm port 5000, sau đó kill chiếm port 5000 để có thể dùng port 5000.

















