lsof 命令详解与参数实测(Ubuntu 22.04)
lsof 逐参数实测详解:默认输出十列结构与 FD/TYPE 解读、-p/-u/-c 进程三筛、-i 网络筛选四连(:8000、TCP:8000、@127.0.0.1)、+D 目录递归、-n/-P 提速、deleted 未释放空间排查三步实证、-t 组合 kill 与 -v/-V 区别,Ubuntu 22.04(lsof 4.93.2)全部回显实测通过。
lsof 命令详解与参数实测(Ubuntu 22.04)
目标:全面掌握 lsof 的每一个常用参数——默认输出十列结构与 FD/TYPE 解读、
-p/-u/-c进程三筛、-i网络筛选(-i :8000、-i TCP:8000、-i@127.0.0.1)、+D目录递归、-n/-P提速、删除未释放(deleted)排查、-t只列 PID 并组合 kill、-v/-V,全部命令与回显已在 Ubuntu 22.04(lsof 4.93.2)实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- 内核:6.8.0-138-generic
- lsof 版本:4.93.2(
lsof -v实测,包名lsof 4.93.2+dfsg-1.1build2) - 演示目录:
/root/demo/lsof/(含 a.txt、b.txt、sub/c.conf 与 20MB 的 bigfile.bin,测完即删) - 网络演示服务:
python3 -m http.server 8000(以/root/demo/lsof为根目录,演示完 kill)
二、命令概览
2.1 安装与版本确认
Ubuntu 22.04 默认预装 lsof(它是系统排障的基础工具)。若当前系统未预装,一条命令安装:
$ apt-get install -y lsof
实测本环境的版本信息(dpkg -l 与 lsof -v 交叉确认):
$ dpkg -l lsof | tail -1
ii lsof 4.93.2+dfsg-1.1build2 amd64 utility to list open files
2.2 语法与测试数据
lsof 的语法是 lsof [-?abhKlnNoOPRtUvVX] [+|-c c] [+|-d s] [+D D] ... [--] [names]:不带任何选择条件时列出所有进程的所有已打开文件(Linux 下「一切皆文件」,套接字、管道、设备都算);跟在选择条件后可以指定具体文件名/目录。多个条件之间默认是「或」关系,加 -a 才是「与」。
准备演示数据(三个小文件、一个子目录、一个 20MB 大文件):
$ rm -rf /root/demo/lsof; mkdir -p /root/demo/lsof/sub; echo "demo line 1" > /root/demo/lsof/a.txt; echo "demo line 2" > /root/demo/lsof/b.txt; echo "config" > /root/demo/lsof/sub/c.conf; dd if=/dev/zero of=/root/demo/lsof/bigfile.bin bs=1M count=20 status=none; ls -lh /root/demo/lsof/ /root/demo/lsof/sub/
/root/demo/lsof/:
total 21M
-rw-r--r-- 1 root root 12 Oct 15 14:07 a.txt
-rw-r--r-- 1 root root 12 Oct 15 14:07 b.txt
-rw-r--r-- 1 root root 20M Oct 15 14:07 bigfile.bin
drwxr-xr-x 2 root root 4.0K Oct 15 14:07 sub
/root/demo/lsof/sub/:
total 4.0K
-rw-r--r-- 1 root root 7 Oct 15 14:07 c.conf
启动一个 HTTP 服务供网络筛选实测(exec 让后台 PID 即 python3 本体,实测得 PID 2627):
$ cd /root/demo/lsof && exec python3 -m http.server 8000 & sleep 1; echo "server pid=$!"; lsof -nP -i :8000
server pid=2627
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python3 2627 root 3u IPv4 4432532 0t0 TCP *:8000 (LISTEN)
2.3 -h 帮助全量输出
lsof -h(等价 -?)输出全部选择条件速查(实测逐行照抄):
lsof 4.93.2
latest revision: https://github.com/lsof-org/lsof
latest FAQ: https://github.com/lsof-org/lsof/blob/master/00FAQ
latest (non-formatted) man page: https://github.com/lsof-org/lsof/blob/master/Lsof.8
usage: [-?abhKlnNoOPRtUvVX] [+|-c c] [+|-d s] [+D D] [+|-E] [+|-e s] [+|-f[gG]]
[-F [f]] [-g [s]] [-i [i]] [+|-L [l]] [+m [m]] [+|-M] [-o [o]] [-p s]
[+|-r [t]] [-s [p:s]] [-S [t]] [-T [t]] [-u s] [+|-w] [-x [fl]] [--] [names]
Defaults in parentheses; comma-separated set (s) items; dash-separated ranges.
-?|-h list help -a AND selections (OR) -b avoid kernel blocks
-c c cmd c ^c /c/[bix] +c w COMMAND width (9) +d s dir s files
-d s select by FD set +D D dir D tree *SLOW?* +|-e s exempt s *RISKY*
-i select IPv[46] files -K [i] list|(i)gn tasKs -l list UID numbers
-n no host names -N select NFS files -o list file offset
-O no overhead *RISKY* -P no port names -R list paRent PID
-s list file size -t terse listing -T disable TCP/TPI info
-U select Unix socket -v list version info -V verbose search
+|-w Warnings (+) -X skip TCP&UDP* files -Z Z context [Z]
-- end option scan
-E display endpoint info +E display endpoint info and files
+f|-f +filesystem or -file names +|-f[gG] flaGs
-F [f] select fields; -F? for help
+|-L [l] list (+) suppress (-) link counts < l (0 = all; default = 0)
+m [m] use|create mount supplement
+|-M portMap registration (-) -o o o 0t offset digits (8)
-p s exclude(^)|select PIDs -S [t] t second stat timeout (15)
-T qs TCP/TPI Q,St (s) info
-g [s] exclude(^)|select and print process group IDs
-i i select by IPv[46] address: [46][proto][@host|addr][:svc_list|port_list]
+|-r [t[m<fmt>]] repeat every t seconds (15); + until no files, - forever.
An optional suffix to t is m<fmt>; m must separate t from <fmt> and
<fmt> is an strftime(3) format for the marker line.
-s p:s exclude(^)|select protocol (p = TCP|UDP) states by name(s).
-u s exclude(^)|select login|UID set s
-x [fl] cross over +d|+D File systems or symbolic Links
names select named files or files on named file systems
Anyone can list all files; /dev warnings disabled; kernel ID check disabled.
💡 说明:lsof 的条件分三类——进程维度(
-pPID、-u用户、-c命令名、-g进程组)、文件/目录维度(names直接给路径、+d/+D目录及其文件)、网络维度(-i按[46][协议][@主机][:端口]筛选)。另有一组输出修饰(-n不解析主机名、-P不解析端口名、-t只输出 PID、-F机器可读字段),排障时几乎总是-nP三连。
2.4 版本信息
$ lsof -v
lsof version information:
revision: 4.93.2
latest revision: https://github.com/lsof-org/lsof
latest FAQ: https://github.com/lsof-org/lsof/blob/master/00FAQ
latest (non-formatted) man page: https://github.com/lsof-org/lsof/blob/master/Lsof.8
compiler: cc
compiler version: 11.2.0 (Ubuntu 11.2.0-19ubuntu1)
Anyone can list all files.
/dev warnings are disabled.
Kernel ID check is disabled.
(完整输出还包含编译 flags 一长行,此处节选关键段。)注意三个小写字母邻居的区别:-v 是版本信息,-V 是选择条件诊断(告诉您某个条件为什么没匹配到,见 3.12),-V 在部分工具里是「校验」,lsof 不是。
2.5 技术拓展:打开文件观测工具对比
💡 技术拓展:查「谁占用」的工具有三家,视角不同——
工具 视角 典型场景 lsof 文件为中心(fd 清单) 谁打开了这个文件/端口/目录;deleted 空间排查 fuser 文件为中心(极简) 快速确认文件被哪个 PID 以何种方式访问 ss / netstat 连接为中心 看监听与连接的收发队列、状态机,网络调优主用(网络命令属后续批次) /proc/ <pid>/fd单进程 精确看某进程每个 fd 指向,配合 lsof 结论验证 fuser 的实测输出(同一个被 tail -f 占用的文件,信息量远小于 lsof 但更快):
USER PID ACCESS COMMAND /root/demo/fuser-demo.txt: root 2754 f.... tail网络视角对比:
lsof -i :8000能看到「哪个 PID 的哪个 fd」在监听/连接,而 ss/netstat 只给 socket 级视图不带进程归属细节;要「文件↔进程↔连接」三向对账,lsof 最顺手。
三、参数逐项实测
3.1 默认输出:十列结构与行数
不带参数的 lsof 列出全机所有打开文件。输出行数先探一眼(本环境较精简,实测 93 行;繁忙的生产服务器动辄数万行,所以「先 wc -l、再 head 采样」是标准打开姿势):
$ lsof 2>/dev/null | wc -l
93
$ lsof 2>/dev/null | head -15
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sleep 1 root cwd DIR 0,313 4096 30408871 /
sleep 1 root rtd DIR 0,313 4096 30408871 /
sleep 1 root txt REG 0,313 35336 30431056 /usr/bin/sleep
sleep 1 root mem REG 0,313 2220400 30409857 /usr/lib/x86_64-linux-gnu/libc.so.6
sleep 1 root mem REG 0,313 240936 30409792 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
sleep 1 root 0u CHR 1,3 0t0 5 /dev/null
sleep 1 root 1w FIFO 0,14 0t0 2749328 pipe
sleep 1 root 2w FIFO 0,14 0t0 2749329 pipe
systemd-u 1218 root cwd DIR 0,313 4096 30408871 /
systemd-u 1218 root rtd DIR 0,313 4096 30408871 /
systemd-u 1218 root txt REG 0,313 1131856 30434952 /usr/bin/udevadm
systemd-u 1218 root mem REG 0,313 11127176 31588588 /usr/lib/udev/hwdb.bin
systemd-u 1218 root mem REG 0,313 613064 30410041 /usr/lib/x86_64-linux-gnu/libpcre2-8.so.0.10.4
systemd-u 1218 root mem REG 0,313 4455728 30421485 /usr/lib/x86_64-linux-gnu/libcrypto.so.3
十列含义:COMMAND(命令名,默认截断 9 字符,如 systemd-u)、PID、USER、FD、TYPE、DEVICE(字符/块设备的主次设备号,socket 显示内核编号)、SIZE/OFF(大小或偏移)、NODE(inode 号或协议)、NAME(文件路径/套接字对端等)。FD 与 TYPE 两列是解读核心,分别见 3.2、3.3。
💡 要点:默认输出的 NAME 列只在特定场景出现 OFFSET(如
-o强制显示文件偏移);普通场景 SIZE/OFF 列显示大小或留空,本文不展开这两列的变体细节。
3.2 FD 列解读:cwd/rtd/txt/mem 与数字描述符
-p $$ 看自身 shell 的 fd 清单,一屏集齐所有 FD 类型:
$ lsof -p $$
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
bash 2588 root cwd DIR 0,313 4096 30408871 /
bash 2588 root rtd DIR 0,313 4096 30408871 /
bash 2588 root txt REG 0,313 1396520 30409742 /usr/bin/bash
bash 2588 root mem REG 0,313 2220400 30409857 /usr/lib/x86_64-linux-gnu/libc.so.6
bash 2588 root mem REG 0,313 200136 30410054 /usr/lib/x86_64-linux-gnu/libtinfo.so.6.3
bash 2588 root mem REG 0,313 240936 30409792 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
bash 2588 root 0r CHR 1,3 0t0 5 /dev/null
bash 2588 root 1w FIFO 0,14 0t0 4431442 pipe
bash 2588 root 2w FIFO 0,14 0t0 4431442 pipe
bash 2588 root 10w FIFO 0,14 0t0 4431443 pipe
bash 2588 root 255r REG 0,313 2996 30436349 /root/lsof-test3.sh
FD 列的字母后缀表示打开模式:r=读、w=写、u=读写。特殊 FD 标识:
| FD 标识 | 含义 | 上例对应 |
|---|---|---|
| cwd | 当前工作目录 | / |
| rtd | 根目录 | / |
| txt | 程序本体(代码段) | /usr/bin/bash |
| mem | 内存映射的文件(共享库等) | libc.so.6 等 |
| 数字+r/w/u | 打开的文件描述符 | 0r 标准输入、1w/2w 标准输出与错误 |
| 255r | bash 自己的高编号 fd(打开的脚本文件) | /root/lsof-test3.sh |
💡 要点:看到
3r/4u这类纯数字 fd 就是常规打开的文件;「数字大得反常」的 fd(如 255r)通常是解释器内部使用。
3.3 TYPE 列解读:REG/DIR/CHR/FIFO/IPv4
TYPE 列标识文件类型,常见五种在 3.1/3.2 的输出里全部出现过:
| TYPE | 含义 | 上例对应 |
|---|---|---|
| REG | 普通文件 | /usr/bin/sleep、libc.so.6 |
| DIR | 目录 | cwd/rtd 指向的 / |
| CHR | 字符设备 | /dev/null(DEVICE 列显示主次号 1,3) |
| FIFO | 管道 | 标准输出/错误接的 pipe |
| IPv4/IPv6 | 网络套接字 | 见 3.7 的 TCP *:8000 |
💡 要点:
lsof | grep -c REG数一数普通文件占比、grep FIFO找管道泄漏,都是现场快速统计的实用小招。
3.4 -p:按 PID 精确圈定
-p 1 看 1 号进程的 fd 清单:
$ lsof -p 1
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sleep 1 root cwd DIR 0,313 4096 30408871 /
sleep 1 root rtd DIR 0,313 4096 30408871 /
sleep 1 root txt REG 0,313 35336 30431056 /usr/bin/sleep
sleep 1 root mem REG 0,313 2220400 30409857 /usr/lib/x86_64-linux-gnu/libc.so.6
sleep 1 root mem REG 0,313 240936 30409792 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
sleep 1 root 0u CHR 1,3 0t0 5 /dev/null
sleep 1 root 1w FIFO 0,14 0t0 2749328 pipe
sleep 1 root 2w FIFO 0,14 0t0 2749329 pipe
-p 也支持逗号列表与脱字符排除,如 -p 123,456、-p ^1(排除 1 号)。
💡 要点:服务异常时
lsof -p <主进程PID>是最快的「它现在打开了什么」透视镜,比 /proc/<pid>/fd 的人读性好。
3.5 -u:按用户筛选
-u root 只看 root 用户的打开文件(本环境所有进程都是 root,行数与全量一致,实测 93 行):
$ lsof -u root 2>/dev/null | wc -l
93
$ lsof -u root 2>/dev/null | head -6
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sleep 1 root cwd DIR 0,313 4096 30408871 /
sleep 1 root rtd DIR 0,313 4096 30408871 /
sleep 1 root txt REG 0,313 35336 30431056 /usr/bin/sleep
sleep 1 root mem REG 0,313 2220400 30409857 /usr/lib/x86_64-linux-gnu/libc.so.6
sleep 1 root mem REG 0,313 240936 30409792 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
生产场景 -u www-data/-u mysql 用来锁定业务用户,配合 ^ 可排除:-u ^root(非 root 用户)。
💡 要点:
-u接的是登录名或 UID,可写列表(-u a,b,c);它只按进程的属主过滤,不按文件属主。
3.6 -c:按命令名筛选
-c bash 列出命令名以 bash 开头的进程的 fd(c 条件是「以...开头」匹配):
$ lsof -c bash 2>/dev/null | head -10
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
bash 2588 root cwd DIR 0,313 4096 30408871 /
bash 2588 root rtd DIR 0,313 4096 30408871 /
bash 2588 root txt REG 0,313 1396520 30409742 /usr/bin/bash
bash 2588 root mem REG 0,313 2220400 30409857 /usr/lib/x86_64-linux-gnu/libc.so.6
bash 2588 root mem REG 0,313 200136 30410054 /usr/lib/x86_64-linux-gnu/libtinfo.so.6.3
bash 2588 root mem REG 0,313 240936 30409792 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
bash 2588 root 0r CHR 1,3 0t0 5 /dev/null
bash 2588 root 1w FIFO 0,14 0t0 4431442 pipe
bash 2588 root 2w FIFO 0,14 0t0 4431442 pipe
启动中的 HTTP 服务的完整视图(-c python3,节选前 12 行):
$ lsof -nP -c python3 | head -12
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python3 2627 root cwd DIR 0,313 4096 31738259 /root/demo/lsof
python3 2627 root rtd DIR 0,313 4096 30408871 /
python3 2627 root txt REG 0,313 5941864 30431631 /usr/bin/python3.10
python3 2627 root mem REG 0,313 170456 30409998 /usr/lib/x86_64-linux-gnu/liblzma.so.5.2.5
python3 2627 root mem REG 0,313 45240 30588408 /usr/lib/python3.10/lib-dynload/_lzma.cpython-310-x86_64-linux-gnu.so
python3 2627 root mem REG 0,313 4455728 30421485 /usr/lib/x86_64-linux-gnu/libcrypto.so.3
python3 2627 root mem REG 0,313 671960 30422041 /usr/lib/x86_64-linux-gnu/libssl.so.3
python3 2627 root mem REG 0,313 221200 30588152 /usr/lib/python3.10/lib-dynload/_ssl.cpython-310-x86_64-linux-gnu.so
python3 2627 root mem REG 0,313 74848 27557462 /usr/lib/x86_64-linux-gnu/libbz2.so.1.0.4
python3 2627 root mem REG 0,313 32120 30588391 /usr/lib/python3.10/lib-dynload/_bz2.cpython-310-x86_64-linux-gnu.so
python3 2627 root mem REG 0,313 27002 31720274 /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache
💡 要点:
-c匹配的是 COMMAND 列(进程名前 9 字符),-c /bash/这种斜杠写法可改用正则;组合-a -c python3 -u root表示「与」关系。
3.7 -i:网络筛选四连(:8000 / TCP:8000 / @127.0.0.1 / TCP)
先起一条实时连接(curl 限速下载 bigfile.bin,让连接存活几秒),再连续跑三种 -i 写法:
$ curl -s --limit-rate 4M -o /dev/null http://127.0.0.1:8000/bigfile.bin & sleep 1; lsof -i :8000; echo ----; lsof -i TCP:8000; echo ----; lsof -i@127.0.0.1
127.0.0.1 - - [15/Oct/2026 14:07:17] "GET /bigfile.bin HTTP/1.1" 200 -
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python3 2627 root 3u IPv4 4432532 0t0 TCP *:8000 (LISTEN)
python3 2627 root 4u IPv4 4436410 0t0 TCP localhost:8000->localhost:38694 (ESTABLISHED)
curl 2635 root 5u IPv4 4431458 0t0 TCP localhost:38694->localhost:8000 (ESTABLISHED)
----
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python3 2627 root 3u IPv4 4432532 0t0 TCP *:8000 (LISTEN)
python3 2627 root 4u IPv4 4436410 0t0 TCP localhost:8000->localhost:38694 (ESTABLISHED)
curl 2635 root 5u IPv4 4431458 0t0 TCP localhost:38694->localhost:8000 (ESTABLISHED)
----
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python3 2627 root 4u IPv4 4436410 0t0 TCP localhost:8000->localhost:38694 (ESTABLISHED)
curl 2635 root 5u IPv4 4431458 0t0 TCP localhost:38694->localhost:8000 (ESTABLISHED)
(首行的 GET 日志是 python3 -m http.server 打印在终端上的访问日志,属于真实回显一并保留。)三种写法对比:
-i :8000——匹配任一端端口为 8000 的套接字:一条 LISTEN + 两条 ESTABLISHED(服务端 fd 4u 与客户端 fd 5u 是同一条连接的两端);-i TCP:8000——加协议限定,结果同上(本机无 UDP 8000);-i@127.0.0.1——按地址筛选:LISTEN 套接字绑定的是*:8000(所有地址)不匹配,只剩本机回环上的两条 ESTABLISHED。
不筛端口看全量 TCP(-i TCP,实测与 :8000 一致,因为本机只有这条业务连接):
$ lsof -i TCP 2>/dev/null | head -8
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python3 2627 root 3u IPv4 4432532 0t0 TCP *:8000 (LISTEN)
python3 2627 root 4u IPv4 4436410 0t0 TCP localhost:8000->localhost:38694 (ESTABLISHED)
curl 2635 root 5u IPv4 4431458 0t0 TCP localhost:38694->localhost:8000 (ESTABLISHED)
-i 的完整语法是 [46][协议][@host|addr][:svc_list|port_list],支持端口列表与范围(如 -i :22,80,443、-i :1-1024)与状态筛选(-s TCP:ESTABLISHED)。
💡 要点:
-i :8000的匹配是「本端或对端任一命中」,所以客户端 curl 也会出现在结果里——这正是「这个端口到底被谁连着」的双向排查能力。
3.8 -n 与 -P:不做名字解析,服务器排障必加
-n 不把 IP 反解成主机名、-P 不把端口翻译成服务名(如 8000→http-alt)。对比 3.7 的 localhost,加上 -nP 后直接显示数字地址:
$ lsof -nP -i :8000
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python3 2627 root 3u IPv4 4432532 0t0 TCP *:8000 (LISTEN)
python3 2627 root 4u IPv4 4436410 0t0 TCP 127.0.0.1:8000->127.0.0.1:38694 (ESTABLISHED)
curl 2635 root 5u IPv4 4431458 0t0 TCP 127.0.0.1:38694->127.0.0.1:8000 (ESTABLISHED)
💡 要点:服务器上 DNS 不通或反解超时时,不带 -n 的 lsof 会卡在名字解析上(每条记录都要查);排查姿势永远是
lsof -nP先跑通,需要名字时再单独查。
3.9 +D:目录递归查占用(与单文件写法对比)
+D 目录 递归列出「该目录树下被打开的文件」;对照「单文件直接跟路径」的写法:
$ lsof +D /root/demo/lsof 2>/dev/null
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python3 2627 root cwd DIR 0,313 4096 31738259 /root/demo/lsof
python3 2627 root 5r REG 0,313 20971520 31738265 /root/demo/lsof/bigfile.bin
$ lsof /root/demo/lsof/a.txt
解读:+D 抓到两处占用——http.server 的工作目录(cwd)本身在该目录树下、curl 正以 fd 5r 读 bigfile.bin(SIZE/OFF 列显示 20971520 即文件完整大小);而第二条命令输出为空,说明 a.txt 此刻没有被任何进程打开(文件存在≠被占用)。
💡 要点:
+D每次都 stat 整棵目录树,大目录(如 /usr、/var)会非常慢,帮助里也标注*SLOW?*;已知具体文件时直接lsof 文件路径,目录级粗筛用+d(不递归、更快)。
3.10 -t:只输出 PID(组合 kill 一条龙)
-t 把输出压缩成裸 PID 列,专为脚本设计。实测同一端口能列出两个 PID——服务端与客户端各一个(呼应 3.7 的双向匹配):
$ lsof -t -i :8000
2627
2635
「找到并干掉占用者」的组合拳,实测一条命令清场:
$ kill $(lsof -t -i :8000) 2>/dev/null; sleep 0.5; lsof -i :8000; echo rc=$?
rc=1
文件没有被任何进程打开时,-t 输出为空、退出码为 1(配合 rc 判断可让脚本幂等):
$ lsof -t /root/demo/lsof/b.txt; echo rc=$?
rc=1
💡 要点:
kill $(lsof -t -i :端口)是释放端口的经典一句流;但生产上先看清 PID 列表再 kill(本例里它同时杀掉了连接的两端进程)。
3.11 排查「删了文件空间没释放」:deleted 标记与 /proc 验证
经典现场:文件被删除,df 看到的空间却没回来。原因:只要有进程还握着这个文件的 fd,内核就不会真正释放数据块。三步实测:
第一步,tail -f 持有文件后删除它,lsof 立刻现形(NAME 列带 (deleted) 标记):
$ tail -f /root/demo/lsof/a.txt & sleep 1; echo "tail pid=$!"; rm /root/demo/lsof/a.txt; ls /root/demo/lsof/; lsof | grep a.txt
demo line 1
tail pid=2666
b.txt
bigfile.bin
sub
tail 2666 root 3r REG 0,313 12 31738262 /root/demo/lsof/a.txt (deleted)
第二步,验证未释放的原理——fd 3 仍指向那个已删除的 inode,且内容还能读出来:
$ tpid=$(pgrep -f "tail -f /root/demo/lsof/a.txt" | head -1); echo "tail pid=$tpid"; ls -l /proc/$tpid/fd/ 2>/dev/null | head -6; echo "--- 仍可读出内容 ---"; cat /proc/$tpid/fd/3 2>/dev/null
tail pid=2666
total 0
lr-x------ 1 root root 64 Oct 15 14:07 0 -> /dev/null
l-wx------ 1 root root 64 Oct 15 14:07 1 -> pipe:[4431442]
l-wx------ 1 root root 64 Oct 15 14:07 2 -> pipe:[4431442]
lr-x------ 1 root root 64 Oct 15 14:07 3 -> /root/demo/lsof/a.txt (deleted)
--- 仍可读出内容 ---
demo line 1
第三步,结束持有进程,句柄释放、文件引用彻底消失(grep 无结果、rc=1):
$ pkill -f "tail -f /root/demo/lsof/a.txt"; sleep 0.5; lsof | grep a.txt; echo rc=$?
rc=1
💡 要点:磁盘上「消失的空间」十有八九是这类 deleted-but-held 文件,
lsof | grep deleted一条命令定位;应急释放两种姿势——让进程退出,或清空它的 fd(: > /proc/<pid>/fd/<N>,仅对可中断场景使用)。
3.12 -V:选择条件诊断(为什么没匹配到?)
-V 让 lsof 对「没匹配到任何文件的每个选择条件」打印诊断。实测两种条件各一例:
$ lsof -V -p 999999
lsof: process ID not located: 999999
$ lsof -V -u nosuchuser 2>&1 | head -4
lsof: can't get UID for nosuchuser
lsof 4.93.2
latest revision: https://github.com/lsof-org/lsof
latest FAQ: https://github.com/lsof-org/lsof/blob/master/00FAQ
第一条直接报告「PID 999999 不存在」;第二条说明 -u 拿到的是个不存在的用户名(后面跟版本横幅属异常退出时的回显)。脚本里可借此区分「没匹配到」与「条件本身写错」。
四、实战示例
4.1 端口被占用:谁在监听 8000?
$ lsof -nP -i :8000
(实测回显见 3.8。)读法:LISTEN 行给出监听进程(python3、fd 3u);ESTABLISHED 行给出连接两端(服务端 fd 4u ↔ 客户端 curl fd 5u)。要「连人带进程」杀掉监听者,kill $(lsof -t -i :8000)(见 3.10)。
4.2 磁盘满了但 du 对不上:揪出 deleted 文件
$ lsof | grep deleted
(实测回显见 3.11 第一步:tail 2666 root 3r REG ... /root/demo/lsof/a.txt (deleted)。)按 SIZE/OFF 列从大到小排序(lsof | grep deleted | sort -k7 -n -r | head)即可找到最大的泄漏源;处理持有进程后 df 立即回血(实测见 3.11 第三步,句柄释放后 grep 无结果)。
4.3 发版/卸载前确认:目录下还有谁在用
$ lsof +D /root/demo/lsof 2>/dev/null
(实测回显见 3.9:cwd 占用 + 正在读取的 bigfile.bin。)目录树里任何进程仍有 fd 就先别动;逐文件精确核对用 lsof 文件路径(空输出=无人占用,见 3.9 第二条命令)。
4.4 服务句柄泄漏巡检
某服务长期运行后怀疑 fd 泄漏,用 -p 看它的 fd 清单并定期对比行数。实测 python3 -m http.server(PID 2627)的完整清单共 23 行:表头 + 22 项,即 3 个身份项 cwd/rtd/txt + 15 个共享库映射 + 标准流 0/1/2 + 监听套接字 3u:
$ lsof -p $(pgrep -f "http.server 8000" | head -1)
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python3 2627 root cwd DIR 0,313 4096 31738259 /root/demo/lsof
python3 2627 root rtd DIR 0,313 4096 30408871 /
python3 2627 root txt REG 0,313 5941864 30431631 /usr/bin/python3.10
python3 2627 root mem REG 0,313 170456 30409998 /usr/lib/x86_64-linux-gnu/liblzma.so.5.2.5
python3 2627 root mem REG 0,313 45240 30588408 /usr/lib/python3.10/lib-dynload/_lzma.cpython-310-x86_64-linux-gnu.so
python3 2627 root mem REG 0,313 4455728 30421485 /usr/lib/x86_64-linux-gnu/libcrypto.so.3
python3 2627 root mem REG 0,313 671960 30422041 /usr/lib/x86_64-linux-gnu/libssl.so.3
python3 2627 root mem REG 0,313 221200 30588152 /usr/lib/python3.10/lib-dynload/_ssl.cpython-310-x86_64-linux-gnu.so
python3 2627 root mem REG 0,313 74848 27557462 /usr/lib/x86_64-linux-gnu/libbz2.so.1.0.4
python3 2627 root mem REG 0,313 32120 30588391 /usr/lib/python3.10/lib-dynload/_bz2.cpython-310-x86_64-linux-gnu.so
python3 2627 root mem REG 0,313 27002 31720274 /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache
python3 2627 root mem REG 0,313 353616 31636556 /usr/lib/locale/C.utf8/LC_CTYPE
python3 2627 root mem REG 0,313 3048928 31626540 /usr/lib/locale/locale-archive
python3 2627 root mem REG 0,313 2220400 30409857 /usr/lib/x86_64-linux-gnu/libc.so.6
python3 2627 root mem REG 0,313 108936 27557594 /usr/lib/x86_64-linux-gnu/libz.so.1.2.11
python3 2627 root mem REG 0,313 198968 30420992 /usr/lib/x86_64-linux-gnu/libexpat.so.1.8.7
python3 2627 root mem REG 0,313 940560 30409897 /usr/lib/x86_64-linux-gnu/libm.so.6
python3 2627 root mem REG 0,313 240936 30409792 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
python3 2627 root 0r CHR 1,3 0t0 5 /dev/null
python3 2627 root 1w FIFO 0,14 0t0 4431442 pipe
python3 2627 root 2w FIFO 0,14 0t0 4431442 pipe
python3 2627 root 3u IPv4 4432532 0t0 TCP *:8000 (LISTEN)
巡检方法:隔几分钟重跑并对比行数,数据行只涨不降(尤其 REG/IPv4 类型持续增加)即为泄漏信号。
五、注意事项与常见问题
Q1:默认输出行数巨大,怎么高效地看?
三段式:lsof | wc -l 摸总量(本环境实测 93 行,见 3.1)、lsof -nP | head -20 采样看结构、再按条件收窄(-p/-u/-c/-i)。服务器上永远带 -nP,避免名字解析拖慢输出(见 3.8)。
Q2:普通用户跑 lsof 看不到别人的进程?
是权限设计如此:普通用户只能看到自己进程的 fd 与「别人的非 fd 信息」,要列全机清单需要 root(或 CAP_SYS_ADMIN、或编译期放开限制)。本环境 lsof -v 输出里的 Anyone can list all files. 表示编译时放开了限制(见 2.4 回显),多数发行版默认不放开。
Q3:+D 为什么慢?怎么办?
+D 要递归 stat 目录树下每个文件再与打开文件对账,帮助里自带 *SLOW?* 标注(见 2.3)。大目录改用三种替代:+d(只看目录一层,不递归)、lsof 文件路径(点名)、或 lsof | grep 目录前缀(一次全量输出反复用,只多跑一次内核对账)。
Q4:deleted 文件的空间什么时候真正释放?
最后一个引用它的 fd 关闭之时。删除只是把目录项摘掉,inode 与数据块要等所有 fd(含 mem 映射)关闭才回收(原理实测见 3.11:/proc/<pid>/fd/3 仍指向 deleted 文件且内容可读)。日志类占用者(tail -f、被轮转重命名的日志写手)要么重启、要么 : > /proc/<pid>/fd/<N> 截断其句柄。
Q5:-i 的端口到底匹配哪一端?
两端都匹配:-i :8000 同时命中监听方与连接双方(实测见 3.10:lsof -t -i :8000 返回服务端 2627 与客户端 2635 两个 PID)。只想看监听,grep (LISTEN);只想看连接,grep (ESTABLISHED)。
Q6:lsof 的退出码怎么用?
有匹配结果返回 0,无匹配返回 1,条件错误返回 1(并打印错误,如 3.12 的 -V -u nosuchuser)。脚本里用 lsof -t ... >/dev/null 的返回值做「是否被占用」的布尔判断即可,无需解析文本。
六、参考链接
- lsof 项目主页(源码与发布):https://github.com/lsof-org/lsof
- lsof 官方 FAQ:https://github.com/lsof-org/lsof/blob/master/00FAQ
- lsof 手册页(man7):https://man7.org/linux/man-pages/man8/lsof.8.html
- Ubuntu 22.04(jammy)lsof 手册页:https://manpages.ubuntu.com/manpages/jammy/en/man8/lsof.8.html
- Ubuntu 官方包信息 lsof:https://packages.ubuntu.com/jammy/lsof