1. lsof到底是什么:从“一切皆文件”说起
干过几年Linux运维或后端开发的人,一定有过这样的经历:明明服务还在跑,端口就是起不来;明明rm删了日志文件,磁盘空间却纹丝不动;明明进程没崩,业务却报“Too many open files”。这些问题有个共同点——都需要搞清楚“到底是谁动了我的文件、我的端口、我的资源”。
这时候lsof就是最趁手的排查工具。lsof的全称是List Open Files,直译过来就是“列出所有进程打开的所有资源”。它的核心逻辑建立在一个非常重要的设计理念上:在Linux世界里,一切皆文件。普通文件是文件,目录是文件,网络套接字是文件,管道是文件,设备节点也是文件,甚至共享内存、信号量这种IPC对象也以文件描述符的形式存在。所以,只要能查清楚“进程打开了哪些文件”,就等于查清楚了进程所占用的全部资源。
lsof之所以叫“全资源利器”,是因为它的信息源直接来自内核的/proc文件系统。当你执行lsof时,它会遍历系统中所有进程,读取每个进程的/proc/PID/fd目录和/proc/PID/maps等接口,把文件描述符逐一解析成可读的文件路径、协议信息、设备号、大小等。换句话说,lsof不是“拦截”文件的打开动作,而是事后“翻账单”,把内核已经记录好的信息翻译给你看。这就是它能做到“无需客户端、无需额外服务、不依赖被排查进程的任何配合”的根本原因。
和同类的几个命令相比,lsof的定位更加全面。fuser通常用于“谁占用了某个文件或端口”,ss和netstat主要专注网络连接,而lsof既能看网络,也能看普通文件、目录、设备、IPC,覆盖面最广。你在生产环境排障时,往往一个lsof就能顶好几个命令的组合拳。
提示:lsof依赖系统内核的/proc接口和动态库,几乎所有的Linux发行版都会预装或可通过包管理器快速安装(CentOS/RedHat系用yum install lsof,Ubuntu/Debian系用apt install lsof)。
2. lsof高频用法与参数拆解,干运维必须掌握的组合拳
2.1 从最简单的“查看所有打开文件”说起
不加任何参数直接执行lsof,命令会尝试把整个系统所有进程打开的每一个文件都列出来。以一台运行中的服务器为例,输出可能多达几万行甚至几十万行,内容大致长这样:
COMMAND PID TID TASKCMD USER FD TYPE DEVICE SIZE/OFF NODE NAME systemd 1 root cwd DIR 253,1 4096 2 / systemd 1 root rtd DIR 253,1 4096 2 / systemd 1 root txt REG 253,1 1624520 133958 /usr/lib/systemd/systemd sshd 1234 root mem REG 253,1 14444032 265138 /usr/lib64/libc-2.17.so sshd 1234 root 3u IPv4 1234567 0t0 TCP *:22 (LISTEN) nginx 5678 www-data 6u IPv4 8901234 0t0 TCP 10.0.0.8:80->192.168.1.100:54321 (ESTABLISHED)这里每一列的含义很明确:COMMAND是进程名,PID是进程号,USER是运行用户,FD表示文件描述符以及打开方式(cwd是当前工作目录,txt是程序可执行文件,mem是内存映射文件,数字加u/r/w表示文件描述符编号和读写权限),TYPE是文件类型(DIR目录、REG普通文件、IPv4网络套接字、CHR字符设备等),NAME则是实际的路径、协议或连接信息。
不过说句实在话,生产环境里很少有人直接全量执行不带参数的lsof,因为输出量太大,分析效率极低。恰恰相反,lsof真正的价值在于组合过滤。你用它时几乎总要带上一个或多个定位条件,让输出范围收敛到几十行以内,这样才有排障意义。
2.2 高频参数速查表
我整理了一份自己平时用得最多的参数清单,按使用频率排序,新手可以直接照抄:
| 参数 | 作用说明 | 典型使用示例 |
|---|---|---|
-i | 按网络连接过滤,支持端口、IP、协议 | lsof -i:8080只看8080端口 |
-p | 按PID查看某进程打开的文件 | lsof -p 1234查看PID 1234的资源 |
-u | 按用户过滤 | lsof -u www-data查看某用户进程打开的文件 |
-c | 按进程名前缀过滤 | lsof -c nginx查看以nginx开头的进程 |
-t | 只输出PID,不输出其他字段 | lsof -t -i:8080只看占用8080端口的PID |
+D | 递归列出某目录下被打开的文件 | lsof +D /var/log查看日志目录被谁占着 |
-d | 按文件描述符编号过滤 | lsof -p 1234 -d 1,2,3只看前三个fd |
-a | 多个条件取“与”关系 | lsof -a -u root -i:22root用户+22端口 |
-n | 不做DNS反向解析,加快速度 | lsof -nP -i:80不解析主机名和端口名 |
-P | 不把端口号换成协议名称 | 配合-n使用,避免把80显示成http |
这里特别说一下-a的用法。lsof里如果给了多个筛选条件,默认是“或”的关系,也就是说lsof -u root -i:22会把“root用户打开的所有文件”和“22端口相关的所有连接”都列出来,这通常不是你想要的结果。想表达“同时满足”的与关系,一定要显式加上-a参数,写成lsof -a -u root -i:22,它才会去交集。
2.3 网络排查三件套:端口、连接、监听
网络相关是lsof用得最频繁的场景。端口被占用、连接数异常、某个服务连不上对方,这些问题用一套组合就能搞定。
最常见的场景是“8080端口被谁占了”。不要犹豫,直接执行:
lsof -i:8080输出会告诉你哪个进程、哪个PID在监听这个端口,甚至能看到已建立的连接。如果只想拿PID去做后续处理,用-t参数:
lsof -t -i:8080这个命令只输出一个数字,干净利落。在脚本里可以直接套用kill -9 $(lsof -t -i:8080)来强制释放端口——当然,生产环境建议先看清楚这个进程是不是该杀的,别误杀了重要服务。
想看某个IP地址相关的全部连接,用:
lsof -i@192.168.1.100还可以叠加协议,只要TCP的:
lsof -iTCP@192.168.1.100这里面有个小坑我要提醒一下。lsof默认会把80端口显示成http、443显示成https这种“服务名”,这本来是为了可读性,但排查时反而容易误导。加上-P参数可以让它直接显示数字端口号,加上-n可以跳过DNS反向解析,既准确又提速:
lsof -nP -i:443我个人的习惯是,凡是排查网络问题,一律带上-nP,除非你需要看域名信息。
3. 实战复盘:四个高频故障排查,直接抄作业
3.1 场景一:端口被占用,“Address already in use”
这是最经典的问题。比如你启动一个Spring Boot应用,默认端口8080,结果报错:
BindException: Address already in use遇到这个报错,第一时间运行:
lsof -i:8080输出中会有一行处于LISTEN状态的记录,进程名和PID都写得明明白白。比如看到java 2345 user 123u IPv4 ... TCP *:8080 (LISTEN),那就说明PID 2345的Java进程占着这个端口。
接下来怎么处理,取决于业务情况。如果确认这个进程可以停,就执行kill 2345,等几秒再确认端口是否释放;要是正常kill杀不掉,再考虑kill -9 2345。这里我建议先kill后kill -9,因为kill -9直接让内核强制终止进程,不给进程任何清理资源的机会,极端情况下会留下临时文件残留或未落盘的数据丢失,能优雅退出就尽量优雅退出。
还遇到过一种比较隐蔽的情况:端口本身没有LISTEN记录,但lsof能查到一堆TIME_WAIT或CLOSE_WAIT状态的连接。此时端口并没有被“占用”,只是大量连接处于等待状态,通常是应用没有正确关闭连接导致的。这时候lsof -i:8080能帮你看到协议栈里这些滞留的连接都归属于哪个PID、远端是哪里,方便你从根源上去排查连接泄漏的bug。
3.2 场景二:磁盘空间100%,但根本找不到大文件
某天登录服务器执行df -h,发现根分区使用率达到了98%甚至100%,可你执行du -sh /一层层往下找,却始终找不到占空间的大文件。这种“空间神秘消失”的场面,十有八九是某个进程正在写一个已经删除的文件。
理解这个现象需要一点Linux文件系统的基础。当进程打开一个文件时,文件描述符指向inode;当外界执行rm删除它时,只是把目录项和inode的硬链接计数减一。如果这个文件正被某个进程打开着,inode并不会被真正回收,直到该进程关闭文件描述符或退出为止。也就是说,文件看不见了,但磁盘空间仍然被占用着。
这时候用lsof找出“幽灵文件”:
lsof | grep deleted输出里会有一行类似java 5678 root 15w REG 253,1 1073741824 345678 /var/log/app.log (deleted)的记录。这说明PID 5678的进程还在持续向这个已删除的日志文件写入数据,文件大小已经达到1GB。
处理方案有两个。一个是重启或让进程重新打开日志文件,绝大多数情况下这个办法最彻底;另一个是用> /proc/5678/fd/15直接“清空”那个已打开的文件描述符指向的文件,不需要重启进程就能释放空间。这个操作我试过几次,对临时排障非常管用,但要注意它只是把文件内容截断成0字节,进程对文件描述符的引用依然存在,写入还会继续。写日志的场景建议优先考虑配置日志轮转(logrotate),从机制上避免这个问题。
3.3 场景三:查看一个进程到底打开了哪些文件
有时业务程序表现得异常,你想知道它是不是误操作了某些路径,或者确认它有没有正常加载配置文件。这时候lsof -p就派上用场:
lsof -p 1234输出会把PID 1234当前打开的所有文件都列出来,包括工作目录、可执行程序本身、加载的共享库、配置文件、日志文件、网络连接、管道等。这里面信息量很大,排查时可以重点看几类:
- TYPE为REG的路径,确认是不是读到了预期配置文件;
- TYPE为IPv4/IPv6的记录,看进程在跟谁通信;
- cwd这一行,看进程当前工作目录是否被切换过;
- FD一列中带w的,看出没往不该写的地方写东西。
我踩过一个印象很深的坑:某个应用明明改了新的配置文件路径,重启后却还是在用旧配置参数。用lsof -p一查,发现进程加载的配置文件仍然是老的绝对路径,原因是启动脚本里环境变量覆盖了配置文件设置的路径。这类问题如果不看实际打开的fd,光靠读代码很难定位。
3.4 场景四:查看某个目录下哪些文件正被占用
当你想卸载一个挂载点、删除一个分区目录,或者重命名某个目录时,系统会提示“device is busy”或者“target is busy”。这种占用往往不像端口那样显眼,得靠lsof把占用者揪出来。
lsof +D /data这个命令会递归列出/data目录下所有正被进程打开的文件,一眼就能看到是哪个进程在占用。比如想卸载/data这个挂载点,但umount /data报错,先跑一下lsof +D /data,你会看到类似bash 1234 user cwd DIR 253,1 4096 2 /data的输出,说明有shell进程把/data当作当前工作目录,切走目录或结束该shell进程后,umount就能顺利执行。
需要注意+D对大目录有点慢,因为它是递归扫描的。如果目录非常深、文件非常多,可以考虑用lsof /data直接看目录本身,或者先缩小范围再递归。运维场景下我倒是觉得慢一点也能接受,毕竟这种“busy”问题不是高频操作。
4. 输出字段解读与提效技巧,别让lsof的输出吓到你
4.1 字段详解,15分钟看懂一行输出
lsof的输出列名常见的有COMMAND、PID、TID、USER、FD、TYPE、DEVICE、SIZE/OFF、NODE、NAME。对于新手来说,最困惑的往往是FD、TYPE和NAME这三列,我拆开讲。
FD列表示文件描述符和打开方式。数字部分就是文件描述符编号,后面跟着的字母含义比较丰富:
r:只读打开w:只写打开u:读写打开空格:未知或正在获取cwd:当前工作目录(不仅是文件,它是目录)txt:程序的可执行文件,对应mmap执行段mem:内存映射文件DEL:已经被删除但还没释放的文件映射NOFD:无法获取文件描述符信息
TYPE列则代表文件对象的类型。最常见的有:REG(普通文件)、DIR(目录)、CHR(字符设备,如终端/dev/pts/1)、BLK(块设备)、IPv4/IPv6(网络套接字)、unix(Unix域套接字)、FIFO(管道)、sock(通用套接字)。通过TYPE你可以在瞬间判断“这行记录是文件还是连接还是通道”,排障时很有用。
NAME列虽然叫“名字”,实际内容取决于TYPE。REG文件显示完整路径;IPv4连接显示本地地址、端口、远端地址、端口和连接状态,比如10.0.0.8:80->192.168.1.100:54321 (ESTABLISHED);unix套接字则显示套接字路径或抽象名。NAME里的箭头很有讲究,->表示一条已建立的连接,没有箭头只显示*:80 (LISTEN)的就是监听套接字——注意,监听套接字和已建立的连接是两种不同的fd,它们会分别单独显示。
4.2 几个能直接提高处理速度的小习惯
第一条,能不解析DNS就不解析。生产环境的服务器上DNS解析有时会超时,导致lsof命令卡顿好几秒。加-n参数禁止DNS反向解析,加-P直接显示端口数字而非服务名。排查网络时固定使用lsof -nP -i:端口,这是我个人最推荐的习惯。
第二条,用-t把输出变成纯PID。这个参数特别适合脚本化处理,省去了用awk或cut去抠PID的环节。比如要杀掉所有占用8080端口的进程,直接写:
kill $(lsof -t -i:8080)如果担心有多个PID,可以先lsof -t -i:8080确认列表无误再执行。在自动化脚本里这招非常省事。
第三条,组合-a取交集,避免输出爆炸。比如查“root用户打开的22端口连接”:
lsof -a -u root -i:22不加-a的话,输出会把root所有文件和所有22端口相关的内容混在一起,干扰判断。
第四条,关注权限问题。普通用户执行lsof时,只能看到自己有权访问的进程信息;想看所有用户所有进程的完整信息,需要root权限。所以生产环境上排障,建议直接切换到root或使用sudo,否则因为权限不足看不到列出的完整记录,反而会漏判。
5. 常见问题与排查技巧实录
5.1 lsof提示command not found,怎么办
有些精简安装的容器镜像或最小化系统不带lsof。此时直接安装即可:
# Debian/Ubuntu apt-get update && apt-get install -y lsof # CentOS/RedHat yum install -y lsof # Alpine apk add lsof如果实在无法安装,也有替代方案:端口占用可以用ss -lntp来查,进程打开的文件可以用ls -l /proc/PID/fd来手工查看。但论便捷程度,还是lsof更胜一筹。
5.2 lsof执行很慢、卡顿怎么办
前面提过,不加任何参数全盘扫描时,lsof要遍历系统所有进程的/proc信息,进程多、文件多时延迟很高。所以应对办法很简单:尽量带上明确的过滤条件,比如-p、-i、+D,只扫描需要的部分。另外加-n跳过DNS解析也能显著提速。如果你确实需要全量快照,可以考虑在服务器低峰期把lsof执行结果重定向到文件再慢慢分析:
lsof > /tmp/lsof_snapshot.txt注意快照文件里可能包含一些敏感路径和连接信息,分析完记得及时清理。
5.3 为什么lsof看不到某些进程的某些文件
最常见的原因是权限不足。普通用户执行lsof受限于内核的ptrace权限检查和/proc节点的可见性,很多其他用户的进程信息是拿不到的。另一个原因是lsof版本和内核版本不匹配,解析/proc信息时某些新类型对象识别不了,会显示“unknown”之类的占位符。遇到strace、gdb等调试工具附加到进程上的情况,lsof还可能在读取过程中看到调试器映射的临时文件,但这属于正常现象,不是程序bug。
如果你碰上容器环境,情况又复杂一些。在容器里执行lsof,看到的主要是容器自己命名空间范围内的文件列表;想查宿主机的全局列表,需要到宿主机上执行,或者给容器挂载宿主/proc目录。这个点在做容器化排查时容易踩坑,我提醒一下。
5.4 与ss、netstat配合,快速判断网络故障
lsof在网络排查上是利器,但它的强项是“按进程回查资源”。如果你需要看更详细的TCP状态统计、队列长度、重传信息,建议和ss搭配使用。比如:
# 看8080端口的监听和连接状态(比netstat快、准) ss -lntp | grep 8080 # 看某IP的连接汇总 ss -ant | grep 192.168.1.100 | wc -llsof告诉你“这个端口被谁占着”,ss告诉你“网络栈层面的状态究竟如何”,两者互补。排查顺序上,我一般先用lsof找到可疑进程,再用ss确认网络细节,或者反过来先用ss发现异常端口,再用lsof定位到具体进程。
5.5 别忽略“FD=DEL”这种隐藏信号
DEL状态意味着文件已经被删除,但是仍然有进程在引用它。这也是我们在3.2节“幽灵文件”里看到的信号。但要注意,DEL状态不一定只出现在普通文件上,也可能出现在共享内存对象(/dev/shm下的临时文件)或临时映射上。排查时如果看到很多DEL条目,可以关注一下它们是否都集中在同一个进程上,如果某个进程积累了大量的DEL文件,很可能是它频繁创建临时文件但释放不及时,长期运行下去会消耗大量磁盘空间和内存映射资源。
6. 复盘:把lsof放进日常工具箱
说了这么多,lsof其实不是一个特别复杂的命令,它的强大源于它的信息维度和过滤组合。我自己的习惯是把lsof的常用组合抄在一张便签上,贴在Shell配置文件里作为别名,比如:
alias port='lsof -nP -i' alias lsof_pid='lsof -p' alias deleted='lsof | grep deleted'日常排查时输一个别名就能快速出手。另外,学lsof时不要孤立地背参数,最好把它和/proc文件系统、文件描述符、inode这些基础概念放在一起理解。一旦你理解了“一切都是文件”的内核设计,lsof的很多用法就是水到渠成的事,不用靠死记硬背。
最后分享一个我总结的经验:真正熟练的运维,不是拿lsof去应对所有问题,而是知道什么时候该用lsof、什么时候该用fuser或ss。lsof的输出信息密度高,适合从文件视角回溯进程行为;fuser更适合“快速确认谁在占用指定文件”;ss适合深挖网络状态。工具用得准,比工具多更重要。
希望这篇文章能帮你把lsof这个命令变得顺手,下次遇上“端口起不来”“磁盘清不掉”“目录卸不掉”这些烦心事,你能第一反应想起来:先跑一个lsof看看。