lsof —— 列出打开的文件(list open files)
一句话定义
lsof 列出进程打开的文件——包括普通文件、目录、设备、网络 socket、unix socket、pipe、设备等。在 Linux 里"一切皆文件",所以 lsof 能回答各种"谁在用什么"的问题。
典型场景
- "为啥端口被占了":
lsof -i :8080 - "rm 文件但磁盘没释放":
lsof | grep deleted - "为啥这个 unmount 不掉":
lsof <mount-point> - "containerd.sock 谁在连":
lsof /run/containerd/containerd.sock - "进程打开了多少 fd":
lsof -p <PID>+wc -l
装
apt install -y lsof # Debian / Ubuntu
yum install -y lsof # CentOS / RHEL
大多数系统自带。
输出格式
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sshd 1234 root cwd DIR 8,3 4096 2 /
sshd 1234 root rtd DIR 8,3 4096 2 /
sshd 1234 root txt REG 8,3 855648 123456 /usr/sbin/sshd
sshd 1234 root mem REG 8,3 166400 123457 /lib/x86_64-linux-gnu/libc.so.6
sshd 1234 root 0u CHR 1,3 0t0 6 /dev/null
sshd 1234 root 3u IPv4 123456 0t0 TCP *:22 (LISTEN)
sshd 1234 root 4u IPv6 123457 0t0 TCP *:22 (LISTEN)
| 列 | 含义 |
|---|---|
COMMAND | 进程名 |
PID | 进程 ID |
USER | 拥有者 |
FD | 文件描述符(数字 + 模式 / 特殊标记) |
TYPE | 文件类型 |
DEVICE | 主设备号 / 次设备号 |
SIZE/OFF | 大小或偏移量 |
NODE | inode |
NAME | 文件名 / socket 描述 |
FD 列的特殊值
| 值 | 含义 |
|---|---|
cwd | current working directory |
rtd | root directory(chroot 后) |
txt | text(可执行文件本身) |
mem | 内存映射文件 |
0u / 1u / 2u | stdin / stdout / stderr(u = read/write) |
3r / 3w / 3u | 数字 + r (read) / w (write) / u (both) |
TYPE 列
| 值 | 含义 |
|---|---|
REG | 普通文件 |
DIR | 目录 |
CHR | 字符设备 |
BLK | 块设备 |
FIFO | 命名管道 |
IPv4 / IPv6 | 网络 socket |
unix | unix domain socket |
核心 5 个用法
1. lsof -i :<port> —— 谁占了某端口
lsof -i :8080
# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
# nginx 1234 root 6u IPv4 123456 0t0 TCP *:8080 (LISTEN)
也可以指定协议:
lsof -i tcp:8080
lsof -i udp:53
lsof -i 4 -i tcp # 仅 IPv4 TCP
lsof -i @10.0.24.28 # 跟某 IP 相关的连接
lsof -i @10.0.24.28:6443 # 跟某 IP:port 相关
2. lsof <path> —— 谁打开了某文件
lsof /var/log/syslog
# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
# rsyslogd 567 root 8w REG 8,3 1234567 ... /var/log/syslog
3. lsof <directory> —— 谁在用某目录
lsof +D /mnt/data
# +D 递归显示该目录下的所有
# 慢但全面
排查 umount: device is busy 必备。
4. lsof -p <PID> —— 某进程打开的所有文件
lsof -p 1234 | head
lsof -p 1234 | wc -l # 这进程打开了多少 fd
5. lsof -u <user> —— 某用户打开的
lsof -u root
lsof -u nginx -i # nginx 用户的网络连接
实战场景
场景 1:端口被占用
$ systemctl start nginx
Job for nginx.service failed because the control process exited with error.
# ... bind: Address already in use
$ lsof -i :80
# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
# httpd 2345 root 4u IPv4 234567 0t0 TCP *:80 (LISTEN)
# 是 Apache 在占!
systemctl stop apache2
systemctl start nginx
ss -lntp 是更现代的等价物,但 lsof -i 在某些场景更直观(含进程名)。
场景 2:rm 文件后磁盘没释放
df -h /
# /dev/sda3 95% full
rm /var/log/huge.log
df -h /
# /dev/sda3 95% full ← 还是满的!
lsof | grep deleted
# rsyslogd 567 root 8w REG 8,3 10737418240 123456 /var/log/huge.log (deleted)
某进程持有这个文件的句柄 → Linux 不真释放空间。
修:
systemctl restart rsyslog # 重启那个进程,句柄关 → 空间释放
# 或者优雅一点(不重启)让进程关那个 fd:不容易,多数情况只能 restart
K8s 节点上 journald / kubelet / containerd 是常见占用者。
场景 3:umount 卡住
$ umount /mnt/data
umount: /mnt/data: target is busy.
$ lsof +D /mnt/data | head
# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
# bash 3456 root cwd DIR 8,16 4096 1 /mnt/data
# vim 4567 root 3u REG 8,16 1234 2 /mnt/data/notes.txt
找到占用者,kill 或者让它退出,再 umount。
或者懒卸载:
umount -l /mnt/data # lazy 立刻断开 mount,但保留旧 fd 工作直到关闭
场景 4:进程打开太多 fd
lsof -p 1234 | wc -l
# 65000 ← 接近 ulimit 上限
# 看大概是什么
lsof -p 1234 | awk '{print $5}' | sort | uniq -c | sort -rn | head
# 60000 IPv4 ← 大部分是网络连接
# 200 REG
# ...
fd 泄漏。看应用代码。
场景 5:找跟 unix socket 相关的进程
# 谁在连 containerd
lsof /run/containerd/containerd.sock
# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
# kubelet 1234 root 5u unix 0x... 0t0 ... /run/containerd/containerd.sock
# 谁在连 docker
lsof /var/run/docker.sock
排查"kubelet 连不上 containerd" / "kubectl exec 卡"。
场景 6:看 K8s pod 内进程开的文件
# 找 pod 容器的 PID
PID=$(crictl inspect $(crictl ps -q --name my-pod) | jq '.info.pid')
# 看它开了什么
lsof -p $PID
# 看它的网络连接
lsof -p $PID -i
# 看是不是 fd 泄漏
lsof -p $PID | wc -l
注意:在节点 root 上跑 lsof 看 pod 进程的 fd 是看不到 pod 内 mount 视角的路径(namespace 隔离)—— 你看到的是宿主机的路径。
要 pod 视角:
nsenter -t $PID --mount --net lsof -p $PID
看网络连接
lsof -i # 所有网络连接
lsof -i tcp # 只 TCP
lsof -i -P -n # -P 不解析端口名,-n 不解析 IP
lsof -i :22 # 22 端口
lsof -i tcp:80-443 # 端口范围
lsof -i @gateway.local # 跟某主机相关
lsof -i 4 -i :443 # IPv4 + 443
-P -n 标配:不加的话 lsof 会反向 DNS + 解析端口名,慢。
lsof -i -P -n | grep ESTABLISHED
# 所有已建立的网络连接
lsof vs ss 在网络场景的对比
| 维度 | ss | lsof -i |
|---|---|---|
| 速度 | 快(netlink) | 慢(遍历所有进程) |
| 信息 | 网络专用,无进程文件 | 综合 |
| 看哪个进程 | -p | 自带 |
| TCP 状态过滤 | state established | grep |
| 推荐 | 日常优先用 ss | lsof 在排错复杂场景 |
ss -lntp # 看监听 + 进程,简洁快
lsof -i :8080 # 看 8080 端口被谁占(同等效果)
看进程的工作目录 / 已执行二进制
lsof -p 1234 -a -d cwd # cwd
# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
# python 1234 root cwd DIR 8,3 4096 1234 /opt/myapp
lsof -p 1234 -a -d txt # 可执行文件(text 段)
# python 1234 root txt REG 8,3 3000000 5678 /usr/bin/python3.10
-a 表示多个过滤条件 AND(默认是 OR)。 -d cwd 限定 fd 类型。
或者更直接:
readlink /proc/1234/cwd
readlink /proc/1234/exe
ls -l /proc/1234/fd/
训练营常用速查
# 谁占了 ApiServer 端口
lsof -i :6443
# 谁连 etcd
lsof -i :2379
# pod 内进程开的文件
lsof -p $(crictl inspect $(crictl ps -q --name my-pod) | jq '.info.pid')
# 找 deleted 文件释放磁盘
lsof | grep deleted | sort -k7 -rn | head
# 看 kubelet 与 containerd 的 unix socket
lsof /var/run/containerd/containerd.sock
lsof /var/run/cri.sock
# 排查节点出网慢:哪些进程在大量网络连接
lsof -i -P -n | awk '{print $1}' | sort | uniq -c | sort -rn | head
常见踩坑
坑 1:lsof 跑得超慢
lsof # 几秒到几十秒
lsof 默认遍历所有进程的所有 fd——大节点几万个 fd。
加 flag 缩小范围:
lsof -p <PID> # 限定 PID
lsof -i :8080 # 限定 socket
lsof -nP # 跳过 DNS / 服务名解析
lsof -c <command> # 按进程名
特别 -n -P 大幅提速。
坑 2:root 才能看全
$ lsof -i :22
# 没输出 / 只显示自己的进程
sudo lsof -i :22。看别人进程必须 root。
坑 3:lsof +D 慢得吓人
lsof +D / # 跑半小时
+D 递归一整个文件系统。只在小目录用:
lsof /mnt/data # 仅匹配这一层
lsof +D /mnt/data # 递归(慢)
坑 4:在容器里 lsof 看不到完整路径
$ kubectl exec my-pod -- lsof | head
# 路径是 pod 内的视角(/app/...)
容器有自己的 mount namespace,路径意义和宿主机不同。这是预期行为。
坑 5:alpine 容器没装 lsof
$ kubectl exec my-pod -- lsof
sh: lsof: not found
alpine 默认没 lsof。装:apk add lsof。
或者用 /proc:
ls -l /proc/<PID>/fd/ # 等价 lsof -p
坑 6:FD 列出现奇怪的字母
FD
NOFD ← 无法读取 fd 信息(权限不够)
cwd
DEL ← 已删除的内存映射
ERR ← 读 fd 失败
NOFD 通常是没权限;DEL 是 mmap 的文件已被删除,但仍在内存里映射。
坑 7:lsof 没显示 "deleted" 但 du / df 差很多
du -sh /var # 5 GB
df /var # used 15 GB
差 10 GB。lsof 应该能找到 deleted 文件,但有些类型的"被持有的空间"lsof 看不到:
- tmpfs 中的内存(restart 才释放)
- 容器 overlay 层
- 旧 kernel 中的 mmap
通用排查:
lsof | grep deleted
ls -la /proc/*/fd/* 2>/dev/null | grep deleted
坑 8:-i 和 -c 混用语义
lsof -i :80 -c nginx # 默认 OR
lsof -a -i :80 -c nginx # AND
要"既符合 -i 又符合 -c" 必须加 -a。多数 lsof 选项默认 OR,反直觉。