本文深入探讨了Linux内核中的Slab内存管理机制,解释了其如何通过精细化管理内存页来高效分配和回收小型内存块,从而优化小对象的内存利用率并降低分配开销。文章还介绍了如何查看Slab内存使用情况,包括使用/proc/meminfo和slabtop工具,以及如何回收Slab内存以应对内存紧张的情况。此外,还提供了内核Slab常见对象的分类及说明,为理解Linux内存管理提供了宝贵的资源。

核心内容提要:Linux内核Slab缓存机制、内存分配效率提升技巧、Linux小对象内存优化、/proc/meminfo解读、slabtop工具使用指南、内核内存回收技术、Linux内存管理最佳实践、Slab内存分配器性能分析、内核对象缓存管理、Linux内存页管理策略

Linux内存管理:Slab分配机制深度解析

背景

在 Linux 内存管理中,页式管理适用于大块内存分配场景。然而,对于内核中频繁使用的小对象(如 inode 和 dentry),如果每次分配都直接申请整页内存,不仅浪费资源,还会导致效率低下。

为了解决这一问题,Linux 内核引入了 Slab 分配机制。Slab 层通过向内核申请完整的页(Page),然后对这些页进行精细化管理,从而高效分配和回收小型内存块。通过这种方式,Slab 不仅优化了小对象的内存利用率,还显著降低了分配和销毁的开销。

更多原理可以参考《性能之巅》和《BFP 之巅》第7章:

《性能之巅》

《BFP 之巅》

简介

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: 网络连接跟踪缓存。

协议相关

  • TCPTCPv6UDPv6RAWRAWv6MPTCPMPTCPv6: 不同协议的缓存。
  • ip4-fragsip6-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 缓存。

标签: Linux, 内存管理, Slab, 操作系统, Linux内核

添加新评论