文章目录
-
- 一、前言
- [二、实验环境:一台 NVIDIA Jetson](#二、实验环境:一台 NVIDIA Jetson)
- [三、`fuser` 是怎么"知道"谁在用某个文件的](#三、
fuser是怎么"知道"谁在用某个文件的) - 四、最常用的两个姿势
-
- [4.1 谁在占用某个 TCP 端口](#4.1 谁在占用某个 TCP 端口)
- [4.2 谁在占用某个文件](#4.2 谁在占用某个文件)
- [五、看懂 ACCESS 列的六个字母](#五、看懂 ACCESS 列的六个字母)
- 六、实战场景:把"占着茅坑"的进程揪出来
-
- [场景 1:`umount /` 报 busy 怎么办](#场景 1:
umount /报 busy 怎么办) - [场景 2:`/dev/shm` 满了,到底是谁塞的](#场景 2:
/dev/shm满了,到底是谁塞的) - [场景 3:MQTT 端口 1883 被谁占了](#场景 3:MQTT 端口 1883 被谁占了)
- [场景 4:CUPS 打印服务 631 端口](#场景 4:CUPS 打印服务 631 端口)
- [场景 5:RPC 端口 111(UDP)](#场景 5:RPC 端口 111(UDP))
- [场景 1:`umount /` 报 busy 怎么办](#场景 1:
- 七、进阶:把占用进程杀掉
- [八、`fuser` vs `lsof` vs `ss`:什么时候用哪个](#八、
fuservslsofvsss:什么时候用哪个) - 九、几个写脚本时常踩的坑
-
- [1. 不加 `sudo` 看到空输出](#1. 不加
sudo看到空输出) - [2. `-v` 输出在脚本里不友好](#2.
-v输出在脚本里不友好) - [3. 端口写法要带协议](#3. 端口写法要带协议)
- [4. `-m` 接的应该是 mount point 或块设备,不是普通文件](#4.
-m接的应该是 mount point 或块设备,不是普通文件)
- [1. 不加 `sudo` 看到空输出](#1. 不加
- 十、完整命令速查表
- [十一、回到那台 Jetson:一次"全身体检"](#十一、回到那台 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 做的事情就是:
- 把你给的那个"名字"解析成内核能识别的内核对象(inode、socket 的 local_port、mount point 等);
- 扫描
/proc/*/fd/、/proc/*/maps、/proc/*/cwd、/proc/*/root、/proc/*/exe,逐个进程对比; - 命中的进程 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,更短。
注意 PID 1 是 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 个固定位置的标志位,每个位置代表一种访问方式:
| 位置 | 字母 | 含义 |
|---|---|---|
| 1 | c |
当前目录(cwd),进程的 /proc/<pid>/cwd 指向该文件 |
| 2 | e |
可执行文件,进程的 /proc/<pid>/exe 指向该文件 |
| 3 | f |
打开的文件(open file),进程的 /proc/<pid>/fd/ 中有此文件 |
| 4 | r |
根目录(root),进程的 /proc/<pid>/root 指向该文件 |
| 5 | m |
内存映射(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 将占用文件的进程杀掉
八、fuser vs lsof vs ss:什么时候用哪个
很多人会问:既然 lsof 也能查端口、查文件,为什么还要 fuser?
| 需求 | fuser |
lsof |
ss/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 目录。所以"看到空输出"有两种含义:
- 真没人占用;
- 有人占用,但不是你 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。
十、完整命令速查表
把全文涉及到的命令汇总一份,方便复制使用:
bash
# 1. 端口(最常用)
sudo fuser -v 22/tcp # 谁在用 TCP 22
sudo fuser -v 1883/tcp # 谁在用 MQTT 1883
sudo fuser -v 111/udp # 谁在用 UDP 111
sudo fuser -v -n tcp 22 # -n 显式语法,等价上面
# 2. 文件 / 目录
sudo fuser -v /etc/hostname # 谁打开了某文件
sudo fuser -v /tmp # 谁打开了某目录
sudo fuser -s /etc/hostname; echo $? # 静默判断,看返回值
# 3. 整个文件系统
sudo fuser -v -m /dev/nvme0n1p1 # 谁在用这块盘
sudo fuser -v -m / # 谁在用根分区
sudo fuser -M /mnt/usb # 仅当是挂载点时才查
# 4. IPv4/IPv6 限定
sudo fuser -v -4 22/tcp # 只看 IPv4
sudo fuser -v -6 22/tcp # 只看 IPv6
# 5. 版本与帮助
fuser -V
fuser # 无参数显示 Usage
十一、回到那台 Jetson:一次"全身体检"
最后,把全文涉及到的命令拼起来,对 192.168.37.56 这台 Jetson 做一次完整的资源占用"全身体检":
bash
# 在 Jetson (192.168.37.56) 上执行
sudo fuser -v 22/tcp 631/tcp 1883/tcp 111/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 |
| CPU | 12 × ARM aarch64, 最高 2601 MHz |
| 内存 | 60 GiB(10 GiB 已用) |
| 根分区 | /dev/nvme0n1p1 → / (467 GiB, 9% 已用) |
| Swap | 2 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 |