virtiofs ENFILE 问题排查手册

六月 13, 2026 [linux, virtualization] #virtiofs #debug #qemu #libvirt #fuse #file-descriptor

virtiofs 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/cachenode_modulestarget/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 内是什么进程在遍历文件