原力注入

Linux Slab 内存管理机制简介

背景

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

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

Image

Powered by ChatGPT



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

Image

Image

简介

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 缓存。