Linux内存管理:Slab分配机制深度解析
本文深入探讨了Linux内核中的Slab内存管理机制,解释了其如何通过精细化管理内存页来高效分配和回收小型内存块,从而优化小对象的内存利用率并降低分配开销。文章还介绍了如何查看Slab内存使用情况,包括使用/proc/meminfo和slabtop工具,以及如何回收Slab内存以应对内存紧张的情况。此外,还提供了内核Slab常见对象的分类及说明,为理解Linux内存管理提供了宝贵的资源。
核心内容提要:Linux内核Slab缓存机制、内存分配效率提升技巧、Linux小对象内存优化、/proc/meminfo解读、slabtop工具使用指南、内核内存回收技术、Linux内存管理最佳实践、Slab内存分配器性能分析、内核对象缓存管理、Linux内存页管理策略
背景
在 Linux 内存管理中,页式管理适用于大块内存分配场景。然而,对于内核中频繁使用的小对象(如 inode 和 dentry),如果每次分配都直接申请整页内存,不仅浪费资源,还会导致效率低下。
为了解决这一问题,Linux 内核引入了 Slab 分配机制。Slab 层通过向内核申请完整的页(Page),然后对这些页进行精细化管理,从而高效分配和回收小型内存块。通过这种方式,Slab 不仅优化了小对象的内存利用率,还显著降低了分配和销毁的开销。
更多原理可以参考《性能之巅》和《BFP 之巅》第7章:
简介
Slab 分配器最早于 1994 年由 Sun Microsystems 提出,详细描述见论文 The Slab Allocator: An Object-Caching Kernel Memory Allocator。Slab 是一种专为内核设计的内存分配器,通过将内存划分为不同大小的块分配给对象,以实现高效的缓存管理,主要用于内核对象的内存分配与管理。
Slab 的两个主要作用:
- 优化小对象分配:Slab 为小对象分配内存,而不是为每个小对象单独申请一整页,从而节省了宝贵的内存空间。
- 提升频繁创建和销毁小对象的效率:Slab 为小对象提供缓存功能,可以重复利用已分配的相同对象,从而减少内存分配和释放的开销,提高系统性能。
查看 Slab 内存使用情况
通过 /proc/meminfo 查看
其中 Slab = SReclaimable + SUnreclaimable,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 缓存。