slab 缓存以及slabtop 命令学习

一、slab 缓存介绍

1.1、什么是slab 缓存

SLAB缓存是Linux内核中用于++优化内存分配和管理++ 的一种机制,特别针对频繁分配和释放的固定大小的小对象 。它是基于 通用内存分配器(如伙伴系统) 之上的一个中间层,旨在通过减少分配和释放小对象的开销、降低内存碎片以及提高缓存局部性来提升系统性能。SLAB缓存的核心思想是 在内核中预先分配一系列相同大小的对象,并将这些对象组织成称为"SLAB"的连续内存块,从而实现高效的内存重用。

1.2、slab 缓存原理

slab 缓存原理如下:

  • 初始化阶段:当内核首次需要分配特定类型和大小的对象时,SLAB分配器会从伙伴系统中分配一大块连续内存(远大于单个对象的需求),并将这块内存切割成多个相同大小的槽位(slot),每个槽位用来存放一个对象。这些槽位组成的集合称为一个SLAB。

  • 对象缓存:每个SLAB都属于一个特定类型的缓存(cache),缓存定义了对象的大小、初始化函数(如果需要的话)以及其他属性。内核维护了一个全局的缓存列表,便于快速查找和分配对象。

  • 分配与释放:当需要一个新的对象时,SLAB分配器首先尝试从已有的、含有未分配对象的SLAB中分配。如果找不到合适的SLAB,才会从伙伴系统中分配新的内存块并创建新的SLAB。释放对象时,通常并不立即归还给伙伴系统,而是留在SLAB中作为未使用的对象,以便下次快速重用,减少了内存碎片和分配开销。

  • 缓存策略:SLAB缓存支持多种策略来管理对象,包括SLAB的分裂与合并、缓存的收缩与扩张、以及冷热数据分离等,以适应不同的使用场景和性能要求。

1.3、slab 缓存优势

  • 减少分配和释放的开销:通过重用对象,避免了每次分配和释放时的系统调用和内存管理开销。
  • 减少内存碎片:通过预分配和固定大小的策略,减少了外部碎片问题。
  • 提高缓存局部性:由于对象在物理内存中是连续存放的,可以提高CPU缓存的命中率。
  • 优化性能:特别是对于频繁操作的小对象,如文件描述符、网络套接字等,可以显著提高系统整体性能。

1.4、slab 的发展

随着时间的发展,SLAB缓存也在不断进化,引入了更高级的管理策略,例如SLOB(Simple List of Blocks,适用于嵌入式系统)和SLUB(SLAB Unqueued,优化了锁的使用,提高了多核处理器的性能)

二、slab 核心概念

2.1、 slab状态

Slab是SLAB机制中的基本组成单元,它是预先分配的一块连续的内存区域。每个Slab由一个或多个大小相同的对象组成,这些对象属于同一个Cache。Slab可以处于以下几种状态之中:

  • Partial(部分分配): 这是最常见的状态。当一个SLAB被分配并开始使用时,最初它可能只有部分对象被分配出去,而其他对象仍处于未分配状态。此时,SLAB处于"部分分配"状态。随着对象的分配和释放,SLAB中的对象数量会变化,但只要还有至少一个对象未被使用,SLAB就保持在这个状态。

  • Full(全分配): 当SLAB中的所有对象都被分配出去,没有剩余未分配对象时,该SLAB就进入了"全分配"状态。这意味着如果有新的分配请求,内核必须寻找其他SLAB或创建新的SLAB来满足需求,而不能直接从这个SLAB中分配。

  • Empty(空闲): 当SLAB中的所有对象都被释放,没有活跃对象时,理论上可以认为SLAB进入了一个"空闲"状态。然而,在实际的SLAB管理中,空闲的SLAB并不直接返回给伙伴系统,而是保持在缓存中以便快速重用。因此,严格来说,Linux SLAB分配器中并没有一个明确标记为"Empty"的状态,但它可以通过"部分分配"状态且所有对象都是未分配的状态来近似理解。

  • Freeing(释放中): 当SLAB决定被销毁或归还给伙伴系统时,它会进入释放流程,这可能包括清除对象数据、执行必要的清理操作等。

  • Reclaim(回收): 在某些情况下,SLAB可能会被标记为可回收状态,等待内核的内存回收机制处理,这通常发生在内存压力较大时。

2.2、slab cache

SLAB Cache,全称SLAB分配器缓存,是Linux内核中一种高效的内存管理机制,专门设计用于优化对小而频繁分配和释放的内核对象的处理。SLAB缓存通过预先分配和管理一组相同大小的对象,极大地减少了内存分配和释放的开销,同时降低了内存碎片,提高了系统性能。下面是对SLAB Cache的详细介绍:

  • 目的:解决传统内存分配器(如伙伴系统)在处理小对象时的效率低下问题,减少内存碎片,加快对象分配和释放速度。

  • 原理:SLAB将物理内存分割成多个缓存区(Cache),每个Cache对应一类特定大小和类型的内核对象。每个Cache又由多个SLAB组成,每个SLAB是一块连续内存,被划分为多个固定大小的槽位(Slots),每个槽位用于存放一个对象实例。

  • 对象池化:预先分配大量相同类型和大小的对象,形成对象池,当需要时直接从池中分配,释放时回收至池中而非归还给底层内存管理系统,减少了分配和释放的系统开销。

  • 缓存层次:SLAB之上有多个层次的缓存管理,包括per-CPU缓存、全局缓存等,进一步加速了对象的分配和回收过程。

  • 对象初始化:支持对象构造和析构回调,可以在对象分配和释放时执行特定的初始化和清理操作,保持对象的一致性。

  • 内存回收策略:SLAB在内存紧张时可以释放未使用的SLAB,或者在长时间未使用的SLAB上执行内存回收,以应对系统对内存的动态需求。

  • 多态性:支持多种SLAB分配策略,如SLAB、SLUB(SLAB Unqueued,减少了锁的使用,提高了多处理器系统的性能)和SLOB(Simple List of Blocks,用于资源受限环境)

2.3、缓存色彩(Cache Coloring)

Cache Coloring技术主要是为了解决多核处理器系统中的一种现象------伪共享(False Sharing)。伪共享发生在两个或多个处理器核心访问同一缓存行(Cache Line)的不同变量时,即使这些变量逻辑上无关。由于缓存一致性协议,当一个核心修改了该缓存行的一个变量,其他核心即使只读取该行的其他变量,整个缓存行也需要在各个核心之间同步,导致不必要的性能损失。

着色策略:

  • 物理着色:在分配内存时,确保相关数据跨不同缓存行分布,避免伪共享。这通常需要操作系统或编译器的支持,通过对齐数据结构到缓存行边界或使用特定的内存分配函数来实现。
  • 逻辑着色:软件层面的解决方案,通过编程技巧(比如padding)人为增加数据结构大小,确保相邻变量位于不同的缓存行。

2.4 构造器和析构器

构造器(Constructor)

构造器是++在对象被分配给用户之前调用的函数++ 。它的主要职责是初始化新分配对象的数据成员,确保对象处于一个有效的、可用的状态。对于内核对象而言,这可能涉及到设置默认值、配置内部指针、初始化锁或其他同步原语等。构造器的使用使得每个新分配的对象都以一致的状态开始其生命周期,这对于维持系统稳定性至关重要。

析构器(Destructor)

析构器是++在对象被释放回SLAB缓存之前调用的函数++ 。它负责清理对象占用的任何资源,还原对象到一个干净的状态,准备下一次分配重用。这可能包括释放分配给对象的额外资源(如内存、文件描述符等)、重置状态标志、解除引用计数等操作。通过确保每个对象在被重新分配前都被正确清理,析构器有助于防止内存泄漏和资源耗尽问题。

如何使用

在内核编程中,构造器和析构器通常在定义SLAB缓存时指定。例如,使用kmem_cache_create()函数创建一个新的SLAB缓存时,可以传入构造器和析构器的函数指针作为参数。这样,每次从该缓存中分配或释放对象时,相应的构造器和析构器就会自动被调用。

实例

c 复制代码
struct my_obj {
    /* 成员变量定义 */
};

static void my_obj_constructor(void *objp, kmem_cache_t *cachep, unsigned long flags)
{
    struct my_obj *obj = objp;
    /* 初始化obj的成员变量 */
}

static void my_obj_destructor(void *objp)
{
    struct my_obj *obj = objp;
    /* 清理或重置obj的资源和状态 */
}

/* 创建SLAB缓存 */
kmem_cache_t *my_cache = kmem_cache_create("my_cache",
                                           sizeof(struct my_obj),
                                           0,
                                           SLAB_PANIC,
                                           my_obj_constructor,
                                           NULL,
                                           my_obj_destructor);

/* 之后就可以使用my_cache来分配和释放对象了 */
struct my_obj *new_obj = kmem_cache_alloc(my_cache, GFP_KERNEL);
/* 使用new_obj */
kmem_cache_free(my_cache, new_obj);

2.5、slab 缓存对象分配和释放过程

对象分配过程

对象的分配过程是SLAB机制的核心,旨在快速有效地提供所需的内存空间。以下是对象分配的基本步骤:

  • 查找合适的Slab Cache:当系统需要一个特定类型的对象时,首先在对应的Slab Cache中查找。
  • 选择Slab:在找到的Slab Cache中,系统会寻找状态为部分满(Partial)或空(Empty)的Slab。优先选择部分满的Slab,以提高内存利用率。
  • 分配对象:从选定的Slab中分配一个空闲对象。如果选择的是空Slab,系统会先初始化该Slab,然后分配对象。
  • 更新Slab状态:分配对象后,更新Slab的状态。如果所有对象都被分配,Slab状态变为满(Full)。

对象释放过程

与对象分配同样重要的是对象的释放过程,它确保了内存资源的有效回收和重用。对象释放过程包括以下步骤:

  • 确定对象所属的Slab:释放对象时,系统首先确定该对象属于哪个Slab。
  • 释放对象:将对象标记为未使用,返回到Slab的空闲对象池中。
  • 更新Slab状态:如果释放对象前Slab是满的,则释放后状态变为部分满(Partial)。如果释放后Slab中所有对象都是空闲的,则状态变为空(Empty)。
  • Slab的回收:如果一个Slab长时间处于空状态,系统可能会决定回收该Slab,释放内存给操作系统。

三、slab 监控

3.1、slabtop 命令

命令执行结果如下

shell 复制代码
 Active / Total Objects (% used)    : 6772985 / 6790551 (99.7%)
 Active / Total Slabs (% used)      : 162301 / 162301 (100.0%)
 Active / Total Caches (% used)     : 84 / 112 (75.0%)
 Active / Total Size (% used)       : 1218639.77K / 1225341.38K (99.5%)
 Minimum / Average / Maximum Object : 0.01K / 0.18K / 15.25K

  OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME                   
3866811 3862386  99%    0.10K  99149	   39    396596K buffer_head
890946 890833  99%    0.19K  21213	 42    169704K dentry
664122 662990  99%    0.04K   6511	102     26044K ext4_extent_status
358400 358254  99%    1.00K  11200	 32    358400K ext4_inode_cache
279650 279650 100%    0.05K   3290	 85     13160K shared_policy_node
155072 153885  99%    0.06K   2423	 64	 9692K kmalloc-64
153916 153332  99%    0.57K   5497	 28     87952K radix_tree_node
 78168  73411  93%    0.66K   3257	 24     52112K proc_inode_cache
 68568  68315  99%    0.66K   2857	 24     45712K ovl_inode
 31552  31466  99%    0.12K    928	 34	 3712K kernfs_node_cache
 26381  26381 100%    0.21K    713	 37	 5704K vm_area_struct
 19698  19698 100%    0.09K    469	 42	 1876K kmalloc-96
 16416  16416 100%    0.58K    608	 27	 9728K inode_cache
 14280  14280 100%    0.04K    140	102	  560K selinux_inode_security
 14127  13465  95%    0.08K    277	 51	 1108K anon_vma
 13312  12471  93%    0.03K    104	128	  416K kmalloc-32
 11776  11776 100%    0.01K     23	512        92K kmalloc-8
 11520  10895  94%    0.12K    360	 32	 1440K kmalloc-128
  9472   9472 100%    0.02K     37	256	  148K kmalloc-16
  9450   9133  96%    0.19K    225	 42	 1800K kmalloc-192
  8480   7431  87%    0.25K    265	 32	 2120K kmalloc-256
  7808   7808 100%    0.03K     61	128	  244K jbd2_revoke_record_s
  6300   6300 100%    0.19K    150	 42	 1200K cred_jar
  4352   4352 100%    0.06K     68	 64	  272K ext4_free_data
  4312   4312 100%    0.07K     77	 56	  308K avc_node
  4020   4020 100%    0.13K    134	 30	  536K ext4_groupinfo_4k
  4000   3739  93%    1.00K    125	 32	 4000K kmalloc-1024
  3488   3242  92%    0.50K    109	 32	 1744K kmalloc-512
  3024   3024 100%    0.38K     72	 42	 1152K mnt_cache
  3024   3024 100%    0.11K     84	 36	  336K jbd2_journal_head
  2904   2853  98%    0.66K    121	 24	 1936K shmem_inode_cache
  2825   2825 100%    0.62K    113	 25	 1808K sock_inode_cache
  2720   2720 100%    0.02K     16	170        64K fsnotify_mark_connector
  2675   2675 100%    0.31K    107	 25	  856K nf_conntrack_1

命令结果解析

  • Active / Total Objects (% used):
    • Active Objects: 当前正在被使用的对象数量(即已分配出去的对象)。
    • Total Objects: SLAB缓存中所有对象的总容量。
    • (99.7%): 当前活跃对象占总对象容量的百分比。在这个例子中,约99.7%的SLAB对象已被分配使用,表明SLAB缓存的使用率非常高,接近饱和。
  • Active / Total Slabs (% used):
    • Active Slabs: 正在使用中的SLAB数量,即包含至少一个活跃对象的SLAB。
    • Total Slabs: 总共创建的SLAB数量。
    • (100.0%): 所有SLAB都在使用中,说明没有空闲的SLAB,SLAB资源完全被占用。
  • Active / Total Caches (% used):
    • Active Caches: 当前活跃的SLAB缓存数量,即至少有一个活跃对象的缓存。
    • Total Caches: 系统中定义的所有SLAB缓存的数量。
    • (75.0%): 表示活跃的SLAB缓存占总缓存数量的比例,这里75%的缓存正在被使用,剩余25%可能是未被访问或暂时不需要的缓存。
  • Active / Total Size (% used):
    • Active Size: 当前被活跃对象占用的总内存大小。
    • Total Size: SLAB缓存分配的总内存大小。
    • (99.5%): 活跃对象占用的内存比例,表明SLAB缓存中的内存使用率极高。
  • Minimum / Average / Maximum Object:
    • Minimum Object: SLAB缓存中对象的最小尺寸,这里是0.01K,即10字节。
    • Average Object: SLAB缓存中对象的平均尺寸,这里是0.18K,即180字节。
    • Maximum Object: SLAB缓存中对象的最大尺寸,这里是15.25K,即15250字节。

buffer_head缓存为例,slabtop获取到的缓存字段含义如下:

字段 解释
OBJS 总对象数量,对于buffer_head缓存来说,共有3,866,811个对象。
ACTIVE 活跃对象数量,此处有3,862,386个buffer_head对象正在被使用。
USE OBJ SIZE 对象使用率,99%,意味着几乎所有的buffer_head对象都已被分配。
OBJ SIZE 单个对象的大小,这里是0.10K,即每个buffer_head对象占用100字节。
SLABS 总SLAB数量,共有99,149个SLAB用于存储buffer_head对象。
OBJ/SLAB 每个SLAB中的对象数量,平均每个SLAB含有大约39个buffer_head对象。
CACHE SIZE SLAB缓存的总大小,为396,596K(约等于396.6MB),这是所有buffer_head SLAB加起来占用的总内存。
NAME 缓存名称,这里是buffer_head,与Linux内核中的缓冲头结构相关,常用于文件系统I/O操作。

常见缓存名称含义

缓存名称 含义
buffer_head 与文件系统操作相关,用于描述缓冲区头部信息,存储文件I/O操作中的元数据。
dentry 目录项(Directory Entry)缓存,加速文件路径名查找。每个dentry对象代表文件系统中的一个目录项或文件名。
inode_cache 索引节点(Inode)缓存,Inode包含文件的元数据信息,如权限、所有权、大小、时间戳等。高效的inode缓存对文件操作速度至关重要。
kmalloc 通用的内核内存分配缓存,用于动态分配大小固定的内核内存块。
filp 文件描述符(File Descriptor)缓存,每个filp对象对应一个打开的文件。
sock_inode_cache 套接字inode缓存,用于网络连接相关的数据结构。
task_struct 进程描述符缓存,每个运行的进程或线程都有一个对应的task_struct结构体。
skbuff Socket Buffer,网络数据包的缓存,用于网络数据的接收和发送。
vm_area_struct 虚拟内存区域(VM Area)结构缓存,用于管理进程地址空间中的不同区域,如堆、栈、共享库映射等。
radix_tree_node 前面提到过,与基数树数据结构相关,用于高效内存管理和查找。
fsnotify_mark_connector 文件系统通知标记连接器,与FSNOTIFY机制相关,用于跟踪文件系统事件。
kmem_cache 内核内存缓存自身的缓存,用于管理其他SLAB缓存的元数据。

3.2、/proc/slabinfo 文件

proc/slabinfo 是Linux系统中一个虚拟文件,提供了关于系统中SLAB缓存的详细信息。/proc/slabinfo 文件的内容可以在不使用额外工具的情况下直接查看,也可以通过像slabtop这样的工具进行更友好的展示和分析。示例如下:

shell 复制代码
cat /proc/slabinfo 
slabinfo - version: 2.1
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
ovl_inode          68315  68568    680   24    4 : tunables    0    0    0 : 
jbd2_journal_handle   1360   1360     48   85    1 : tunables    0    0    0 : slabdata     16     16      0
jbd2_journal_head   3024   3024    112   36    1 : tunables    0    0    0 : slabdata     84     84      0
jbd2_revoke_table_s    256    256     16  256    1 : tunables    0    0    0 : slabdata      1      1      0
jbd2_revoke_record_s   7808   7808     32  128    1 : tunables    0    0    0 : slabdata     61     61      0
...

包含字段含义如下

字段 解释 备注
name SLAB缓存的名称,表明了该缓存类型,如kmalloc-64, dentry等,代表了内核中特定类型的对象。
active_objs 活跃对象的数量,即当前正在被使用的对象数量。
num_objs 缓存中对象的总数量,包括活跃和非活跃(未分配)的对象。
objsize 单个对象的大小,单位通常是字节。
objperslab 每个SLAB中包含的对象数量。
pagesperslab 构成一个SLAB所需的物理页数,反映了SLAB的大小与页框(通常为4KB)的关系。
limit 缓存自动扩展或收缩的上限值,决定了何时开始创建或销毁SLAB。 SLAB缓存的调整参数(tunables)
batchcount 在进行SLAB分配或回收时,每次操作的对象数量。 SLAB缓存的调整参数(tunables)
sharedfactor 如果是per-CPU SLAB,这个值表示每个CPU应该共享多少个SLAB;对于非per-CPU SLAB,这个值无意义。 SLAB缓存的调整参数(tunables)
active_slabs 当前活动的SLAB数量,即正在使用的SLAB。 SLAB分配状态
num_slabs 总SLAB数量,包括活动和非活动的。 SLAB分配状态
sharedavail 对于per-CPU SLAB,表示可用的共享SLAB数量;如果不是per-CPU SLAB,则这个字段可能没有意义或者显示为N/A。 SLAB分配状态

参考文档

1、https://www.cnblogs.com/tcindex/p/16040808.html

2、https://zhuanlan.zhihu.com/p/491500340

3、https://developer.aliyun.com/article/1469092

4、https://kernel.meizu.com/2016/05/09/slab-allocator-and-kmalloc.html/

5、https://cloud.tencent.com/developer/article/2357041

相关推荐
Ivan陈哈哈9 分钟前
Redis是单线程的,如何提高多核CPU的利用率?
数据库·redis·缓存
球求了1 小时前
C++:继承机制详解
开发语言·c++·学习
时光追逐者1 小时前
MongoDB从入门到实战之MongoDB快速入门(附带学习路线图)
数据库·学习·mongodb
一弓虽1 小时前
SpringBoot 学习
java·spring boot·后端·学习
头顶秃成一缕光2 小时前
Redis的主从模式和哨兵模式
数据库·redis·缓存
观无2 小时前
Redis安装及入门应用
数据库·redis·缓存
genggeng不会代码3 小时前
用于协同显著目标检测的小组协作学习 2021 GCoNet(总结)
学习
搞机小能手3 小时前
六个能够白嫖学习资料的网站
笔记·学习·分类
XY.散人4 小时前
初识Redis · 哨兵机制
数据库·redis·缓存
The_cute_cat6 小时前
25.4.22学习总结
学习