卸载nfsv3挂载点时报错device is busy
,但用lsof +D <挂载点>
和fuser -m <挂载点>
都无法找到使用挂载点的进程。
用以下命令打开nfs日志开关(参考《nfs调试方法》):
echo 0xFFFF > /proc/sys/sunrpc/nfs_debug
# echo 0 > /proc/sys/sunrpc/nfs_debug # 在生产环境中关闭日志请执行这个命令
kprobe抓进程信息(参考《内核调试方法》):
kprobe_func_name=nfs_file_open # 或者 nfs_file_read
cd /sys/kernel/debug/tracing/
cat available_filter_functions | grep ${kprobe_func_name}
echo 1 > tracing_on
echo "p:p_${kprobe_func_name} ${kprobe_func_name}" >> kprobe_events
echo 1 > events/kprobes/p_${kprobe_func_name}/enable
echo stacktrace > events/kprobes/p_${kprobe_func_name}/trigger # 打印栈
# echo '!stacktrace' > events/kprobes/p_${kprobe_func_name}/trigger # 关闭栈
# echo 0 > events/kprobes/p_${kprobe_func_name}/enable
# echo "-:p_${kprobe_func_name}" >> kprobe_events
echo 0 > trace # 清除trace信息
cat trace_pipe
挂载:
mount -t nfs -o vers=3 localhost:/tmp /mnt
在用户态通过创建两个线程,不断打开又关闭文件,编译运行thread-open-file-short-time.c
:
gcc -o thread-open-file-short-time thread-open-file-short-time.c -lpthread
./thread-open-file-short-time
这时无法卸载nfs,且用lsof +D <挂载点>
和fuser -m <挂载点>
都无法找到使用挂载点的进程:
umount /mnt # umount.nfs: /mnt: device is busy
lsof +D /mnt # 找不到进程
fuser -m /mnt # 找不到进程
用上面的kprobe trace抓到以下信息:
thread-open-fil-956 [010] .... 7723.365916: p_nfs_file_open: (nfs_file_open+0x0/0x60 [nfs])
956
是线程id,用以下命令查看完整的进程名:
ps -eLf | grep 956
在内核空间打开文件,用lsof +D <挂载点>
和fuser -m <挂载点>
无法找到进程。
源码文件如下:
挂载:
mount -t nfs -o vers=3 localhost:/tmp /mnt
加载ko,打开并读文件/mnt/dir/file
,注意这个操作不要在生产环境中尝试:
mkdir /mnt/dir -p
echo something > /mnt/dir/file # 创建文件
echo 3 > /proc/sys/vm/drop_caches
insmod kernel-open-file.ko
日志请查看nfs-umount-device-is-busy-log.txt
:
...
[ 122.567308] NFS: open file(dir/file)
...
[ 122.571219] NFS: read(dir/file, 4096@0) ...
这时我们卸载nfs挂载点就能得到一样的报错信息,且无法找到使用挂载点的进程:
umount /mnt # umount.nfs: /mnt: device is busy
lsof +D /mnt # 找不到进程
fuser -m /mnt # 找不到进程
移除ko后,在内核关闭了文件,就能正常卸载nfs挂载点了:
rmmod kernel_open_file # 在内核中关闭文件
umount /mnt # 正常卸载,不报错
系统调用的跟踪调试请查看《文件系统延迟卸载》。
只有在用户空间打开文件时会把文件描述符放到files_struct -> fdt
中:
openat
do_sys_open
do_sys_openat2
fd_install rcu_assign_pointer(fdt->fd[fd], file);
在内核空间打开文件时,不会把文件描述符加到fdtable
中,fuser -m
和lsof +D
无法遍历到文件描述符,所以无法找到打开文件的进程。
可以用kprobe-fd_install.c
调试, 其中mydebug_dump_stack()
相关的用法可以查看《mydebug模块》。