一、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/