lsof(list open files)命令用于列出系统打开的文件,因为Linux中"一切皆文件",所以该命令可以查看进程打开的文件、目录、网络连接等信息。
一、基础语法
bash
lsof [选项]
二、常用参数
| 参数 | 说明 |
|---|---|
-c 进程名 |
查看指定进程打开的文件 |
-p PID |
查看指定PID进程打开的文件 |
-u 用户名 |
查看指定用户打开的文件 |
-i |
查看网络连接 |
-i:端口号 |
查看指定端口的连接 |
-d 文件描述符 |
查看指定文件描述符 |
+d 目录 |
查看目录被哪些进程打开 |
-t |
仅输出PID(静默模式) |
-n |
不解析主机名 |
-P |
不解析端口服务名 |
三、常用示例
1. 查看所有打开的文件
bash
lsof
2. 查看指定进程打开的文件
bash
lsof -c nginx # 查看nginx进程
lsof -p 1234 # 查看PID为1234的进程
3. 查看网络连接
bash
lsof -i # 所有网络连接
lsof -i:80 # 查看80端口
lsof -i tcp # 查看TCP连接
lsof -i udp # 查看UDP连接
lsof -i @192.168.1.1 # 查看指定IP连接
4. 查看用户相关
bash
lsof -u root # root用户打开的文件
lsof -u ^root # 非root用户打开的文件
5. 查看目录/文件使用情况
bash
lsof /var/log # 查看/var/log目录
lsof /etc/passwd # 查看谁在使用passwd文件
lsof +D /var/log # 递归查看目录
6. 组合查询
bash
lsof -i -u root # root用户的网络连接
lsof -c sshd -i # sshd进程的网络连接
lsof -p 1234 -d 0-2 # 查看进程1234的标准输入输出错误
四、输出字段解析
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
nginx 1234 root cwd DIR 253,0 4096 2 /
- COMMAND:进程名称
- PID:进程ID
- USER:进程所有者
- FD :文件描述符
cwd:当前工作目录txt:程序代码mem:内存映射文件0u:标准输入,1u:标准输出,2u:标准错误3u:普通文件描述符
- TYPE:文件类型(DIR、REG、CHR、IPv4等)
- DEVICE:设备号
- SIZE/OFF:文件大小/偏移量
- NODE:索引节点号
- NAME:文件名或挂载点
五、实用场景
1. 查看端口占用
bash
lsof -i:3306
# 杀死占用端口的进程
kill -9 $(lsof -t -i:3306)
2. 恢复删除的文件
bash
# 1. 查看删除文件进程
lsof | grep deleted
# 2. 从/proc/PID/fd/FD恢复
cp /proc/1234/fd/1 /recovery/file.txt
3. 排查磁盘空间未释放
bash
# 查看已删除但仍被进程占用的文件
lsof | grep deleted
4. 查看进程打开的文件限制
bash
lsof -p PID | wc -l
六、结合其他命令
bash
# 查看哪个进程使用最多文件
lsof | awk '{print $1}' | sort | uniq -c | sort -rn | head
# 监控实时打开文件
watch -n 1 'lsof -p PID'
# 查看所有TCP连接并按程序分组
lsof -i tcp | awk '{print $1}' | sort | uniq -c
提示 :直接运行lsof会输出大量信息,建议结合具体参数使用。需要root权限查看所有进程信息。
lsof底层原理详解
一、核心思想:追踪"一切皆文件"的纽带
最核心的原理 :Linux系统中"一切皆文件",而lsof的任务就是找出哪些进程握着哪些文件的把手(文件描述符),并将这些抽象的连接转化为人类可读的信息。
想象这样一个场景:
- 每个进程就像一个工厂车间
- 每个打开的文件/网络连接/设备就像原材料或运输管道
- 文件描述符就是车间工人手里的提货单
/proc文件系统就是全厂的中央监控室
lsof就是那个拿着万能通行证在中央监控室查阅所有记录的审计员。
二、数据来源的三大支柱
1. /proc文件系统------内核的"记忆宫殿"
/proc不是一个真实的磁盘文件夹,而是内核实时生成 的虚拟视图。每当访问/proc时,内核都会:
- 动态组装信息:不像普通文件那样存储在磁盘上,而是即时生成
- 提供结构化的接口:以文件和目录形式组织,便于工具读取
- 暴露内核数据结构:进程表、文件描述符表、网络连接表等都被"翻译"成文本格式
关键目录结构:
/proc/
├── 1234/ # 进程1234的所有信息
│ ├── cmdline # 启动命令:"nginx -c /etc/nginx.conf"
│ ├── status # 进程状态:运行中、用户ID、内存使用
│ ├── fd/ # ★ 核心目录:所有打开的文件描述符
│ │ ├── 0 -> /dev/null # 标准输入
│ │ ├── 1 -> socket:[11223] # 标准输出是个套接字
│ │ ├── 2 -> /var/log/error.log # 标准错误
│ │ └── 5 -> /var/www/index.html # 打开的文件
│ ├── fdinfo/ # 每个fd的附加信息:读写位置、打开模式
│ └── maps # 内存映射:哪些文件被加载到内存中
└── net/ # 整个系统的网络连接表
├── tcp # 所有TCP连接及状态
└── udp # 所有UDP连接
2. 系统调用------直接询问内核
有些信息/proc不提供或不完整,lsof会直接"敲内核的门"询问:
- stat():这个文件的详细信息(大小、所有者、权限)
- readlink():这个符号链接到底指向哪里?
- getpwuid():用户ID 1000对应哪个用户名?
3. 特殊接口(不同系统不同方法)
- Linux :主要靠
/proc - FreeBSD :用
kvm接口直接"窥探"内核内存 - macOS :用
proc_pidinfo()等专用API - Solaris :用
/proc加ioctl命令
三、核心工作流程:如何关联进程与文件
步骤1:收集所有"嫌疑人"(进程)
lsof首先扫描/proc目录,找出所有数字命名的目录(每个数字是一个进程ID)。
好比:先拿到全厂所有车间的名单。
步骤2:检查每个"车间"的"提货单"(文件描述符)
对于每个进程,进入/proc/<PID>/fd/目录,这里每个文件代表一个打开的资源:
读取符号链接内容:
/dev/null→ 这是黑洞设备/var/log/app.log→ 这是普通日志文件socket:[1234567]→ 这是一个网络套接字,inode是1234567pipe:[987654]→ 这是一个管道
好比:检查每个车间工人的提货单,看他们拿着什么。
步骤3:破解"暗号"(特殊类型的文件)
关键难点 :socket:[1234567]这种表示什么意思?
-
提取inode:1234567是这个套接字在内核中的唯一编号
-
查找网络连接表 :去
/proc/net/tcp文件里搜索inode=1234567的那一行 -
解码连接信息 :
/proc/net/tcp中的一行: 0: 0100007F:0050 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1234567 解码后: - 本地地址:0100007F:0050 → 127.0.0.1:80 - 状态:0A → LISTEN(监听) - 用户ID:1000 → 用户uid - inode:1234567 → 匹配成功!
好比:发现一个提货单写着"3号仓库-货架A7",于是去仓库总账本查这个位置是什么货。
步骤4:收集附加信息
- 文件详细信息 :用
stat()系统调用获取文件大小、修改时间等 - 用户/组名:把数字UID(如1000)转换成用户名(如alice)
- 命令行 :从
/proc/<PID>/cmdline读取进程启动命令 - 内存映射 :从
/proc/<PID>/maps看是否有文件被映射到内存
步骤5:建立关系网
现在lsof知道了:
- 进程1234是nginx,用户是www-data
- 它打开了文件描述符5,对应
/var/www/index.html - 它还打开了文件描述符1,是个套接字,正在监听127.0.0.1:80
- 它通过文件描述符2向
/var/log/nginx/error.log写错误日志
最终输出:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
nginx 1234 www-data 5r REG 8,1 5432 100 /var/www/index.html
nginx 1234 www-data 1u IPv4 12345 0t0 TCP 127.0.0.1:80 (LISTEN)
nginx 1234 www-data 2w REG 8,1 102400 200 /var/log/nginx/error.log
四、关键技术难点与解决方案
难点1:性能问题------成千上万进程怎么办?
解决方案:
- 懒加载 :用户不要求的信息就不查(如不指定
-i就不读网络表) - 智能缓存:用户名/组名转换结果缓存起来,避免反复查系统
- 并行处理:现代lsof用多线程同时查多个进程
难点2:动态环境------进程边查边消失
解决方案:
- 容错处理:如果查某个进程时它正好退出,跳过而不是崩溃
- 快照思想 :lsof输出是某个瞬间的状态,不是连续监控
难点3:权限墙------普通用户能看到什么?
安全机制:
- root用户:能看到所有进程的所有文件
- 普通用户:只能看到自己的进程
- 实现方式 :
/proc/<pid>目录有权限控制,非自己的进程目录打不开
难点4:复杂类型------不只是普通文件
处理策略:
- 设备文件 :
/dev/sda1→ 解析主设备号/次设备号知道是磁盘 - 目录:目录也是一种"打开的文件"
- 匿名文件 :比如
[anon_inode:inotify],是内核内部使用的
五、特殊场景处理
场景1:容器化环境
问题 :容器里进程的"文件"可能在宿主机上路径不同
解决方案:
- 通过
/proc/<pid>/ns/识别命名空间 - 对于挂载命名空间不同的进程,可能需要特殊路径转换
- 使用
/proc/<pid>/root访问容器内的文件系统视图
场景2:网络连接状态追踪
详细流程:
1. 在/proc/1234/fd/发现符号链接指向"socket:[112233]"
2. 去/proc/net/tcp逐行扫描,找inode=112233的行
3. 找到后解码:
- 本地地址:将"0100007F:0016"从16进制转成点分十进制 → 127.0.0.1:22
- 远程地址:将"00000000:0000" → 0.0.0.0:0(表示监听)
- 状态:"0A"转成TCP状态码 → LISTEN
4. 如果是UDP,去/proc/net/udp找
5. 如果是IPv6,去/proc/net/tcp6或udp6找
场景3:查找"谁在用这个文件"
逆向查找过程:
1. 用户问:谁打开了/var/log/syslog?
2. lsof获取这个文件的设备号和inode号(假设是8,1和456789)
3. 遍历所有进程的/proc/<pid>/fd/目录
4. 对每个符号链接,获取它指向的实际文件信息
5. 比较设备号和inode号是否匹配(8,1 + 456789)
6. 匹配成功!发现是进程999的fd 3
六、与其他工具的关系
ps:只关注进程本身,不关注它打开了什么文件netstat/ss:只关注网络连接,不关注是哪个进程的fuser:类似lsof的简化版,专门查"谁在用这个文件/端口"strace:动态追踪系统调用,太重;lsof是静态查看当前状态
七、设计哲学
lsof体现了Unix设计的几个核心理念:
- "一切皆文件":网络连接、设备、管道都是文件
- "提供机制,而非策略":lsof只展示信息,怎么用由用户决定
- "透明性" :通过
/proc让内核内部状态透明可见 - "文本化接口" :
/proc用文本文件格式,方便各种工具处理
八、实际示例:找出谁占用了80端口
当您运行lsof -i :80时:
- 解析参数:知道用户要查80端口
- 预读网络表 :读取
/proc/net/tcp和/proc/net/tcp6,建立inode->连接信息的映射 - 遍历进程 :对每个进程:
- 打开
/proc/<pid>/fd/目录 - 发现符号链接指向
socket:[xxxxx] - 查表:inode xxxxx对应的本地端口是80吗?
- 如果是,记录这个进程和文件描述符
- 打开
- 补充信息:获取进程名、用户名等
- 格式化输出
总结
lsof的底层原理可以概括为:通过解析内核暴露的/proc文件系统,追踪每个进程与系统资源(文件、网络、设备等)之间的连接关系,将抽象的内核数据结构转化为人类可读的列表。
它就像系统的全局连接追踪器,无论进程打开了普通文件、网络连接,还是管道、设备,lsof都能找到这些连接,并告诉你"谁连着谁"。这种能力使得lsof成为系统调试、安全审计和性能分析的瑞士军刀。