kernel version: 5.1.0
现象
XFS 文件系统出现挂起(hung),业务进程大量处于 D
状态;多处堆栈显示卡在 xfs_iget
与 xfs_fs_destroy_inode
路径上。
诊断过程
关键堆栈1(最早D住的进程,释放inode卡住)
-
进程 :
postgres
-
PID :
202276
-
症状 :在
xfs_fs_destroy_inode
路径上等待,向下追溯可见正在尝试读取/获取 AGF 缓冲(xfs buf),而该锁被其他事务持有。AGF保存了空闲块的信息。#0 schedule
#1 schedule_timeout
#2 down (信号量/互斥等待)
#3 down
#4 xfs_buf_lock [xfs]
#5 xfs_buf_find [xfs]
#6 xfs_buf_get_map [xfs]
#7 xfs_trans_read_buf_map [xfs]
#8 xfs_trans_read_buf_map [xfs] (封装层)
#9 xfs_read_agf / xfs_alloc_read_agf [xfs] ← 正在尝试拿 AGF buf
#10 xfs_agf* / xfs_read_agf* [xfs]
#11 xfs_trans_read_buf [xfs]
#12 xfs_alloc_read_agf [xfs]
#13 xfs_btree_update / xfs_btree_del* [xfs]
#14 xfs_inodegc_* / xfs_ifree_cluster [xfs]
#15 xfs_inactive_ifree [xfs]
#16 xfs_destroy_inode [xfs]
#17 xfs_fs_destroy_inode [xfs] ← 释放 inode 主路径
#18 destroy_inode (VFS)
#19 evict (VFS)
#20 dentry_kill (VFS)
#21 dput (VFS)
#22 renameat2 / unlinkat (syscall)
#23 _x64_sys* (syscall)
#24 do_syscall_64
#25 entry_SYSCALL_64_after_hwframe
解析xfs_buf地址,顺着 xfs_buf → xfs_trans → xlog_ticket → task_struct.pid
反查,锁持有者落到下一条堆栈(关键堆栈2)。
关键堆栈2(锁的持有者,创建/iget 路径卡住)
-
进程 :
postgres
-
PID :
1894063
-
症状 :在
xfs_create / xfs_iget
路径;该事务已持有 AGF/AGI 相关日志项(从日志 item 链可见),同时在iget
上等待 inode 资源,构成与 #1 的 ABBA 互等。#0 __schedule
#1 schedule_timeout
#2 xfs_iget [xfs] ← iget 等待(可能循环)
#3 xfs_ilock [xfs]
#4 xfs_iunlock [xfs]
#5 xfs_dir_ialloc [xfs]
#6 xfs_ialloc [xfs] (第1次/第2次分配)
#7 xfs_create [xfs]
#8 xfs_generic_create [xfs]
#9 path_openat / do_open (VFS)
#10 do_filp_open (VFS)
#11 do_sys_openat2
#12 do_sys_open
#13 __x64_sys_openat
#14 do_syscall_64
#15 entry_SYSCALL_64_after_hwframe
关联关系:堆栈2 持有 AGF → 堆栈1 需要 AGF;堆栈1 持有 inode/inode-bp → 堆栈2 需要 inode-bp;互相等待形成系统级挂起(xfs hung + iget 死循环)。
根因
两个进程:
- 进程 1(销毁文件):正在删除文件、回收 inode;
- 进程 2(创建文件) :正在分配新的 inode。
两个进程都要去改 XFS 的元数据结构,尤其是: AGF
(空闲块信息)AGI
(inode 信息)inode cluster buffer
(一组 inode 的缓存块)
这几个结构之间是要上锁的。 如果 A 拿着 inode 的锁再去要 AGF 的锁, 而 B 拿着 AGF 的锁再去要 inode 的锁, 就会变成 "你等我,我等你" ------ 这就是典型的死锁(deadlock) 。
于是,整个 XFS 文件系统"挂死"(hung),看到的就是:- postgres 进程全在 D 状态;
xfs_iget
死循环;
修复
补丁 :xfs: use deferred frees for btree block freeing
(commit b742d7b4f0e03...
)
核心思想 :把"立刻释放"改为"延迟释放"。
技术上怎么实现的:
- 把原来直接调用的
xfs_free_extent()
改成了xfs_free_extent_later()
。
意思是: "我先把要释放的块记录到一个待办列表(deferred list)里, 等当前事务快提交时再一起处理。" - 提交阶段,这些"待释放块"会被系统安全地处理:
- 如果空间紧张,可以分多次提交;
- 不会在持有其他锁时再去改 AGF;
- 因此避免了死锁的条件。
- 同时引入了一个
xefi_agresv
参数,
确保延迟释放的块仍然走正确的空闲空间管理逻辑(防止把 AG 专用块搞乱)。