资讯动态

Linux lsof排障实战:端口占用、文件句柄与磁盘空间释放

发布时间:2026/10/27 19:51:23 来源:尧图企业网站定制
1. lsof 到底在查什么把一切皆文件落到排查现场在 Linux 上排查问题最怕的不是没思路而是没有抓手。端口被谁占了、磁盘满了却找不到大文件、日志删了空间没释放、某个目录卸载不掉这些问题最后都会收敛到同一个问题到底是谁还开着我这个文件。lsoflist open files就是专门干这件事的命令它把当前系统里所有进程打开的文件句柄摊开给你看包括普通文件、目录、字符设备、管道、网络套接字、Unix 域套接字。名字看着平平无奇但它是少数几个能在进程和资源之间建立映射关系的工具端口对应到 PID、磁盘对应到进程全靠它。我一般把 lsof 定位成排障工具箱里的第二把螺丝刀第一把是 ps/top 看进程状态第二把就是 lsof 看进程到底攥着什么资源。它适合的人其实很广运维和 SRE 天天在用后端开发在本地起服务端口冲突时离不开测试同学查日志文件被谁锁住也要用甚至连做嵌入式调试的人都会拿它看设备节点被哪个进程占用。这篇文章不打算罗列 man 手册而是按我平时实际排查的顺序把 lsof 的输出怎么读、参数怎么组合、哪些坑会让人误判一条条拆开讲清楚。1.1 从 /proc 遍历说起lsof 不是魔法很多人以为 lsof 是什么内核接口其实它就是个用户态程序干的事情非常朴素读取/proc下每个进程目录里的信息再加上/proc/net/下的网络表拼装出最终输出。具体来说它会进/proc/[pid]/fd/看这个进程打开了哪些描述符进/proc/[pid]/cwd、/proc/[pid]/root、/proc/[pid]/exe分别拿到工作目录、根目录、可执行文件再从/proc/[pid]/maps里读内存映射的文件从/proc/net/tcp、/proc/net/tcp6、/proc/net/udp、/proc/net/unix里拿套接字信息最后用stat()补全设备号、inode、大小这些元数据。理解这一点非常关键因为它直接解释了几个常见现象。第一lsof 跑得慢是正常的进程上千个、每个进程几百个 fd它要挨个 stat耗时天然摆在那里。第二非 root 用户看不到别人的进程不是 lsof 藏私是/proc/[pid]/fd这个目录本身就设了权限普通用户进去只会看到 Permission denied。第三在容器里跑 lsof 结果会很干净因为容器有自己的 PID 命名空间/proc只挂着容器内的进程宿主上的进程它根本看不见。我见过有同学在容器里对着lsof -i:8080没输出坚称端口没被占结果跑到宿主机上一查进程明明在跑只是两者不在同一个命名空间。这类误判的根源就是把 lsof 当成了内核查询接口其实它只是个文件系统遍历器看到什么取决于它能看到哪个/proc。提示lsof 的输出是拍快照不是实时监控。进程在你敲下回车和看到结果之间新开的 fd它不会补上。真要看变化得配合-r轮询。1.2 为什么排查端口时我还是先敲 lsof现在工具很多ss、netstat、fuser、直接读/proc/net/tcp都能查端口。但我个人的习惯是先ss -lntp确认监听状态一旦需要往下追这个进程还开了哪些文件、它的工作目录在哪、有没有配置文件被误改立刻切到 lsof。原因很简单ss 只讲网络lsof 讲全貌。举个典型场景某服务端口起来了配置却没生效怀疑读的是另一个配置文件。这时候一条lsof -p PID | grep conf就能把进程实际打开的配置路径全部列出来比翻代码找默认路径快得多。再比如怀疑某个进程在偷偷写一个临时文件导致磁盘异常增长lsof -p PID里 FIL 类型的 REG 条目按大小排一下十分钟就能定位。这就是 lsof 的真正价值它不只回答谁占了这个端口还回答这个进程此刻握着哪些真实资源。把进程当成一个实体去看它的资源持有情况很多看起来玄学的问题会瞬间变得可解释。2. 一行输出的九个字段逐列拆给你看先把默认输出摆出来逐列说清楚后面所有排查动作都建立在这套读法上。$ sudo lsof -i:8080 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME java 2317 app 45u IPv6 123456 0t0 TCP *:8080 (LISTEN) java 2317 app 88u IPv6 123459 0t0 TCP 10.0.2.11:8080-10.0.2.31:51234 (ESTABLISHED)看起来字段不多但每一列都有信息量尤其是 FD 和 NAME 这两列容易被一扫而过实际上错误往往就藏在这两个地方。2.1 COMMAND、PID、USER 三列先定位谁COMMAND 是进程名注意它默认只截取前 9 个字符超过的会被砍掉。如果你看到两个名字几乎一样的进程分不清加c参数放开宽度lsof c 20 -p 2317就能显示完整命令名。不过更稳妥的办法是直接用 PID 定位名字这东西在 Java、Python 这类进程里参考价值有限。PID 是核心。所有后续操作无论是看完整的 fd 列表、进/proc/2317看细节、还是决定要不要重启都围绕这个号。这里有个小技巧如果你的目标是脚本化用-t参数让它只输出 PID一行一个干净得可以直接喂给kill或者xargs。USER 是进程属主。这一列在排查为什么我查不到时特别有用因为非 root 用户跑 lsof别人的进程根本不会出现在结果里不是没匹配上是压根读不到。我吃过一次亏普通账号下lsof -i:3306返回空以为数据库没起实际上是被权限挡住了sudo一加就全出来了。注意很多发行版需要单独安装 lsof 包最小化安装的镜像里默认没有。command not found的时候别怀疑人生先装包。2.2 FD 列cwd、rtd、txt、mem 和数字后缀门道FD 这一列是整个输出里信息密度最高的它表示文件描述符File Descriptor或者文件的特殊用途。纯数字代表真实的 fd 编号后面跟的字母表示打开模式r读、w写、u读写。比如45u就是第 45 号描述符可读可写。这个细节在排查权限问题时很有用——你怀疑某个文件被只读打开导致写不进去看一眼模式就清楚了。除了数字还有几个特殊值必须记住cwd进程的当前工作目录。排查服务为什么找不到相对路径下的文件时这条就是答案。rtd根目录root directory。在 chroot 环境里这条会显示实际的根指向容器排障时偶尔能用到。txt程序的可执行文件和共享库。想确认某个进程加载的是哪个版本的程序看 txt 条目比看ls -l /proc/PID/exe更全面因为它会把所有映射进来的 so 都列出来。mem内存映射文件包括 mmap 的库和映射进来的数据文件。DEL标记为已删除但仍被打开的文件这个后面单独讲是省空间的利器。NOFD没有可用的 fd 信息通常是/proc/[pid]/fd读不到时的降级显示。我个人的经验是看一个陌生进程先看cwd和txt这两条基本就能判断它在干什么、跑的是哪个路径下的程序。有一次线上一个脚本行为异常cwd指向了/tmp下一个残留目录一眼就知道是启动方式有问题环境变量没设对进程在错误的工作目录下解析了相对路径配置。2.3 TYPE、DEVICE、SIZE/OFF、NODE、NAME 的配合读法TYPE 是文件类型常见的有这么几类TYPE 值含义典型场景REG普通文件日志、配置、数据文件DIR目录cwd、打开的目录句柄CHR字符设备/dev/null、/dev/urandom、终端BLK块设备磁盘分区被直接打开FIFO命名管道进程间通信IPv4 / IPv6网络套接字端口监听与连接unixUnix 域套接字本地服务间通信、数据库 socketLINK符号链接少见的直接打开链接场景DEVICE 列对磁盘文件是主次设备号形如8,1对网络文件是一串内部编号参考价值不大不用纠结。SIZE/OFF 对普通文件是文件大小对字符设备和网络套接字通常是0t0因为它表示的是偏移量而不是大小很多人在这里误以为文件是空的其实是读错了含义。NODE 列在磁盘文件上是 inode 号这个非常有用——同一个 inode 被多个进程打开时可以立刻识别出来在网络文件上它显示协议名TCP、UDP方便快速分类。NAME 列则是路径或连接四元组网络连接会写成本地地址:端口-对端地址:端口 (状态)那个括号里的状态就是 TCP 状态机LISTEN、ESTABLISHED、TIME_WAIT 全在这里。把这几列串起来读一条输出就是一个小故事。java 2317 app 45u IPv6 123456 0t0 TCP *:8080 (LISTEN)翻译成人话是app 用户跑的 java 进程PID 2317第 45 号描述符以读写方式打开是一个 IPv6 套接字正在所有地址的 8080 端口监听。读顺了之后扫一眼输出就能提取关键信息不用再逐字对照。3. 参数组合的底层逻辑默认或加 -a 才与lsof 的参数设计有一个必须提前讲清楚的规则不然用起来会怀疑命令有 bug多个选择条件之间默认是或关系不是与。也就是说lsof -p 2317 -i的意思是PID 是 2317 的文件或者所有网络文件输出会比预期大得多。想要取交集必须显式加-a。这个设计在当年是有道理的——lsof 的选择器种类多默认取并集方便一次性列出多类资源。但对新手极度不友好我见过太多人写了lsof -p 1234 -d 1然后困惑为什么输出了几百行。正确写法是lsof -p 1234 -a -d 1加一个-a立刻收敛成一条。3.1 五个过滤参数-i、-p、-u、-c、-d-i是网络过滤语法最丰富值得单独记一下lsof -i # 所有网络文件 lsof -i:8080 # 指定端口 lsof -i:8000-8100 # 端口范围 lsof -i TCP # 只 TCP lsof -i UDP:53 # UDP 的 53 lsof -i 10.0.2.11 # 指定本地或对端地址 lsof -i4 / -i6 # 按地址族过滤-p按 PID 过滤支持逗号分隔多个比如lsof -p 2317,2318。有些版本还支持^前缀取反但这个特性在不同发行版上表现不完全一致脚本里用之前先在目标机器上验一下别直接上生产。-u按用户过滤可以写用户名也可以写 UID同样支持^排除lsof -u ^root就是排除 root 的所有文件排查普通用户进程时能大幅减少噪音。-c按命令名前缀匹配注意是前缀不是精确匹配-c nginx会把 nginx 和 nginx-worker 都带上。多个-c之间是或关系-c java -c python表示两类都要。-d按 fd 类型过滤这个参数被严重低估了。-d cwd只看工作目录-d 1,2只看标准输出和标准错误-d mem只看内存映射。我在排查某个进程是不是还挂着已删除的日志文件时常用lsof -p PID -a -d 1,2直接锁定标准流比全量输出清爽得多。3.2 三个提速参数-n、-P、-b-n禁止把 IP 反解成主机名-P禁止把端口号反解成服务名。这两个参数在排查现场几乎应该默认加上因为 DNS 反查和/etc/services解析都可能成为瓶颈尤其是网络环境不干净的时候不加这两个参数一条命令能卡上半分钟。-b的作用是避开可能阻塞的内核调用。这个参数平时用不上但在挂载了网络文件系统、或者某个挂载点已经失联的机器上它能把 lsof 从卡死里救回来。我遇到过一次机器上一个远端挂载点断了lsof一跑就挂住CtrlC 都杀不掉加-b之后立刻正常输出虽然结果里会少一些信息但至少能干活。3.3 三个脚本化参数-t、-F、-w-t只输出 PID是最常用的脚本化开关lsof -t -i:8080 lsof -t -i:8080 | xargs -r kill配合-w抑制警告信息更干净否则遇到读不到权限的进程会往 stderr 刷一堆提示管道下游处理起来很烦。-F是结构化输出参数里每个字母代表一个字段比如p是 PID、c是 COMMAND、f是 fd、n是 NAME。输出格式是每行一个字段以字段字母开头遇到p开头就代表新的一条记录$ sudo lsof -F pcn -i:8080 p2317 cjava n*:8080这种格式对 awk 非常友好比解析默认的列对齐输出稳定得多。如果你要写监控脚本采集某个端口的持有进程强烈建议走-F别去awk {print $2}切列因为 COMMAND 里带空格的时候对齐会乱切出来是错的。还有一个-s参数用于指定列宽或按状态过滤-s TCP:LISTEN在网络排查里很实用能直接把 LISTEN 和 ESTABLISHED 分开省掉一次 grep。4. 六个高频现场从发现到定位到处置参数讲完进入真正干活的部分。下面这六个场景基本覆盖了我日常 90% 的 lsof 使用每个都按现象—命令—判定—处置的顺序走一遍。4.1 端口被占用三步锁定进程现象很经典启动服务报Address already in use或者bind: address already in use。sudo lsof -n -P -i:8080 sudo lsof -n -P -i:8080 -sTCP:LISTEN sudo lsof -t -n -P -i:8080第一步看全貌第二步只看监听态第三步只要 PID。输出里如果有多行注意区分 LISTEN 和 ESTABLISHED——有时候端口是被一个已经在处理请求的进程占着贸然 kill 会断掉线上流量。我一般先看 LISTEN 那条确认 PID 之后再ps -p PID -o pid,ppid,cmd看一眼父进程是谁判断是主进程还是工作进程。处置的时候有个顺序讲究先kill发 TERM等几秒还在再kill -9。直接-9会跳过进程的清理逻辑数据库类进程可能留下未完成的写入。用lsof -t -i:8080 | xargs -r kill一条命令搞定-r是 xargs 的参数表示输入为空时不执行避免误杀。还有一种情况是 lsof 查不到但端口确实起不来。这时候别死磕 lsof换ss -lntp交叉验证如果 ss 也说没占那大概率不是端口冲突而是权限问题1024 以下端口要特权或者 SELinux 之类的策略拦截方向要换。4.2 磁盘满但 du 看不到L1 抓已删除句柄这是 lsof 最经典的封神场景几乎每个运维都遇到过df -h显示磁盘 100%du -sh /*加起来的数字却差了几十 G。原因在于 Linux 的文件删除机制——rm只是把目录项摘掉inode 上的链接数减一只要还有进程持有这个文件的 fd数据块就不会释放。du是走目录树统计的它看不到这些已经没有目录项的文件所以两边对不上。sudo lsof L1 sudo lsof L1 | sort -k7 -n -r | head -20L1的意思是列出链接数小于 1 的文件也就是已经被删除但还没释放的。第一条命令出全量第二条按 SIZE/OFF 列排序取前二十。看到某个 java 进程挂着一个几 G 的xxx.log (deleted)问题就明确了。处置有两个方向。最彻底的是重启进程数据自然释放。但如果不能重启可以用清零的方式把空间还回去sudo truncate -s 0 /proc/2317/fd/45或者: /proc/2317/fd/45注意这里操作的是/proc/[pid]/fd/[fd]这个路径不是原文件路径原文件已经没了。这样做的前提是进程本身支持日志被外部截断有些程序会自己维护偏移量截断后继续写会形成空洞文件文件逻辑大小不变但实际占用变小用ls -l看还是很大用du看才是真实占用。这个差异要心里有数别以为没生效。更根本的解决办法是让应用自己管日志轮转用 logrotate 的copytruncate模式或者干脆改成按日切分加定期清理别让长跑进程一直攥着一个不断增长的句柄。4.3 文件被占用覆盖、删除、卸载都失败第二种常见情况是操作被拒绝删不掉某个文件、tar覆盖时提示Text file busy、umount报target is busy。这类问题的共同点是资源被别的进程持有lsof 就是用来把持有者揪出来的。sudo lsof /path/to/file sudo lsof d /path/to/dir sudo lsof D /path/to/dirlsof 文件路径是精确查单个文件最直接。如果文件数量多、范围大用d查目录本身不递归用D递归整个目录树。要提醒的是D的开销非常大因为它会对目录下每一个文件做 stat几万个文件的目录能跑好几分钟能不用就不用。真要批量查我一般先缩小范围按业务目录分段跑。处理完后记得确认一遍重新跑一次同样的 lsof输出为空才算真的释放了。有时候你以为 kill 掉了主进程实际上工作进程还活着攥着句柄尤其是 master-worker 模型的程序只杀 master 会留下孤儿子进程。这时候用ps -ef | grep 关键字配合确认一下进程树。关于umount失败还有一种情况是进程的 cwd 在这个挂载点里面。这时候你不仅要看谁打开了文件还要看lsof D /mnt/point | grep cwd把工作目录在里面但没打开具体文件的进程也找出来。这一步很容易漏很多人只查了具体文件没查 cwd结果umount -l强制卸载后留下一地僵尸挂载。4.4 连接堆积与状态异常按状态过滤网络问题的排查里lsof 的另一个价值是把连接状态和对端地址一次性列出来配合-s可以按状态切片。sudo lsof -n -P -i TCP:8080 -sTCP:ESTABLISHED sudo lsof -n -P -i TCP:8080 -sTCP:CLOSE_WAIT sudo lsof -n -P -i TCP:8080 | wc -l第三条用来估算连接总量虽然不如ss -s精确但足够做趋势观察。CLOSE_WAIT 堆积是经典的应用层 bug 信号——对端已经发了 FIN本端没调 close句柄一直挂着。用 lsof 按状态过滤出来能看到具体是哪个进程、连的对端是谁定位到代码里的连接池或者异常处理分支就快多了。还有一种情况是连接数看起来不高但服务不可用这时候要怀疑文件描述符上限。lsof -p PID | wc -l数一下这个进程打开了多少 fd再对比ulimit -n和/proc/[pid]/limits里的上限如果快顶到了那就是 fd 泄漏问题不在网络层。这一步很多人跳过直接去查网络方向就错了。4.5 误删文件在线抢救文件删了但进程还开着这时候数据其实还在磁盘上可以救回来。操作不复杂关键是手要快进程一重启就彻底没了。sudo lsof | grep deleted | grep important # 拿到 PID 和 FD比如 2317 和 45 sudo ls -l /proc/2317/fd/45 sudo cp /proc/2317/fd/45 /data/recover/important.log/proc/[pid]/fd/下的每个条目都是指向真实文件的符号链接虽然文件已经不在目录树里但通过这个链接依然能读到内容。cp的时候有个细节要注意目标路径一定要选另一个分区或者有足够空间的地方如果原地复制到同一个快满的磁盘很容易把空间彻底撑爆。如果文件特别大cp过程会比较久期间千万别重启进程。想要更快可以用cat重定向或者dd指定块大小。恢复完之后立刻用truncate把原句柄清零释放空间然后把这个流程写进应急手册别下次再临时现查。4.6 目录递归扫描卡住D 的代价前面提过D很慢这里补一个具体的坑。在挂载了网络存储的机器上D会把网络路径里的文件也 stat 一遍如果对端响应慢命令能挂到你怀疑人生。判断方法是加-b如果加了之后立刻返回基本可以确认是阻塞在某个挂载点上。另一个容易忽略的点是符号链接。D默认不做链接跟随所以如果目录里全是软链扫描结果可能不如预期。想要覆盖链接的情况需要另外处理但反过来想不跟随链接恰恰保证了扫描不会跑出预期范围这个默认值是合理的。我的建议是D只作为最后手段优先用fuser -v /path或者直接grep /proc/*/maps的方式做初筛确认范围之后再上D精确确认。工具没有好坏关键看代价能不能接受。5. 常见问题与坑位速查用了这么多年踩过的坑基本集中在三个维度权限和命名空间导致的看不见、性能导致的卡住、以及输出解读上的看错。这一节把这些问题归拢一下方便出问题时直接对照。5.1 权限、容器与命名空间三个盲区第一个盲区是权限。非 root 用户只能看到自己进程的文件别人的进程连/proc/[pid]/fd都进不去。表现是命令跑得飞快、结果少得可怜很容易被误读成没有占用。养成习惯凡是查系统级资源sudo先加上。第二个盲区是容器。容器内的 lsof 只能看到容器 PID 命名空间里的进程宿主上的进程一概不知。如果容器以 host 网络模式跑端口冲突的真相可能藏在宿主上容器里查永远查不出来。排查跨容器问题时正确做法是到宿主上查或者进到对应命名空间里查别在错误的地方找答案。第三个盲区是隐藏的挂载与 chroot。rtd这一列有时候会显示一个奇怪的路径说明进程处于 chroot 或者特殊的挂载环境里它看到的文件系统和你看的不一样。这种场景不多但一旦碰上按常规路径去查会完全对不上号。提示lsof报no pwd entry for UID xxx这类警告说明某个进程的属主 UID 在/etc/passwd里找不到通常是容器镜像里删了用户或者宿主同步问题不影响查询结果用-w可以静音。5.2 性能与稳定性风险lsof 的开销跟进程数和文件数成正比在进程上万台的机器上一次全量查询可能几十秒。生产环境里避免无参数裸跑lsof一定带上过滤条件-p、-i、-u随便哪个都比全量强。-r轮询模式要慎用lsof -r 1是每秒跑一次全量等于给机器持续加压。真要做监控用-F加过滤条件把范围收窄到目标进程再控制频率。还有一个容易忽略的风险lsof 会对目标文件做stat如果目标在可移动介质或者已经拔掉的挂载点上可能触发长时间的内核等待表现为命令假死。上面说的-b是应对方案但从根上解决还是要清理掉失效挂载。5.3 常见问题速查表现象可能原因处置方式命令不存在未安装 lsof 包用包管理器安装结果为空但资源确实被占非 root 权限 / 命名空间隔离加 sudo到宿主或对应命名空间查命令长时间无输出挂载点失联、网络存储阻塞加-b清理失效挂载过滤条件没生效输出过多默认是或逻辑加-a改为与端口显示成服务名看不懂端口反解加-P主机名解析卡顿DNS 反查加-n磁盘满但 du 对不上已删除文件仍被持有lsof L1定位后 truncate 或重启umount 提示 busy进程打开文件或 cwd 在挂载点内查文件同时查 cwd输出 COMMAND 列被截断默认宽度 9 字符加c放宽脚本取 PID 取错列对齐被空格破坏改用-t或-F这张表我建议直接存成笔记出问题的时候照着对比翻文档快。真实排障现场没时间研究参数手册能条件反射地想起这个现象该用哪个开关才是熟练度的体现。最后分享一个我自己的习惯排查完一个问题把当时用的完整命令记下来附一句这条命令解决了什么。半年下来就是一份贴合自己环境的命令集比任何通用教程都好用。lsof 的参数组合空间很大但真正高频的就那么十几个把这十几个用熟剩下的遇到再查完全来得及。工具的价值不在于参数背得多全而在于遇到问题时能第一时间想到它、并且知道该往哪个方向收窄。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑