Linux Slab 内存管理机制简介
背景
在 Linux 内存管理中,页式管理适用于大块内存分配场景。然而,对于内核中频繁使用的小对象(如 inode 和 dentry),如果每次分配都直接申请整页内存,不仅浪费资源,还会导致效率低下。
为了解决这一问题,Linux 内核引入了 Slab 分配机制。Slab 层通过向内核申请完整的页(Page),然后对这些页进行精细化管理,从而高效分配和回收小型内存块。通过这种方式,Slab 不仅优化了小对象的内存利用率,还显著降低了分配和销毁的开销。
Powered by ChatGPT
简介
Slab 分配器最早于 1994 年由 Sun Microsystems 提出,详细描述见论文 The Slab Allocator: An Object-Caching Kernel Memory Allocator(Jeff Bonwick)(https://people.eecs.berkeley.edu/~kubitron/courses/cs194-24-S14/hand-outs/bonwick_slab.pdf)。Slab 是一种专为内核设计的内存分配器,通过将内存划分为不同大小的块分配给对象,以实现高效的缓存管理,主要用于内核对象的内存分配与管理。
Slab 的两个主要作用:
• 优化小对象分配:Slab 为小对象分配内存,而不是为每个小对象单独申请一整页,从而节省了宝贵的内存空间。
• 提升频繁创建和销毁小对象的效率:Slab 为小对象提供缓存功能,可以重复利用已分配的相同对象,从而减少内存分配和释放的开销,提高系统性能。
查看 Slab 内存使用情况
通过 /proc/meminfo 查看
其中 Slab = SReclaimable + SUnreclaim,SReclaimable 表示可回收使用的内存。
cat /proc/meminfo | grep -Ei "reclaim|slab"
KReclaimable: 19333912 kB # KReclaimable 是可回收的内核内存,通常等于 SReclaimable,包括一些 Slab 内存和其他内核缓存
Slab: 22328620 kB
SReclaimable: 19333912 kB
SUnreclaim: 2994708 kB使用 slabtop 查看
slabtop --once
Active / Total Objects (% used) : 75079724 / 77001820 (97.5%)
Active / Total Slabs (% used) : 1845605 / 1845605 (100.0%)
Active / Total Caches (% used) : 180 / 207 (87.0%)
Active / Total Size (% used) : 19640357.66K / 20027161.22K (98.1%)
Minimum / Average / Maximum Object : 0.01K / 0.26K / 12.75K OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
33009942 33009888 99% 0.19K 785951 42 6287608K dentry
23556546 22264266 94% 0.10K 604014 39 2416056K buffer_head
12689873 12689746 99% 0.64K 258977 49 8287264K proc_inode_cache
1023434 972065 94% 0.94K 30101 34 963232K xfs_inode
968772 906343 93% 0.57K 34599 28 553584K radix_tree_node
814080 774395 95% 0.16K 16960 48 135680K xfs_ili
757824 683026 90% 0.06K 11841 64 47364K kmalloc-64
493248 488108 98% 0.09K 11744 42 46976K kmalloc-96
433152 429962 99% 0.25K 13536 32 108288K kmalloc-256
328224 318136 96% 0.50K 10257 32 164112K kmalloc-512
270304 201940 74% 1.00K 8447 32 270304K kmalloc-1024
239184 228624 95% 0.66K 4983 48 159456K ovl_inode
226816 116538 51% 0.03K 1772 128 7088K kmalloc-32
221696 189534 85% 0.01K 433 512 1732K kmalloc-8
200420 197801 98% 0.58K 3644 55 116608K inode_cache
190848 134020 70% 0.38K 4544 42 72704K mnt_cache
189482 188542 99% 0.12K 5573 34 22292K kernfs_node_cache
154392 128007 82% 0.19K 3676 42 29408K kmalloc-192
128928 117047 90% 0.12K 4029 32 16116K kmalloc-128
99937 91849 91% 0.21K 2701 37 21608K vm_area_struct
84992 73290 86% 0.02K 332 256 1328K kmalloc-16
78704 67028 85% 2.00K 4919 16 157408K kmalloc-2048
50624 49609 97% 0.07K 904 56 3616K avc_node
42636 37949 89% 0.08K 836 51 3344K anon_vma
38760 38760 100% 0.04K 380 102 1520K selinux_inode_security
31171 30422 97% 0.05K 427 73 1708K avc_xperms_node
27408 25179 91% 0.66K 571 48 18272K shmem_inode_cache
27030 27030 100% 0.02K 159 170 636K fsnotify_mark_connector
25020 21984 87% 0.11K 695 36 2780K task_delay_info
24375 20125 82% 0.81K 625 39 20000K task_xstate
24174 21608 89% 0.62K 474 51 15168K sock_inode_cache
20504 20504 100% 0.18K 466 44 3728K xfs_log_ticket
19737 16130 81% 0.31K 387 51 6192K nf_conntrack_ffffffffafd11640
19584 19584 100% 0.08K 384 51 1536K Acpi-State
18949 17875 94% 4.06K 2707 7 86624K task_struct
14943 14194 94% 0.16K 293 51 2344K sigqueue
14586 14331 98% 0.31K 286 51 4576K nf_conntrack_ffff949ec0fa6680
free 输出的 Buff/Cache 含义
free 命令输出看到的 Buff/Cache,满足如下等式:
Buff/Cache = Cached + SReclaimable + Buffers(from /proc/meminfo):
从 /proc/meminfo 获取的各项值:
Buffers: 2857232 KB
Cached: 48287792 KB
SReclaimable: 2363524 KB
总 Cache (Cached + SReclaimable): 50651316 KB
扩展 Cache (总 Cache + Buffers): 53508548 KB
从 free 获取的 Cache: 53508548 KB
差值 (free Cache - 扩展 Cache): 0 KB
验证通过:free 的 Cache 等于 Cached + SReclaimable + Buffers验证脚本(compare_cache.sh):
#!/bin/bash # 提取各项值
buffers=$(grep -i '^Buffers:' /proc/meminfo | awk '{print $2}')
cached=$(grep -i '^Cached:' /proc/meminfo | awk '{print $2}')
sreclaimable=$(grep -i '^SReclaimable:' /proc/meminfo | awk '{print $2}')
free_cache=$(free -k | awk '/^Mem:/ {print $6}')
# 计算总 Cache
total_proc_cache=$((cached + sreclaimable))
extended_proc_cache=$((total_proc_cache + buffers))
# 输出对比
echo "从 /proc/meminfo 获取的各项值:"
echo " Buffers: $buffers KB"
echo " Cached: $cached KB"
echo " SReclaimable: $sreclaimable KB"
echo " 总 Cache (Cached + SReclaimable): $total_proc_cache KB"
echo " 扩展 Cache (总 Cache + Buffers): $extended_proc_cache KB"
echo "从 free 获取的 Cache: $free_cache KB"
# 差值分析
diff=$((free_cache - extended_proc_cache))
echo "差值 (free Cache - 扩展 Cache): $diff KB"
if [[ $diff -eq 0 ]]; then
echo "验证通过:free 的 Cache 等于 Cached + SReclaimable + Buffers"
else
echo "验证失败:差值非 0,需进一步排查。"
fi
TODO:分析 free 工具代码来明确等式是否成立。
统计 Slab 占用超过 100M 的对象
cat /proc/slabinfo | awk '{if ($3 * $4 / 1024 / 1024 > 100) {print $1, $3 * $4 / 1024 / 1024}}'awk 部分代码解析:
•
$3: 每种Slab缓存的活跃对象数量(allocated)。•
$4: 每个对象的大小(以字节为单位)。•
$3 * $4: 计算该Slab缓存的总内存使用量(单位:字节)。•
/1024/1024: 将字节转换为MB。•
> 100: 筛选出占用内存超过100 MB的Slab缓存。•
print $1, $3 * $4 / 1024 / 1024: 输出Slab名称和对应的内存占用(单位:MB)。
回收 Slab 内存
系统默认内存释放配置在 /proc/sys/vm/drop_caches 中,drop_caches 的值可以是 0-3 之间的数字,代表不同的含义:
•
0:不释放(系统默认值)。默认情况下表示不释放内存,由操作系统自动管理;•
1:释放页缓存;•
2:释放dentries和inodes;•
3:释放所有缓存;
可以使用 echo 3 > /proc/sys/vm/drop_caches 来释放,然后再改回默认值:echo 0 >/proc/sys/vm/drop_caches。
在系统内存紧张的时候,大家会想要通过
drop_caches的方式来释放一些内存,但是由于他们清楚Page Cache被释放掉会影响业务性能,所以就期望只去drop slab而不去drop page cache。于是很多人这个时候就运行echo 2 > /proc/sys/vm/drop_caches,但是结果却出乎了他们的意料:Page Cache也被释放掉了,业务性能产生了明显的下降。但实际上,drop_caches的操作在实现过程中,释放Slab缓存时,可能会导致Page Cache被释放。原因是:1)Slab缓存中的inode和dentry与Page Cache关联。如果释放了这些Slab对象,对应的Page Cache引用计数会减少;2)当Page Cache的引用计数降为零时,内核会将其标记为可回收,进而释放这些页面。
内核 Slab 常见对象分类及说明
1. 通用内存分配对象
•
kmalloc-*和dma-kmalloc-*:
通用的内存分配器,用于小对象内存分配。•
kmalloc-*: 通用分配器。•
dma-kmalloc-*: 专用于 DMA(直接内存访问)相关的分配。• 按对象大小划分,例如
kmalloc-8表示每个对象占用 8 字节。•
kmem_cache:
Slab 缓存管理器,管理通用小对象缓存。
2. 文件系统相关
目录和 inode 缓存
•
dentry: 目录项缓存,存储文件路径和目录结构信息。•
inode_cache: inode 缓存,存储文件的元数据。•
ext4_inode_cache: ext4 文件系统的 inode 缓存。•
shmem_inode_cache: 共享内存的 inode 缓存。•
nfs_inode_cache: NFS 文件系统的 inode 缓存。•
xfs_inode: XFS 文件系统的 inode 缓存。
文件锁和上下文
•
file_lock_cache: 文件锁缓存。•
file_lock_ctx: 文件锁上下文缓存。•
filp: 文件描述符缓存。
文件系统元数据
•
buffer_head: 缓存块的元数据。•
mbcache: ext 文件系统元数据块缓存。•
xfs_btree_cur: XFS 文件系统的 B 树游标缓存。•
xfs_buf: XFS 文件系统的缓冲区缓存。
3. 网络相关
套接字和数据包
•
sock_inode_cache: 套接字的 inode 缓存。•
skbuff_head_cache: 数据包(skb)的头缓存。•
skbuff_ext_cache: 数据包的扩展缓存。
连接跟踪
•
nf_conntrack: 网络连接跟踪缓存。
协议相关
•
TCP、TCPv6、UDPv6、RAW、RAWv6、MPTCP、MPTCPv6: 不同协议的缓存。•
ip4-frags、ip6-frags: IPv4/IPv6 数据包分片缓存。
其他
•
net_namespace: 网络命名空间缓存。
4. 进程和线程管理
进程和任务
•
task_struct: 进程描述符缓存。•
mm_struct: 内存管理结构体缓存。•
task_group: 进程组缓存。•
sighand_cache: 信号处理句柄缓存。•
signal_cache: 信号结构体缓存。
命名空间
•
user_namespace: 用户命名空间缓存。•
pid_namespace: PID 命名空间缓存。•
uts_namespace: UTS 命名空间缓存。
5. 文件系统相关
文件系统通知
•
fsnotify_mark_connector: 文件系统通知缓存。
挂载点和命名
•
mnt_cache: 挂载点缓存。•
names_cache: 文件名缓存。
6. 设备和驱动相关
块设备和请求
•
bdev_cache: 块设备缓存。•
request_queue: 请求队列缓存。
SCSI 和 DMA
•
scsi_sense_cache: SCSI 错误缓存。•
dmaengine-unmap-*: DMA 映射相关缓存。
7. 安全相关
权限和凭证
•
cred_jar: 用户凭证缓存。
文件系统加密
•
fscrypt_info: 文件系统加密信息缓存。•
ecryptfs_inode_cache: 加密文件系统的 inode 缓存。
SELinux 和安全模块
•
iint_cache: 安全模块的完整性缓存。
8. 虚拟内存和调度
匿名页和 VMA
•
anon_vma: 匿名虚拟内存区域缓存。•
vm_area_struct: 虚拟内存区域描述符缓存。
内存策略
•
numa_policy: NUMA 策略缓存。
内核分页和大页
•
khugepaged_mm_slot: 大页分配管理缓存。•
hugetlbfs_inode_cache: 大页文件系统的 inode 缓存。