virtiofs ENFILE 问题排查手册
六月 13, 2026 [linux, virtualization] #virtiofs #debug #qemu #libvirt #fuse #file-descriptorvirtiofs ENFILE 问题排查手册
问题现象
VM 内执行文件操作时报错:
ls: 无法打开目录 '.': 系统中打开的文件过多
# 对应系统错误: ENFILE (Too many open files in system)
排查步骤
1. 系统级文件句柄状态
cat /proc/sys/fs/file-nr
# 输出格式: <已分配数量> <空闲数量> <最大限制>
# 如果第 2 列 = 0 表示文件表已耗尽
cat /proc/sys/fs/file-max
cat /proc/sys/fs/nr_open
2. 定位 virtiofsd 进程
pgrep -a virtiofsd
ps -o pid,ppid,user,etime,rss,args -p $(pgrep -d',' virtiofsd)
# rss: 内存占用 etime: 运行时长
3. 检查 virtiofsd fd 数量
# 精确计数
sudo -A ls /proc/<PID>/fd | wc -l
# 间接估算(fd 目录字节数 ≈ 30×fd数)
stat /proc/<PID>/fd | grep Size
4. 查看 fd 类型分布
sudo -A ls -la /proc/<PID>/fd/ | awk '{
target=$NF
if (target ~ /^socket:/) type="socket"
else if (target ~ /^pipe:/) type="pipe"
else if (target ~ /^anon_inode:/) type="anon_inode"
else if (target ~ /\(deleted\)$/) type="deleted_file"
else if (target ~ /^\//) type="regular_file"
else if (target ~ /^\/dev\//) type="dev"
else type="other"
count[type]++
} END { for(t in count) print count[t], t }' | sort -rn
5. 查看 fd 指向哪些目录
sudo -A ls -la /proc/<PID>/fd/ | awk '{print $NF}' | \
sed 's|/home/peter/project||' | \
awk -F'/' '{print $1"/"$2"/"$3"/"$4}' | \
sort | uniq -c | sort -rn | head -30
6. 采样查看 fd 具体内容
# 前 40 个
sudo -A ls -la /proc/<PID>/fd/ | head -40
# 中间段
sudo -A ls -la /proc/<PID>/fd/ | sed -n '5000,5020p'
# 末尾
sudo -A ls -la /proc/<PID>/fd/ | tail -20
7. 检查 virtiofsd 内存和资源限制
for pid in <PID_LIST>; do
echo "=== PID $pid ==="
cat /proc/$pid/limits | grep -E "open files|processes"
cat /proc/$pid/status | grep -E "VmRSS|VmSize|Threads"
done
8. 检查 FUSE 连接参数
ls /sys/fs/fuse/connections/
for conn in /sys/fs/fuse/connections/*/; do
echo "=== $(basename $conn) ==="
cat "$conn/max_background" 2>/dev/null
cat "$conn/congestion_threshold" 2>/dev/null
cat "$conn/waiting" 2>/dev/null
done
# max_background 默认 12,congestion_threshold 默认 9
9. 检查 QEMU 进程
ps aux | grep -E "qemu|libvirt" | grep -v grep
for pid in <QEMU_PID_LIST>; do
stat /proc/$pid/fd | grep Size
done
10. 全系统 fd 统计
total=0
for p in /proc/[0-9]*/fd/; do
total=$((total + $(ls "$p" 2>/dev/null | wc -l)))
done
echo "用户可见: $total"
echo "系统分配: $(awk '{print $1}' /proc/sys/fs/file-nr)"
echo "差值(root): $(( $(awk '{print $1}' /proc/sys/fs/file-nr) - total ))"
11. 查找 fd 最大的进程
for p in /proc/[0-9]*/fd/; do
size=$(stat -c%s "$p" 2>/dev/null)
[ "$size" -gt 1000 ] 2>/dev/null && \
echo "Size=$size PID=$(basename $(dirname $(dirname $p))) Name=$(cat /proc/$(basename $(dirname $(dirname $p)))/comm 2>/dev/null)"
done | sort -t= -k2 -rn | head -10
根因分析
debian13 VM 内某个进程 (IDE/编译器/文件搜索)
→ 遍历 /home/peter/project 下数十万文件
→ virtiofsd 为每个 open() 在宿主机分配一个真实 fd
→ 数小时内累积数十万 fd + 数 GB 内存
→ 宿主机 file-nr 耗尽 (allocated=1,015,480, free=0)
→ 其他 VM 尝试访问 virtiofs → ENFILE
判断标准
| 症状 | 结论 |
|---|---|
file-nr 第 2 列 ≈ 0 | 文件表已饱和 |
| virtiofsd fd 总数 > 10 万 | 严重泄漏 |
| fd 99% 为常规文件 | VM 内进程遍历文件系统 |
fd 集中在 build/cache、node_modules、target/debug 等 | 构建工具或 IDE 扫描 |
| virtiofsd RSS > 数 GB | 内存与 fd 同步泄漏 |
修复方案
临时:释放 fd
# 重启 virtiofsd(会短时断开 VM 内挂载)
sudo -A kill -HUP <virtiofsd_PID>
VM 内:清理缓存
# 创建定时脚本
cat > /usr/local/bin/manage_virtiofs_cache.sh << 'EOF'
#!/bin/bash
CACHED_MB=$(free -m | awk '/^Mem:/ {print $6}')
if [ $CACHED_MB -gt 4096 ]; then
sync
echo 3 > /proc/sys/vm/drop_caches
fi
EOF
chmod +x /usr/local/bin/manage_virtiofs_cache.sh
# crontab: 每 30 分钟执行
# */30 * * * * /usr/local/bin/manage_virtiofs_cache.sh
VM 内:调整内核参数
echo 1000 > /proc/sys/vm/vfs_cache_pressure
echo 1000 > /proc/sys/vm/dirty_writeback_centisecs
# 持久化
cat >> /etc/sysctl.conf << EOF
vm.vfs_cache_pressure=1000
vm.dirty_writeback_centisecs=1000
EOF
永久:修改 libvirt 配置
<filesystem type='mount' accessmode='passthrough'>
<driver type='virtiofs' queue='1024'/>
<source dir='/home/peter/project'/>
<target dir='project'/>
<binary path='/usr/libexec/virtiofsd' xattr='on'>
<cache size='8192'/>
</binary>
</filesystem>
核心排查逻辑
file-nr 异常高
→ ps 找 virtiofsd 进程
→ ls /proc/<pid>/fd | wc -l 数 fd
→ ls -la /proc/<pid>/fd/ 看类型
→ awk 按目录聚合 找来源
→ 定位 VM 内是什么进程在遍历文件