1、已经删了文件,但是df查看时,发现磁盘空间还被占着?
2、明明没跑几个应用,文件描述符使用率报警了?
3、吊炸天的重定向到底是什么原理?
4、被删掉的但是还没被释放的文件,有没有可能还能恢复?
5、都说linux的世界”一切皆文件“,那到底是什么意思?
6、清理了入侵系统的挖矿木马,不到10分钟,它换了个名字又跑起来了?
7、为什么一个查看打开文件的命令,居然可以用来排查各种各样的故障?
lsof = list open files,能列出系统中所有打开的文件。
FD = File Descriptor,文件描述符。
上面这些疑惑其实都跟linux的核心设计理念有关,即 ”一切皆文件“。
从用户视角来看,linux系统里发生的所有与I/O设备和通信资源相关的一切,都是在操作文件,
它把块设备、进程、网络连接、管道等等统统抽象为文件,然后提供统一的文件接口来操作他们。
所以 lsof 不仅能看普通文件,还能看网络连接、设备、管道等。
因此学会lsof在排查问题时,就又多了一件趁手的神兵利器!
# opsnot.com - 常见的文件类型:
REG # 普通文件
DIR # 目录
CHR # 字符设备
BLK # 块设备
FIFO # 命名管道
unix # UNIX 域套接字
IPv4 # IPv4 网络连接
IPv6 # IPv6 网络连接
PIPE # 匿名管道
DEL # 已删除文件
如果把linux世界看作一个图书馆,那lsof就是档案查阅员,注意:它只有查阅权限,无管理权限。
# by opsnot.com
图书馆系统 (Linux 内核)
│
├── 图书分类法 (VFS 虚拟文件系统) - 统一所有"书籍"的借阅规则
│
├── 各种书籍类型:
│ ├── 普通图书 (REG 文件)
│ ├── 期刊杂志 (DIR 目录)
│ ├── 电子设备 (CHR/BLK 设备文件)
│ ├── 电话线路 (IPv4/IPv6 网络连接)
│ ├── 内部传话 (PIPE/FIFO 管道)
│ └── 会议记录 (unix 套接字)
│
├── 借书证 (FD 文件描述符) - 每个进程的"借阅权限"
│
└── 档案查阅员 (lsof) - 只能查看借阅记录,不能管理书籍
而FD就是借书证,它记录了每个程序的借阅权限,随时供lsof查阅
# opsnot - 重要的 FD 类型:
cwd # 当前工作目录
txt # 程序代码(二进制文件或共享库)
rtd # 根目录
mem # 内存映射文件
DEL # 已删除但仍在使用的文件
附:lsof工作流程
// 简化的 lsof 工作流程
1. 遍历 /proc/<pid>/fd/ 目录
2. 读取每个文件描述符的符号链接
3. 通过 VFS 接口获取文件信息:
- 文件类型(TYPE)
- 设备号(DEVICE)
- 文件大小(SIZE)
- inode 号(NODE)
- 文件路径(NAME)
# 精准查找被删除但进程还占用的文件
# +L1 表示查找链接数小于1的文件(即已删除)
lsof +L1
# 或者用传统方式(opsnot.com)
lsof -nP | grep deleted
**输出示例**:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
nginx 12345 opsnot 7w REG 8,1 2147483648 0 123456 /var/log/nginx/opsnot-access.log (deleted)
原理:文件虽然 rm 了,但进程的文件描述符还开着,磁盘空间不会释放。
解决方案:
# 方法1:重启(简单粗暴)
systemctl restart nginx
# 方法2:截断文件(加班哥推荐:不停服)
# 先确认 PID 和 FD
> /proc/12345/fd/7
# 查看 8080 端口被谁占用
lsof -nP -i :8080
# 输出
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 8964 opsnot 42u IPv6 98765 0t0 TCP *:8080 (LISTEN)
# 查看端口范围
lsof -nP -i :8000-9000
# 加班哥提醒:比 netstat -nltp 更快,输出更直观。记得加 -nP 参数,否则会很慢。
# 查看 nginx 进程的所有文件
lsof -c nginx
# 或者用 PID
lsof -p 1234
# 只看网络连接(注意 -a 参数)
lsof -c nginx -a -i
加班哥重要提醒: -a 表示 AND 条件,不加就是 OR
# 错误写法:会输出 nginx 的所有文件 + 系统所有网络连接(这输出绝对不是你想要的)
lsof -c nginx -i
# 正确:只输出 nginx 的网络连接
lsof -c nginx -a -i
# 查看某个文件被谁打开
lsof /var/log/mysql/error.log
# 卸载 U 盘时提示 device is busy
lsof /mnt/usb
# 只查一层目录(快)
lsof +d /var/log
# 递归查目录(慢,慎用!!!)
lsof +D /var/log
# 查看所有 TCP 连接
lsof -nP -i TCP
# IPv4 TCP 连接
lsof -nP -i4TCP
# IPv6 UDP 连接
lsof -nP -i6UDP
# 查看特定 IP 的连接
lsof -nP -i @192.168.1.100
# 查看 ESTABLISHED 状态的连接
lsof -nP -i TCP -s TCP:ESTABLISHED
# 持续监控(opsnot 运维脚本)
watch -n 1 'lsof -nP -i TCP -s TCP:ESTABLISHED | wc -l'
# 查看 opsnot 用户打开的网络连接
lsof -u opsnot -a -i
# 查看除了 root 外的所有用户
lsof -u ^root
# 查看端口 3306 且状态是 LISTEN
lsof -nP -i :3306 -a -s TCP:LISTEN
再次提醒: 多个条件必须加 -a,否则是 OR 关系
# 只显示 PID
lsof -t -i :80
# 批量 kill(慎用!)
# 先用 SIGTERM,给进程清理资源的机会
kill $(lsof -t -i :8080)
# 等几秒后如果进程还在,再用 SIGKILL
sleep 3
kill -9 $(lsof -t -i :8080)
# 自定义输出字段(适合脚本解析)
lsof -F pcn -i :80
加班哥提醒: kill -9 会强制杀死进程,不给它任何清理机会(关闭文件、断开连接等),可能导致数据丢失。生产环境慎用!
# lsof 默认会做 DNS 反解析和端口转服务名,很慢
# 生产环境必加这两个参数
lsof -nP -i
# -n:不做主机名解析(避免 DNS 查询)
# -P:不做端口名解析(避免读 /etc/services)
#!/bin/bash
# opsnot.com - 文件描述符监控脚本
THRESHOLD=1000
PID=$1
if [ -z "$PID" ]; then
echo "Usage: $0 <pid>"
exit 1
fi
while true; do
FD_COUNT=$(lsof -p $PID 2>/dev/null | wc -l)
echo "[$(date '+%Y-%m-%d %H:%M:%S')] FD Count: $FD_COUNT"
if [ $FD_COUNT -gt $THRESHOLD ]; then
echo "WARNING: FD count exceeds $THRESHOLD"
# 发送告警(接入你的告警系统)
fi
sleep 5
done
#!/bin/bash
# 统计每个进程的网络连接数 - opsnot
lsof -nP -i TCP -s TCP:ESTABLISHED | \
awk 'NR>1 {print $1, $2}' | \
sort | uniq -c | \
sort -rn | head -10
**输出**:
127 tomcat 8964
45 nginx 1234
23 redis 5678
CLOSE_WAIT 连接#!/bin/bash
# 诊断 CLOSE_WAIT 连接 - opsnot.com
# 加班哥提醒:CLOSE_WAIT 通常是应用没有正确关闭 socket
# 直接干掉进程可能导致数据丢失,这个脚本只做诊断
echo "=== CLOSE_WAIT 连接统计 ==="
lsof -nP -i TCP -s TCP:CLOSE_WAIT | \
awk 'NR>1 {count[$1]++} END {
for (cmd in count)
print cmd, count[cmd]
}' | sort -k2 -rn
echo -e "\n=== 详细列表(PID + 命令 + 远程地址)==="
lsof -nP -i TCP -s TCP:CLOSE_WAIT | \
awk 'NR>1 {print $2, $1, $9}' | sort -n
echo -e "\n建议:检查应用代码是否正确关闭了 socket"
为什么不直接 kill:
CLOSE_WAIT 是应用层问题,不是系统问题 直接杀进程可能导致正在处理的请求丢失 加班哥建议:应该先去排查代码,看是否有资源泄露
# 每 2 秒监控一次 8080 端口
lsof -nP -r 2 -i :8080
# -r 参数:重复执行模式
# Ctrl+C 停止
lsof 很慢怎么办# 1. 加 -nP 参数(必须)
lsof -nP -i
# 2. 缩小查询范围
lsof -c nginx -a -i # 只查 nginx 的网络连接
# 3. 避免全量扫描
lsof +D /path # 慢,会递归扫描所有子目录
lsof +d /path # 快,只扫描一层
# 4. 用 -F 格式化后再处理(适合脚本)
lsof -Fn -i :80 | grep '^p' | cut -c2- # 只获取 PID
# 5. 在负载高的系统上使用,-b 避免 lsof 自身阻塞
lsof -b -nP -i
# sudo 普通用户只能看自己的进程,看不到其他用户的。生产环境排查问题时记得 `sudo`。
sudo lsof -nP -i :80
# 能看到所有进程
# 只看关键信息
lsof -nP -i | awk '{print $1, $2, $8, $9}'
# 或者用 grep
lsof -nP -i | grep ESTABLISHED
# 统计而不是列出
lsof -nP -i TCP -s TCP:ESTABLISHED | wc -l
# 基础过滤
-a AND 条件(多个选项时必须)
-c 按命令名过滤
-p 按 PID 过滤
-u 按用户过滤(^user 排除某用户)
-i 网络连接
# 性能优化
-n 不做主机名解析
-P 不做端口名解析
-b 避免阻塞
# 输出控制
-t 只输出 PID
-F 字段格式化输出(适合脚本)
-R 显示父进程 PID
# 文件过滤
+d 非递归目录(快)
+D 递归目录(慢)
+L1 查找已删除的文件
# 网络过滤
-i4 只看 IPv4
-i6 只看 IPv6
-i TCP 只看 TCP
-i :80 只看 80 端口
-s TCP:ESTABLISHED 按状态过滤
# 循环执行
-r N 每 N 秒重复执行
-F 专用格式化参数
# 牛逼的 -F
lsof -F? # 命令行输入这个,可以返回-F的帮助信息
a 访问权限,r = 只读;w = 只写;u = 读写
c 打开文件的进程命令名
d 设备标识符的字符形式
D 以 0x<十六进制> 格式显示的主/次设备号
f 打开文件的文件描述符编号
G 以 0x<十六进制> 格式显示的文件标志
i 文件在文件系统中的 inode 编号
k 文件的硬链接计数
K 任务标识符(TID)
l 锁状态,r/R = 读锁;w/W = 写锁;u = 读写锁
L 打开文件进程的登录用户名
m 在重复输出之间的分隔标记
M 任务的完整命令名称
n 文件名、注释或网络地址信息
o 以 0t<十进制> 或 0x<十六进制> 格式显示的文件偏移
p 打开文件进程的进程标识符(PID)
g 进程组标识符(PGID)
P 网络连接使用的协议名
r 以 0x<十六进制> 格式显示的原始设备号
R 父进程标识符
s 文件的大小(字节)
S 流模块和关联的设备名称
t 文件的类型(如普通文件、目录、套接字等)
T TCP或TPI相关网络连接信息
u 打开文件进程的用户标识符(UID)
0 字段终止符,使用NUL字符(\0)而非换行符(NL)作为字段分隔符
lsof 是排查问题的利器,但也要注意:
- 生产环境务必加 -nP,否则很容易卡住,别坏人没找到,枪先卡壳了
- 多条件查询记得加 -a,否则是 OR 关系
- 不要频繁全量扫描,对系统有性能影响
- CLOSE_WAIT 不要无脑杀进程,先排查应用代码
- 慎用 kill -9,先用 SIGTERM,你要温柔,才会有魅力
相信看到这里,开篇的问题已经能自行解决了,恭喜!
更多强大排障利器:
Strace命令,Linux系统调用追踪神器!
运维火眼金睛之 - tcpdump抓包实操手册
Kubectl Describe,k8s故障排查利器!
Docker Inspect,值得族谱单开一页的命令
更多运维技术请点击左下方 阅读原文
本文由 opsnot.com 整理,转载请注明出处,点击下方卡片关注