lsof命令的基础用法及底层原理

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 :用/procioctl命令

三、核心工作流程:如何关联进程与文件

步骤1:收集所有"嫌疑人"(进程)

lsof首先扫描/proc目录,找出所有数字命名的目录(每个数字是一个进程ID)。

好比:先拿到全厂所有车间的名单。

步骤2:检查每个"车间"的"提货单"(文件描述符)

对于每个进程,进入/proc/<PID>/fd/目录,这里每个文件代表一个打开的资源:

读取符号链接内容

  • /dev/null → 这是黑洞设备
  • /var/log/app.log → 这是普通日志文件
  • socket:[1234567] → 这是一个网络套接字,inode是1234567
  • pipe:[987654] → 这是一个管道

好比:检查每个车间工人的提货单,看他们拿着什么。

步骤3:破解"暗号"(特殊类型的文件)

关键难点socket:[1234567]这种表示什么意思?

  1. 提取inode:1234567是这个套接字在内核中的唯一编号

  2. 查找网络连接表 :去/proc/net/tcp文件里搜索inode=1234567的那一行

  3. 解码连接信息

    复制代码
    /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知道了:

  1. 进程1234是nginx,用户是www-data
  2. 它打开了文件描述符5,对应/var/www/index.html
  3. 它还打开了文件描述符1,是个套接字,正在监听127.0.0.1:80
  4. 它通过文件描述符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设计的几个核心理念:

  1. "一切皆文件":网络连接、设备、管道都是文件
  2. "提供机制,而非策略":lsof只展示信息,怎么用由用户决定
  3. "透明性" :通过/proc让内核内部状态透明可见
  4. "文本化接口"/proc用文本文件格式,方便各种工具处理

八、实际示例:找出谁占用了80端口

当您运行lsof -i :80时:

  1. 解析参数:知道用户要查80端口
  2. 预读网络表 :读取/proc/net/tcp/proc/net/tcp6,建立inode->连接信息的映射
  3. 遍历进程 :对每个进程:
    • 打开/proc/<pid>/fd/目录
    • 发现符号链接指向socket:[xxxxx]
    • 查表:inode xxxxx对应的本地端口是80吗?
    • 如果是,记录这个进程和文件描述符
  4. 补充信息:获取进程名、用户名等
  5. 格式化输出

总结

lsof的底层原理可以概括为:通过解析内核暴露的/proc文件系统,追踪每个进程与系统资源(文件、网络、设备等)之间的连接关系,将抽象的内核数据结构转化为人类可读的列表

它就像系统的全局连接追踪器,无论进程打开了普通文件、网络连接,还是管道、设备,lsof都能找到这些连接,并告诉你"谁连着谁"。这种能力使得lsof成为系统调试、安全审计和性能分析的瑞士军刀。

相关推荐
cui_win6 个月前
Linux问题排查-找到偷偷写文件的进程
linux·运维·服务器·进程·lsof
孤城2861 年前
Linux下lsof命令使用
linux·运维·服务器·netstat·lsof
岁月标记1 年前
lsof 命令
lsof
威迪斯特2 年前
Linux系统运维命令:终止监听在 TCP端口80上的所有进程(使用lsof,grep,awk组合命令, 终止监听在 TCP某个端口上的所有进程)
linux·运维·服务器·tcp/ip·grep·awk·lsof