☰
fuser
2026/9/29 6:25:28 网站建设 项目流程

文章目录

    • 一、前言
    • 二、实验环境:一台 NVIDIA Jetson
    • 三、`fuser` 是怎么"知道"谁在用某个文件的
    • 四、最常用的两个姿势
      • 4.1 谁在占用某个 TCP 端口
      • 4.2 谁在占用某个文件
    • 五、看懂 ACCESS 列的六个字母
    • 六、实战场景:把"占着茅坑"的进程揪出来
      • 场景 1:`umount /` 报 busy 怎么办
      • 场景 2:`/dev/shm` 满了,到底是谁塞的
      • 场景 3:MQTT 端口 1883 被谁占了
      • 场景 4:CUPS 打印服务 631 端口
      • 场景 5:RPC 端口 111(UDP)
    • 七、进阶:把占用进程杀掉
    • 八、`fuser` vs `lsof` vs `ss`:什么时候用哪个
    • 九、几个写脚本时常踩的坑
      • 1. 不加 `sudo` 看到空输出
      • 2. `-v` 输出在脚本里不友好
      • 3. 端口写法要带协议
      • 4. `-m` 接的应该是 mount point 或块设备,不是普通文件
    • 十、完整命令速查表
    • 十一、回到那台 Jetson:一次"全身体检"
    • 十二、结语
      • 附录:本次实验的环境与版本

一、前言

很多 Linux 资源排障文章会告诉你 “先用lsof看一眼”,但lsof在很多嵌入式 / 最小化镜像里根本没有装。而fuser来自psmisc包,体积小、依赖少,几乎是所有 Debian/Ubuntu 系镜像(包括 NVIDIA Jetson 的 L4T 镜像)默认就在的工具。它做的事情很专一:

给我一个"名字"(文件、目录、块设备、TCP/UDP 端口),我把正在使用它的进程 PID 全部列出来。

在边缘设备上,你经常会遇到这些场景:

  • NVMe SSD 显示 busy,umount报target is busy;
  • 想重启某个服务,但端口被占着,不知道是谁;
  • /dev/shm莫名其妙被塞满;
  • 串口/dev/ttyACM0被某个进程占着打不开;
  • 系统里跑着一堆python3、jtop、mosquitto,想知道它们都在摸哪些资源。

fuser就是干这些活的。


二、实验环境:一台 NVIDIA Jetson

为了让后面的输出有上下文,先把目标设备192.168.37.56的真实信息贴出来(SSH 登录后采集):

$ hostname localhost.localdomain $ cat /etc/os-release | grep PRETTY PRETTY_NAME="Ubuntu 24.04.3 LTS" $ uname -a Linux localhost.localdomain 6.8.12-rt-tegra #1 SMP PREEMPT_RT Tue Sep 1 12:48:16 CST 2026 aarch64 aarch64 aarch64 GNU/Linux $ lscpu | head -10 Architecture: aarch64 CPU op-mode(s): 64-bit CPU(s): 12 Vendor ID: ARM Model name: - Thread(s) per core: 1 Core(s) per cluster: 12 CPU max MHz: 2601.0000 $ free -h total used free shared buff/cache available Mem: 59Gi 10Gi 54Gi 27Mi 1.5Gi 48Gi Swap: 2.0Gi 0B 2.0Gi $ df -h | grep -vE 'tmpfs|efivarfs' Filesystem Size Used Avail Use% Mounted on /dev/nvme0n1p1 467G 38G 405G 9% /

几个关键点:

  • 这是一台ARM64 / aarch64架构的 NVIDIA Jetson(Tegra SoC)边缘设备;
  • 跑的是PREEMPT_RT 实时内核(6.8.12-rt-tegra),常见于机器人、感知、控制等对时延敏感的场景;
  • 12 核 ARM、60GB 内存、467GB NVMe SSD;
  • 根分区在/dev/nvme0n1p1,挂载点/;
  • 已经默认跑着cupsd、mosquitto、rpcbind、systemd-resolved、jtop(Jetson 监控工具)、lttng-sessiond等。

fuser在这台设备上的版本:

$ fuser -V fuser (PSmisc) 23.7 $ which fuser /usr/bin/fuser

注意 PSmisc 23.7 是较新的版本,输出格式和某些老教程里的略有不同,但语义完全一致。下面所有命令都在这台 Jetson 上实际执行过。


三、fuser是怎么"知道"谁在用某个文件的

在开始敲命令之前,先用一句话讲清原理,否则你只会把它当黑盒。

Linux 把"进程打开了哪些文件"这种关系全部保存在/proc/<pid>/fd/下——每个打开的文件描述符(fd)都是一个符号链接,指向真实文件或 socket。fuser做的事情就是:

  1. 把你给的那个"名字"解析成内核能识别的内核对象(inode、socket 的 local_port、mount point 等);
  2. 扫描/proc/*/fd/、/proc/*/maps、/proc/*/cwd、/proc/*/root、/proc/*/exe,逐个进程对比;
  3. 命中的进程 PID 打印出来。

正因为它是从/proc读出来的,所以普通用户只能看到自己的进程,要看其他用户(root、mosquitto、_rpc 等)的占用,必须sudo。下面所有示例都加了sudo,否则你会看到"空输出"而误以为没人占用。


四、最常用的两个姿势

4.1 谁在占用某个 TCP 端口

这是日常最高频的场景:“我的 22 端口被谁占了?”

$ sudo fuser -v 22/tcp USER PID ACCESS COMMAND 22/tcp: root 1 F.... systemd root 2218 F.... sshd root 8511 F.... sshd nvidia 8574 F.... sshd root 8617 F.... sshd nvidia 8656 F.... sshd root 8863 F.... sshd nvidia 8906 F.... sshd

字段含义:

  • USER:进程属主;
  • PID:进程号;
  • ACCESS:访问类型(下面专门解释);
  • COMMAND:进程名。

22/tcp这种写法是fuser自己的语法糖——“协议/端口号”。等价的写法是用-n显式指定命名空间:

$ sudo fuser -v -n tcp 22 USER PID ACCESS COMMAND 22/tcp: root 1 F.... systemd root 2218 F.... sshd root 8511 F.... sshd ...

这两种写法等价,记住一种就行。我个人推荐22/tcp,更短。

注意 PID1是systemd本身,因为它负责 socket activation,会先把 22 端口"占住",等真实sshd起来后再交给它——这是 systemd 资源管理的正常表现,不是异常。

4.2 谁在占用某个文件

$ sudo fuser -v /etc/hostname $

没有输出,说明这一刻没有进程正打开着/etc/hostname。这点很重要:fuser 的"空输出"是一个有效结果,意思是没人占用。在自动化脚本里判断一个文件是否被占用,可以用-s(silent)模式,结合返回值判断:

$ sudo fuser -s /etc/hostname; echo $? 1

返回值1表示"没有进程占用",0表示"有进程占用",非常适合写脚本。

再看一个有占用的例子:

$ sudo fuser -v /tmp USER PID ACCESS COMMAND /tmp: root 1567 ..c.. atopacctd

atopacctd把/tmp作为工作目录(cwd),所以fuser把它列出来了。..c..这一串字母描述的就是访问类型。


五、看懂 ACCESS 列的六个字母

-v输出里最容易让新手困惑的就是中间那串像F....、..c..、F...m一样的字母。它们其实是 6 个固定位置的标志位,每个位置代表一种访问方式:

位置字母含义
1c当前目录(cwd),进程的/proc/<pid>/cwd指向该文件
2e可执行文件,进程的/proc/<pid>/exe指向该文件
3f打开的文件(open file),进程的/proc/<pid>/fd/中有此文件
4r根目录(root),进程的/proc/<pid>/root指向该文件
5m内存映射(mmap),出现在/proc/<pid>/maps中
6.该位置无对应访问

所以:

  • F....—— 该进程打开了此 fd(F 实际是f的大写形式,表示 “current file” 是直接打开的,PSmisc 23.x 在第一列用大写以强调);
  • ..c..—— 进程把该路径作为 cwd;
  • F...m—— 既被打开,又被 mmap 进内存(典型如jtop占用/dev/shm);
  • frce.—— cwd + root + open,常出现在容器或systemd这种根进程上。

把这段记下来,看fuser输出就再不会觉得它像"乱码"了。


六、实战场景:把"占着茅坑"的进程揪出来

下面这些都是在192.168.37.56这台 Jetson 上实际复现过的真实场景。

场景 1:umount /报 busy 怎么办

Jetson 的根文件系统在/dev/nvme0n1p1,挂载到/。你想换 SD 卡 / 换 NVMe,或者想从备份镜像里挂载一个相同分区做比较,但mount时报错:

mount: /mnt: /dev/nvme0n1p1 already mounted or mount point busy.

显然,根分区正在被整个系统占用。用-m选项,列出所有把这个文件系统当成 mount 用的进程:

$ sudo fuser -v -m /dev/nvme0n1p1 | head -30 USER PID ACCESS COMMAND /dev/nvme0n1p1: root kernel swap /swapfile root kernel mount / root 1 frce. systemd root 2 .rc.. kthreadd root 3 .rc.. pool_workqueue_release root 4 .rc.. kworker/R-rcu_g root 5 .rc.. kworker/R-rcu_p root 6 .rc.. kworker/R-slub_ root 7 .rc.. kworker/R-netns root 9 .rc.. kworker/0:0H-events_highpri root 11 .rc.. kworker/u24:0-ext4-rsv-conversion root 12 .rc.. kworker/R-mm_pe root 13 .rc.. rcu_tasks_kthread root 14 .rc.. rcu_tasks_rude_kthread root 15 .rc.. rcu_tasks_trace_kthread root 16 .rc.. ksoftirqd/0 root 17 .rc.. ktimers/0 root 18 .rc.. pr/legacy root 19 .rc.. rcu_preempt root 20 .rc.. rcub/0 root 21 .rc.. rcuc/0 root 22 .rc.. migration/0 root 23 .rc.. irq_work/0 root 24 .rc.. cpuhp/0 ...

注意前两行很特别:

root kernel swap /swapfile root kernel mount /

这两行不是进程,而是内核自己把/dev/nvme0n1p1用作swap 源和挂载点。对于根分区来说,这种"占用"是结构性的,你不可能通过kill任何进程来解决——除非进入单用户模式或者重启到 initramfs,否则永远 busy。

这就是为什么在 Jetson 上扩容 rootfs、做备份还原时通常要走 USB recovery 模式或者 Live 系统而不是在线操作:fuser一眼就能告诉你"这是结构性占用,别挣扎了"。

场景 2:/dev/shm满了,到底是谁塞的

Jetson 跑jtop时,监控数据会写到共享内存。有一天df -h /dev/shm显示 100% 满,谁干的?

$ sudo fuser -v -m /dev/shm USER PID ACCESS COMMAND /dev/shm: root kernel mount /dev/shm root 1277 ....m lttng-sessiond root 3978 F...m jtop root 4018 F...m jtop root 4026 F...m jtop nvidia 4648 ....m python3 nvidia 4871 ....m python3 nvidia 4874 ....m python3 nvidia 4878 ....m python3 nvidia 4879 ....m python3 nvidia 4880 ....m python3 nvidia 4881 ....m python3

一眼看到几个"嫌疑犯":

  • jtop(PID 3978、4018、4026)—— 同时有F(打开 fd)和m(mmap),典型的共享内存消费者;
  • 一堆python3(4648 起)—— 这些很可能是用户跑的脚本,比如处理摄像头帧的推理服务,把模型或者中间张量扔进了 shm;
  • lttng-sessiond(1277)—— Linux trace 框架,也用 shm 做环形缓冲区。

如果继续排查,可以用lsof /dev/shm/看具体哪些文件名被打开,但fuser在不知道文件名的情况下能直接给出"占用者名单",这是它的独门优势。

场景 3:MQTT 端口 1883 被谁占了

ss -tlnp显示127.0.0.1:1883在 LISTEN,但没显示进程名(因为进程属于别的 user)。fuser直接穿透:

$ sudo fuser -v 1883/tcp USER PID ACCESS COMMAND 1883/tcp: mosquitto 2227 F.... mosquitto

mosquitto用专门的mosquitto用户跑,所以普通用户ss看不到 PID,但sudo fuser一击命中。

场景 4:CUPS 打印服务 631 端口

$ sudo fuser -v 631/tcp USER PID ACCESS COMMAND 631/tcp: root 2617 F.... cupsd

只有一个cupsd进程,干净利落。

场景 5:RPC 端口 111(UDP)

fuser同样支持 UDP:

$ sudo fuser -v 111/udp USER PID ACCESS COMMAND 111/udp: root 1 F.... systemd _rpc 1016 F.... rpcbind

_rpc是 Debian/Ubuntu 给 rpcbind 专门开的低权用户。注意systemd又出现在这里——这是它的 socket activation 机制在接管 socket,等rpcbind起来后由它接手。


七、进阶:把占用进程杀掉

使用 kill 将占用文件的进程杀掉

八、fuservslsofvsss:什么时候用哪个

很多人会问:既然lsof也能查端口、查文件,为什么还要fuser?

需求fuserlsofss/netstat
查谁打开了某文件✅ 直接给 PID✅ 但需要lsof <file>❌ 不支持
查谁占某端口✅port/tcp✅lsof -i :port✅ss -tlnp
查某 mount point 的占用者✅-m一行命令⚠️lsof +D /mnt慢得多❌
输出 PID 给脚本✅ 默认就是 PID⚠️ 要解析文本⚠️ 要-p等额外参数
在最小化镜像上可用✅ psmisc 极轻❌ 默认不装✅ iproute2 通常有

总结一句:fuser是"端到端最短路径"工具——给它一个名字,它要么给你 PID 列表,要么直接帮你杀进程。在 Jetson 这种最小化嵌入式 Linux 上,lsof经常不在,但fuser几乎永远在,这是它的"出生优势"。


九、几个写脚本时常踩的坑

1. 不加sudo看到空输出

这是最常见的误判。fuser从/proc/<pid>/fd/读,普通用户没权限读别人的 fd 目录。所以"看到空输出"有两种含义:

  1. 真没人占用;
  2. 有人占用,但不是你 user,你无权看。

脚本里要么sudo,要么用-s配合返回值并明示检查权限。

2.-v输出在脚本里不友好

-v是给人看的,给脚本用时建议去掉-v,直接拿默认的 PID 输出:

$ sudo fuser /tmp 1567

干净一行 PID,方便for pid in $(sudo fuser /tmp)。

3. 端口写法要带协议

# 错:fuser -v 22 ← 会被当成"名叫 22 的文件" # 对:fuser -v 22/tcp # 对:fuser -v -n tcp 22

4.-m接的应该是 mount point 或块设备,不是普通文件

$ sudo fuser -m /dev/nvme0n1p1 ✅ $ sudo fuser -m / ✅ / 是 mount point $ sudo fuser -m /etc/hostname ❌ 普通文件,相当于普通 fuser

如果只想在"必须是 mount point"时才查,用-M:

$ sudo fuser -M /mnt/usb

只在/mnt/usb真的是一个挂载点时才返回结果,避免误把目录当 mount。

十、完整命令速查表

把全文涉及到的命令汇总一份,方便复制使用:

# 1. 端口(最常用)sudofuser-v22/tcp# 谁在用 TCP 22sudofuser-v1883/tcp# 谁在用 MQTT 1883sudofuser-v111/udp# 谁在用 UDP 111sudofuser-v-ntcp22# -n 显式语法,等价上面# 2. 文件 / 目录sudofuser-v/etc/hostname# 谁打开了某文件sudofuser-v/tmp# 谁打开了某目录sudofuser-s/etc/hostname;echo$?# 静默判断,看返回值# 3. 整个文件系统sudofuser-v-m/dev/nvme0n1p1# 谁在用这块盘sudofuser-v-m/# 谁在用根分区sudofuser-M/mnt/usb# 仅当是挂载点时才查# 4. IPv4/IPv6 限定sudofuser-v-422/tcp# 只看 IPv4sudofuser-v-622/tcp# 只看 IPv6# 5. 版本与帮助fuser-Vfuser# 无参数显示 Usage

十一、回到那台 Jetson:一次"全身体检"

最后,把全文涉及到的命令拼起来,对192.168.37.56这台 Jetson 做一次完整的资源占用"全身体检":

# 在 Jetson (192.168.37.56) 上执行sudofuser-v22/tcp631/tcp1883/tcp111/udp\/tmp /dev/shm /var/log /dev/nvme0n1p1

一次跑完,你会看到:

  • 22/tcp:systemd+ 一堆sshd;
  • 631/tcp:cupsd;
  • 1883/tcp:mosquitto;
  • 111/udp:systemd+rpcbind;
  • /tmp:atopacctd;
  • /dev/shm:jtop、若干python3、lttng-sessiond;
  • /dev/nvme0n1p1:内核 swap/mount + 几乎所有进程。

一张"谁在用谁"的全景图就这样画出来了。下次设备出问题(比如 NVMe 满了、shm 满了、某端口起不来),你只要把这台 Jetson 当"活体标本"——把上面这一串命令跑一遍,问题就藏不住。


十二、结语

fuser是一个有"古老智慧"的小工具:它没有花哨的功能,只做一件事——告诉你"谁在用这个名字"。但正因为专注,它做到了:

  • 在最小化镜像里都能装下;
  • 一个命令同时覆盖文件、目录、块设备、TCP/UDP 端口;
  • 输出可以直接喂给kill,从"诊断"到"治疗"无缝衔接。

在一台 NVIDIA Jetson 边缘设备上跑了一遍真实场景后,最大的感受是:很多看似复杂的问题(umount busy、端口冲突、shm 满),其实只差一个fuser -v就能定位到根因。这就是"小工具、大用处"的真正含义。

下次遇到"target is busy"或者"端口被占",别再瞎kill -9了——先fuser -v,看清楚再说。


附录:本次实验的环境与版本

项目值
设备NVIDIA Jetson (Tegra)
系统Ubuntu 24.04.3 LTS (Noble Numbat)
内核6.8.12-rt-tegra SMP PREEMPT_RT aarch64
CPU12 × ARM aarch64, 最高 2601 MHz
内存60 GiB(10 GiB 已用)
根分区/dev/nvme0n1p1→/(467 GiB, 9% 已用)
Swap2 GiB swapfile
fuser 版本PSmisc 23.7,/usr/bin/fuser
监听端口53(Lo), 631, 22, 111, 1883(Lo), 5345(Lo), 6566, 5201
在跑服务systemd, sshd, cupsd, mosquitto, rpcbind, jtop, lttng-sessiond, atopacctd, gnome-shell

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询