nfsv3缓存占用太多的问题

点击这里查看配套的教学视频

点击跳转到nfs课程所有目录

1 问题描述

nfsv3的文件占用缓存太多,使用echo 3 > /proc/sys/vm/drop_caches命令依然无法释放缓存。

2 20251105 vmcore分析

详细的crash输出请点击这里查看

占用缓存最多的inode地址为0xffff8dc8f6cb4380,找出超级块地址:

crash> struct inode.i_sb 0xffff8dc8f6cb4380
  i_sb = 0xffff8df33b6a5800,

在挂载信息中找不到这个超级块地址:

crash> mount | grep ffff8df33b6a5800

根据以下在虚拟机中验证可知,在导出vmcore之前,环境已经执行过umount -l

查看第一个page,可以看到private标记没有清除:

crash> kmem ffffc7b0a71abc40
      PAGE         PHYSICAL      MAPPING       INDEX CNT FLAGS
ffffc7b0a71abc40 19c6af1000 ffff8db67005d210     3773  2 17ffffc000102a error,uptodate,lru,private

文件的大小为613G:

crash> struct inode.i_size 0xffff8dc8f6cb4380
  i_size = 658510457197,

3 20251202 vmcore分析

详细的crash输出请点击这里查看

占用缓存最多的inode地址为0xffff9d9eeca42da0,找出超级块地址:

crash> struct inode.i_sb 0xffff9d9eeca42da0
  i_sb = 0xffff9dae81c1d000,

在挂载信息中找不到这个超级块地址:

crash> mount | grep ffff9dae81c1d000

根据以下在虚拟机中验证可知,在导出vmcore之前,环境已经执行过umount -l

查看第一个page,可以看到private标记没有清除:

crash> kmem fffffae836418f40
      PAGE         PHYSICAL      MAPPING       INDEX CNT FLAGS
fffffae836418f40 ad9063d000 ffff9d9eeca42f10  54e470e  2 197ffffc000102a error,uptodate,lru,private

文件的大小为613G:

crash> struct inode.i_size 0xffff9d9eeca42da0
  i_size = 658367030190,

4 构造复现

合入补丁0001-reproduce-4.19-nfsv3-cannot-drop-cache.patch

mount -t nfs -o vers=3 192.168.53.211:/tmp/s_test /mnt

测试步骤:

echo something > something
echo something_else > something_else
echo something_else_again > something_else_again
# 为什么不直接用 echo something > /mnt/file 呢,因为用ps无法查看到echo进程
cat something > /mnt/file &
cat something_else > /mnt/file &
cat something_else_again > /mnt/file &

4.1 vmcore解析

详细的crash命令输出请点击这里查看

我们看到page的flags中的private没有清除:

crash> kmem ffffea000434e4c0
      PAGE       PHYSICAL      MAPPING       INDEX CNT FLAGS
ffffea000434e4c0 10d393000 ffff8881037967f0        0  3 17ffffc000102b locked,error,uptodate,lru,private

执行完echo 3 > /proc/sys/vm/drop_caches后,还是一样。

5 正常情况调试

挂载:

mount -t nfs -o vers=3 192.168.53.209:/tmp/s_test /mnt

用户态程序test.c请点击这里查看

编译运行:

dd if=/dev/random of=/mnt/file bs=1M count=1024 # 文件大小1G
echo 3 > /proc/sys/vm/drop_caches
gcc test.c
./a.out & # 读100M数据
cd /mnt # 进入挂载点

5.1 vmcore解析

参考《内核调试方法》在虚拟机中导出vmcore。

详细的crash命令输出请点击这里查看

查看地址空间中有25728个page,每个page有4K大小,总共100M:

crash> struct address_space.nrpages 0xffff88810437dd38
  nrpages = 25728, # 执行完 echo 3 > /proc/sys/vm/drop_caches 后 nrpages 为 0

执行umount -l后重新再mount(挂载参数一样,路径可以不同),mount命令输出中包含inode所在的super block,files命令也可以找到打开这个文件的进程:

crash> mount | grep ffff88812ae61800
ffff8881002ce880 ffff88812ae61800 nfs    192.168.53.209:/tmp/s_test /mnt

crash> foreach files -R mnt
PID: 923      TASK: ffff8881045bcd40  CPU: 14   COMMAND: "a.out"
ROOT: /    CWD: /root 
 FD       FILE            DENTRY           INODE       TYPE PATH
  3 ffff88800ee72a80 ffff888004e1c000 ffff88810437dbc8 REG  /mnt/file

执行umount -l后重新再mount(挂载参数不同),mount命令输出中不包含inode所在的super block,files命令也找不到打开文件的进程:

crash> mount | grep ffff88812ae61800 # 找不到
crash> foreach files -R mnt # 没有找到

6 找出缓存大于特定值的文件

麒麟服务器v10没有vmtouch,可以在这里下载vmtouch rpm包

mount_point=/mnt
export size_threshold_mb=100

find ${mount_point} -type f -print0 | xargs -0 -n1 -P16 sh -c '
    for file do
        out=$(vmtouch -v "$file")
        pages=$(echo "$out" | awk "/Resident Pages:/ {print \$3}" | cut -d/ -f1)
        mb=$((pages*4096/1024/1024))
        if [ "$mb" -gt ${size_threshold_mb} ]; then
            echo "$file Cached_MB=${mb}"
        fi
    done
  ' sh

7 代码分析

其他相关的分析请查看以两个链接:

page设置private的地方:

nfs_inode_add_request
  SetPagePrivate(req->wb_page);

page清除private的地方:

nfs_write_error_remove_page
  // 在这里打印出inode地址
  req->wb_context->dentry->d_inode
  // 出问题的代码合入补丁 22876f540bdf NFS: Don't call generic_error_remove_page() while holding locks
  // 以下函数不会调用
  generic_error_remove_page
    truncate_inode_page
      truncate_cleanup_page
        do_invalidatepage
          nfs_invalidate_page
            nfs_wb_page_cancel
              nfs_inode_remove_request
                // 由于没调用generic_error_remove_page()
                // 所以不会执行到这里,也不会清除private标记
                ClearPagePrivate(head->wb_page);

调用nfs_write_error_remove_page()的地方:

// done
nfs_async_write_error

// todo
nfs_page_async_flush // 这个在软锁问题那里分析过,也有可能有些流程会不触发软锁

调用nfs_async_write_error()的地方:

// done
nfs_async_write_reschedule_io

// done
.error_cleanup

调用nfs_async_write_reschedule_io()的地方:

// done,pnfs不涉及
ff_layout_reset_write
  .reschedule_io

调用.error_cleanup的地方:

// todo
nfs_pageio_cleanup_request

// todo
nfs_pageio_error_cleanup

// todo
nfs_pageio_resend

8 解决方案

回退补丁14bebe3c90b3 NFS: Don’t interrupt file writeout due to fatal errors