目录标题
- ✅ 概览(一句话版本)
- 1) dentry 的内部(数据结构与生命周期 — 很重要)
- struct dentry(关键字段与含义)
- dentry 类型
- 生命周期(核心)
- 2) slab allocator(SLAB/SLUB/SLOB)与 dentry 的关系
- 3) `/proc/slabinfo` 字段解析(如何精确计算内存占用)
- 4) dentry 为什么会暴涨?常见根因(实战经验)
- 5) 诊断/排查步骤与命令(从概览到定位进程)
- 快速查看
- 计算 dentry 内存(脚本)
- 找出哪些目录 inode 很多 / 哪些目录文件多
- 找出频繁 open/create 的进程(实时)
- 找出谁在大量创建删除文件
- 哪些进程持有大量 fd(可能是大量打开文件)
- 6) 如何判断“是不是 dentry 导致的问题” — 指标与阈值(更精确)
- 7) 线上缓解与根本解决(从轻到重、并说明副作用)
- 临时缓解(会短暂影响性能)
- 调整回收策略(推荐先试)
- 文件系统/应用层面优化(根本)
- FS 层面(如果是特定 FS)
- 8) 防止复现的长期策略(监控+报警)
- 9) 高级排查(内核级,必要时用)
- 10) 常见误区纠正(避免踩坑)
- 11) 常用脚本/命令汇总(可直接复制使用)
- 12) 小结(如何把握优先级)
- 1) 快速结论(先给结论省时间)
- 2) 为什么会有这么高的 load(简要解释)
- 3) 你现在可以立即做的诊断命令(按优先级,直接跑并把结果贴回我看)
- 4) 根据你当前 slabtop / top 的发现,我的建议(实战)
- 5) 我现在能帮你做的事(二选一或都可以)
- 6) 补充 — 你可能关心的数据(我已从你输出总结)
✅ 概览(一句话版本)
- dentry= VFS 的目录项缓存(每个路径组件一个 dentry),用于加速路径解析与复用目录元数据。
- slab(slab/slub/slob)= 内核为小对象频繁分配提供的分配器。dentry 就是“被 slab 管理的一类对象”。
- 过多的 dentry 会占用 slab(内核内存),影响可用内存并导致内核频繁回收,影响性能。
1) dentry 的内部(数据结构与生命周期 — 很重要)
(面向有内核/低层背景的读者,尽量贴近 linux 源码逻辑)
struct dentry(关键字段与含义)
d_name:目录项名(字符串 + length)d_parent:指向父 dentry 的指针(形成树)d_inode:关联的 inode 指针(NULL 表示 negative dentry)d_count(引用计数):当前被多少地方持有(dget/dput 增减)d_flags:状态位(例如 DCACHE_AUTOMOUNT 等)d_lock(spinlock)/RCU 链表项:用于并发与哈希链d_hash:hash table 中的哈希节点,用于快速查找同名 dentryd_time/ LRU 链表:回收优先级用
dentry 类型
- positive dentry:指向真实 inode 的 dentry(文件/目录存在)
- negative dentry:表示某路径不存在(避免频繁 stat 查不到而打到 FS)
生命周期(核心)
- 路径解析(lookup)时:如果 hash 中无,创建 dentry(d_alloc) -> 可能立即填充 inode(d_splice_alias / inode->i_iget)。
- 使用时引用计数 ++(dget); 不使用时 dput(引用计数减为0时放入 LRU 回收队列)。
- 内核回收有两套机制:主动回收(shrinkers)+内存压力触发,还有
vfs_cache_pressure控制 inode/dentry 的回收倾向。
2) slab allocator(SLAB/SLUB/SLOB)与 dentry 的关系
- slab allocator 管理同类小对象(固定 objsize)的缓存池。每种对象类型(例如
dentry)都会有自己的缓存(cache)。 - slab 内部按 slab(页)分块,每页能放 N 个 obj。
/proc/slabinfo和slabtop就是从这里取统计数据。 - 当 dentry 被创建,会从
dentryslab cache 分配一块,释放时放回 slab cache(未必马上归还给页分配器)。
3)/proc/slabinfo字段解析(如何精确计算内存占用)
典型行(示例):
dentry 1538418 1588545 192 42 2 : tunables 0 0 0 : slabdata 37823 37823 0字段按顺序(常见格式):
nameactive_objs(活动对象数,当前仍被引用/在使用的)num_objs(总对象数,包含 free 的)objsize(每个对象的大小,bytes)obj_per_slab(每 slab 能放多少对象)pages_per_slab(每 slab 占用多少页): tunables ... : slabdata ...(更细的 runtime 数据,slab 数等)
常用计算方式(两种):
- 近似法(简单):
mem ≈ active_objs * objsize—— 快速估算活跃对象占用 - 精确法(按 slab):
num_slabs = slabdata_active(或用 num_objs / obj_per_slab 向上取整) →total_mem = num_slabs * pages_per_slab * PAGE_SIZE
(PAGE_SIZE 通常 4096)
你之前的示例:
1538418 * 192 ≈ 295,373,000 B ≈ 282 MB—— 就是用粗略法,足够判断级别。
4) dentry 为什么会暴涨?常见根因(实战经验)
- 程序遍历大量目录(例如:递归扫描 /data、
find /、备份脚本、误写的 for 循环) - 应用频繁创建/删除大量小文件(短生命周期文件)
- 容器/Pod 日志文件无限增长或不停 rotate(产生大量 inode/dentry)
- 监控/agent/安全软件频繁 stat/fstat/scan(例如 antivirus、文件完整性检测)
- NFS/网络文件系统异常导致 negative dentry 增多(缓存的不存在路径)
- bug:程序对文件系统做了“热”操作(如无限循环 open/close)
5) 诊断/排查步骤与命令(从概览到定位进程)
我按从“快排查”到“精查”的顺序给命令和脚本。遇到线上问题时按这个走能快速落点。
快速查看
# slab 总览cat/proc/slabinfo|egrep'dentry|inode|buffer_head|kmalloc'# slabtop 交互式(实时)slabtop -s c -o# 内存概览free-h;vmstat15;top-b -n1|head-20# page cache 大小grep-i'^Cached:'/proc/meminfogrep-i'^Active:'/proc/meminfo计算 dentry 内存(脚本)
awk'/^dentry/ {printf "active=%d, num=%d, objsize=%d => approx_mem=%0.2fMB\n",$2,$3,$4,($2*$4)/1024/1024}'/proc/slabinfo找出哪些目录 inode 很多 / 哪些目录文件多
# top dirs by # of inodes (目录级别统计)fordin/*;doecho"$(find"$d"-xdev -type f2>/dev/null|wc-l)$d";done|sort-n# 更深的按目录列 inode count(慢)find/ -xdev -printf'%h\n'2>/dev/null|sort|uniq-c|sort-nr|head找出频繁 open/create 的进程(实时)
- 推荐用bcc / eBPF:
opensnoop-bpfcc(需要 bcc 工具)
# 需要安装 bcc-toolsopensnoop-bpfcc -t5# 监控 5 秒内的 open- 或用
sysdig/strace -f -p(重):
sysdig evt.type=open and fd.name contains /path# sysdig 筛选找出谁在大量创建删除文件
- 使用
inotify/auditd/eBPF 工具来追踪unlink/open/creat系统调用:
# bpftrace 例子:统计每个 pid 的 open 系统调用计数sudobpftrace -e'tracepoint:syscalls:sys_enter_openat { @[comm]++; }'哪些进程持有大量 fd(可能是大量打开文件)
lsof|awk'{print$1}'|sort|uniq-c|sort-nr|head# or per pidforpidin$(ls/proc|egrep'^[0-9]+$'|head);doecho-n"$pid";ls/proc/$pid/fd|wc-l;done|sort-k2 -n|tail6) 如何判断“是不是 dentry 导致的问题” — 指标与阈值(更精确)
- 观察
/proc/slabinfo:dentry的active_objs * objsize占总内存比例。 - 结合
free:如果available很低,kswapd占 CPU 高且 slab 中 dentry 占显著比例 → dentry 可能是主要原因。 - 经验阈值(实践):当 dentry 占用超过系统内存的 5–10%时值得警惕;超过20%则很可能影响系统(但具体看 workload)。
(你机器 47G 下 280MB = 0.6%,完全安全 —— 你之前的结论正确)
7) 线上缓解与根本解决(从轻到重、并说明副作用)
临时缓解(会短暂影响性能)
# 清 page cacheecho1>/proc/sys/vm/drop_caches# 清 dentry+inodeecho2>/proc/sys/vm/drop_caches# 清 page+inode+dentryecho3>/proc/sys/vm/drop_caches注意:这是无害的“缓存丢弃”操作,但会让系统重新热加载缓存,短期内可能降低性能。不能作为根本长期策略。
调整回收策略(推荐先试)
# 提高内核回收 dentry 的积极性(默认 100)sysctl -w vm.vfs_cache_pressure=200# 永久写入 /etc/sysctl.confvfs_cache_pressure越高,内核越倾向回收 dentry/inode(但可能增加 I/O,因为要频繁重新 stat/read)。
文件系统/应用层面优化(根本)
- 减少频繁创建/删除短文件:使用一批预分配文件或内存队列/缓存。
- 拆分大目录:避免单目录内大量文件(Hash 分桶)。
- 日志轮转/压缩策略:合理设置 logrotate,限制容器日志;对容器日志启用 log rotation 或限速。
- tmpfs:短期高频文件放 tmpfs(内存)减少磁盘 inode/dentry 压力,但要看内存预算。
- mount options:
noatime/nodiratime减少无谓写操作(对创建/读取压力帮助有限,但常见优化)。 - 调整应用:避免在高并发场景中频繁
stat、scandir。
FS 层面(如果是特定 FS)
- XFS/EXT4 有不同 inode/dentry 行为:例如对小文件很多的场景,用 XFS+合理 inode 配置可能更优。
- 对已有磁盘,可考虑增加文件系统的 directory hash / readdir 性能 tuning。
8) 防止复现的长期策略(监控+报警)
- 监控
dentry大小(使用 prometheus node_exporter 的 slab 或者自写 exporter 抓/proc/slabinfo) - 监控
Cached/Buffers/Active/Available,结合kswapdCPU 占用报警 - 监控文件系统中文件数(
df -i)和单目录文件数量 - 记录开、关机时的 baseline slabinfo,异常时对比
9) 高级排查(内核级,必要时用)
- ftrace:trace d_alloc/dput 行为,找出哪个 task 调用频繁
- kernel slab leak detection / kmemleak:如果怀疑内核对象泄露(不是普通的 cache 增大)
- perf:查看系统在 file IO/kswapd/softirq 等处消耗
- BPF 程序:写 tracepoints 统计
lookup/open/unlink按 comm/pid/path 的热点
示例 bpftrace 统计 open 系统调用按进程:
sudobpftrace -e'tracepoint:syscalls:sys_enter_openat { @[comm] = count(); }'10) 常见误区纠正(避免踩坑)
- 误以为 dentry 是 page cache 的一部分:两者不同,dentry 属于 slab(对象缓存),page cache 属于页缓存;
drop_caches有时会同时影响这两者。 - 频繁 echo drop_caches 是好事:不是——会影响性能,掩盖根因。
- “dentry 大 = 一定内存 leak”:不一定。很多场景是正常热缓存(例如大量小文件的服务),关键看是否造成内存压力与回收 thrash。
11) 常用脚本/命令汇总(可直接复制使用)
计算 dentry 占用百分比(更完整)
PAGE_SIZE=4096TOTAL_MEM_KB=$(awk'/MemTotal/ {print$2}'/proc/meminfo)# KBawk-vP=$PAGE_SIZE-vTM=$TOTAL_MEM_KB' /^dentry/ { active=$2; num=$3; objsize=$4; objs_per_slab=$5; pages_per_slab=$6; approx_active_mb = active * objsize / 1024 / 1024; # 更精确:用 num -> slab count -> pages * PAGE_SIZE slabs = int((num + objs_per_slab - 1) / objs_per_slab); precise_mb = slabs * pages_per_slab * P / 1024 / 1024; printf "dentry active_objs=%d objsize=%d -> approx_active=%.2fMB precise_total=% .2fMB (slabs=%d)\n", active, objsize, approx_active_mb, precise_mb, slabs; printf "dentry ~ %.3f%% of total mem\n", (approx_active_mb*1024)/(TM)/10.24; }'/proc/slabinfo监测短时间内哪个进程在 open/create 文件
# opensnoop-bpfcc 需安装 bccsudoopensnoop-bpfcc -n10# top 10 files with open events12) 小结(如何把握优先级)
- 先看影响:是否有内存紧张、kswapd/oom 出现、IO/latency 增高?
- 若无紧张:dentry 数高很可能只是缓存热身(不处理)。
- 若有紧张:按上面诊断顺序(slabinfo -> top dirs by inode -> eBPF tracking -> fix app/fs)逐步定位并修复。
- 长期防范:监控 + 限制容器日志与短生命周期文件写入 + 优化应用逻辑。
生产环境
1) 快速结论(先给结论省时间)
- dentry 大小:
active=2,258,037 * objsize=216B ≈ 465.14 MB。
占总内存(502 GiB)的比例约0.09%——完全不算高 / 无需处理。 - 真正占用较多的是其它 slab & page cache:
buffer_head、xfs_inode、kmalloc-512等占用了几 GB 到十几 GB。
slabtop 显示Active Size ≈ 10,144,671 KB (~9.67 GB);其中buffer_head单项约4,961,756 KB (~4.7 GB),xfs_inode约2,152,480 KB (~2.05 GB),dentryslabtop 行列显示约643,280K?(你之前 slabtop 行列的 dentry 行显示 643,280K? 实际计算按 active*objsize 为 ~465MB)。 - 系统总体内存很空闲:
Mem total 502Gi,used 21Gi,buff/cache 198Gi,available 477Gi—— 内存非常充足。 - 异常点:系统
load avg极高(~180),同时有大量kworker、xfs_ham+等在运行/D态并消耗大量系统 CPU(top 显示 system CPU ~44%),vmstat 显示磁盘 IO 活跃(bi/bo 很大)。这说明当前问题更可能是IO/文件系统(XFS)相关的高并发/元数据操作导致系统负载飙高,而不是 dentry 本身占内存。
结论:dentry 无需处理。应把精力放在找出导致高 load / 大量 I/O / 大量 kworker 的进程与操作(很可能与 XFS 元数据或 buffer_head 相关)并定位根因。
2) 为什么会有这么高的 load(简要解释)
- load 很高但内存并不紧张,说明很多任务处于D(uninterruptible sleep)或等待 I/O,或大量内核线程在做 work(kworker、xfs 元数据处理等)。
- slab 中
buffer_head数量极大,表示 block layer 上有大量 buffers(通常和磁盘读写、元数据更新、XFS 缓冲有关)。 xfs_inode/xfs_ili数量大,说明 XFS 有大量 inode / I/O 元数据活动(可能是大量并发文件操作或后台扫描/repair/flush)。
3) 你现在可以立即做的诊断命令(按优先级,直接跑并把结果贴回我看)
- 实时查看 IO 负载与设备(快速定位是哪块盘):
iostat -x110- 看哪个进程产生最多 I/O(需 iotop,若无先安装):
iotop -aoP# 或者 iotop -a -o -P- 找出大量处于 D/正在做 IO 的进程(按 PID 列出状态):
ps-eo pid,stat,comm,%cpu,%mem --sort=-%cpu|head-n40# 或找 D 态进程ps-eo pid,stat,comm|awk'$2~ /D/ {print$0}'|head- 查看内核日志是否有 XFS / I/O 错误:
dmesg|egrep-i'xfs|error|warn|i/o|hard io'|tail-n200journalctl -k -n200|egrep-i'xfs|i/o|error'- 查看哪些目录文件数很多(可能导致元数据热点):
# top-level 快速统计fordin/*;doecho"$(find"$d"-maxdepth3-xdev -type f2>/dev/null|wc-l)$d";done|sort-nr|head# 若磁盘/目录明确,替换 /pathfind/path/to/suspected -xdev -printf'%h\n'|sort|uniq-c|sort-nr|head- 追踪短时间内谁在 open/create(需要 bcc 或 bpftrace):
# opensnoop (bcc)sudoopensnoop-bpfcc -t10# bpftrace 快速统计 open 系统调用按 commsudobpftrace -e'tracepoint:syscalls:sys_enter_openat { @[comm] = count(); }'- 查看每个进程打开的 fd 数(是否某进程打开数异常):
forpidin$(ls/proc|egrep'^[0-9]+$');doecho-n"$pid";ls/proc/$pid/fd2>/dev/null|wc-l;done|sort-k2 -n|tail-n30- 如果怀疑 XFS 后台操作(scrub/repair/flush),列出 xfs 相关线程和命令行:
ps-ef|egrep-i'xfs|fsync|xfs_repair|xfs_io|xfs_fsr'|grep-vegrep4) 根据你当前 slabtop / top 的发现,我的建议(实战)
- 不要盲目 drop_caches(你内存大且缓存正常,drop 会引发短期性能降低并且掩盖根因)。
- 把排查放在哪个进程 / 哪块磁盘 / 哪类操作上(opens/creat/unlink/fsync/stat),上述命令能迅速定位。
- 若发现是某后台任务(比如备份、fsck、xfs_fsr、scrub、massive metadata scan)在跑,先考虑暂停/节流,观察 load 变化。
- 如果是某应用在短时间内大量 create/unlink,建议:改为批量处理、减少 fsync、或者使用 tmpfs 缓冲再批量落盘。
- 如果是 XFS 自身或内核 bug(dmesg 有错误或大量 xfs log 输出),请先把相关日志贴上来,我帮你分析是否需要升级内核或做特殊修复步骤。
5) 我现在能帮你做的事(二选一或都可以)
A. 你把下面输出贴来,我立即帮你分析并给出下一步建议(我会指出最可能的罪魁):
iostat -x 1 10的输出iotop -aoP的前 50 行dmesg | tail -n 200ps -eo pid,stat,comm,%cpu --sort=-%cpu | head -n 60
B. 如果你愿意我也可以直接给一套“应急抑制”命令(例如对 suspect job 限速、nice/ionice 调整、临时停止某个服务),但我建议先定位再采取抑制,否则可能影响业务。
6) 补充 — 你可能关心的数据(我已从你输出总结)
- dentry ≈465 MB(安全)
- slab active 总体 ≈~9.7 GB(正常,主要是 buffer_head/xfs_inode/ kmalloc)
- page cache (
Cached:) ≈197,711,988 KB ≈ 188.5 GiB(这就是buff/cache很大的来源) - load 极高 + 大量 kworker/xfs 线程 →优先排 IO / XFS 元数据而不是 dentry