大家好,这里是物联网心球。
今天我们来聊一下物理内存管理最后一个重要章节,slab内存分配器。
slab分配器是一种内核内存分配器,它提供了一种高效的方式来分配小对象(通常是小于一个页面大小的对象)的内存。
slab分配器设计目标是提高内存分配的速度和效率,同时减少内存碎片和减轻内核内存管理的负担。
1.slab,slub,slob分配器
slab、slub、slob是Linux内核中常用的内存分配器。
slab分配器是一种基于slab缓存的内存分配器。它将内核中常用的数据结构和对象分成若干个大小相等的slab缓存,每个slab缓存对应一种大小的数据结构或对象。当需要分配内存时,就从相应大小的slab缓存中获取一块空闲内存,当不需要使用时,就将其释放到相应大小的slab缓存中。
slub分配器是一种基于slab缓存的内存分配器,是对slab分配器的改进。它采用了更加高效的算法,提高了内存分配的性能和效率。
slob分配器则是一种轻量级的内存分配器,通常用于嵌入式设备。
由于slub分配器使用最为广泛,本文重点讨论slub分配器。
2.slub分配器概述
伙伴系统最小内存管理单元为页(page:4KB),伙伴系统无法解决小于一个页面的内存分配问题。
slub分配器最小内存分配单元为字节(object:通常大于8字节)。
slub分配器能够解决更小粒度的内存分配需求,提高Linux内存管理的效率。

slub内存分配器主要由:kmem_cache,kmem_cache_cpu,kmem_cache_node,slab,object 组成。
(1)kmem_cache结构体
struct kmem_cache是Linux内核中用于实现slub内存分配器的关键数据结构。
kmem_cache结构体的作用是描述一个内存缓存池的属性,其中包括缓存池的名称、缓存池中每个元素的大小、缓存池的容量、缓存池中每个元素的对齐方式等等。
kmem_cache结构体被广泛地应用于各种内存分配场景,比如文件系统、网络协议栈,进程等。

kmem_cache结构体中重要的成员变量:
list:内核通过list成员将kmem_cache对象插入全局链表slab_caches,通过cat /proc/slabinfo命令可以查看所有的kmem_cache对象信息。
cpu_slab:cpu slab缓存池,每个cpu都有一个cpu_slab副本,从cpu_slab副本分配和释放object对象,不需要加锁,解决了cpu竞争问题。
node:内存节点node slab缓存池,所有cpu共享node成员变量,cpu之间存在竞争关系。
size:slab object大小,包括填充部分。
object_size:slab object实际大小,不包括填充部分。
offset:freepointer相对于object首地址的偏移位置。
oo:表示一个slab包含多少物理页,一个slab包含多少个object对象。
name:kmem_cache对象名称。
通过ls /sys/kernel/slab/kmem_cache名称/ 可以查看kmem_cache结构体成员信息。

(2)kmem_cache_cpu结构体
struct kmem_cache_cpu结构体是用于描述与cpu相关的slab缓存池信息。

kmem_cache_cpu结构体中重要成员变量:
void *freelist:指向当前cpu slab缓存池空闲对象链表的指针。
unsigned long tid:记录最后一个分配或释放对象的cpu编号,用于判断是否需要重新填充空闲对象链表。
page:指向当前cpu上的slab缓存池。
partial:指向当前cpu备用slab缓存池。
(3)kmem_cache_node结构体
struct kmem_cache_node是numa node内存节点空闲slab链表,用于存储空闲slab缓存池。

kmem_cache_node结构体中重要成员变量:
nr_partial:内存节点partial链表slab缓存池数量。
partial:node节点空闲slab链表。
(4)slab缓存池
slab缓存池其实就是页(page) ,struct page结构体有和slab相关的成员,slab的分配器的核心是slab缓存池,一个slab缓存池可以理解为多个物理页的集合,将slab根据size大小划分成一个个object,object就是内存分配的对象。

page结构体中重要成员变量:
freelist:指向slab缓存池第一个空闲object对象。
slab_cache:指向kmem_cache对象。
inuse:slab缓存池已经分配的object对象数量。
objects:slab缓存池可以容纳的object对象数量。
(5)object对象
object对象是slub分配器内存分配的最小单元。
object对象的格式如下:

slab缓存池object对象大小固定为size,一个object对象由:
red left pad,object内存区域,red zone,freepinter,track,padding等部分组成。
通过object对象freepointer指针将slab缓存池所有object对象进行连接。
3.slub分配器使用
内核提供四个API函数来使用slub分配器。

kmem_cache_create函数:用于创建kmem_cache对象,调用成功后返回kem_cache对象指针。
kmem_cache_destroy函数:用于销毁kmem_cache对象。
kmem_cache_alloc函数:用于从slab缓存池申请object对象,调用成功后,返回object对象虚拟地址。
kmem_cache_alloc函数:用于释放object对象释放至slab缓存池。
3.1 创建kmem_cache对象
通过kmem_cache_create函数创建kmem_cache对象,kmem_cache_create函数主要完成一下几个工作:

创建并初始化kmem_cache对象。
创建并初始化kmem_cache_cpu对象。
创建并初始化kmem_cache_node对象。
创建成功后将kmem_cache对象插入slab_caches全局链表。
注意:此时的kmem_cache对象还没有slab缓存池,需要通过kmem_cache_alloc函数动态创建slab缓存池。
3.2 申请object对象
通过kmem_cache_alloc函数向slab缓存池申请object对象。

申请object对象有两种情况:快速路径和慢速路径。 object对象申请slab缓存池优先级:cpu freelist slab缓存池>cpu partial slab缓存池 > node partial slab缓存池 > 伙伴系统 (1)快速路径
如果kmem_cache_cpu对象的slab缓存池(freelist或partial slab内存池)还有空闲的object对象,则直接从cpu slab缓存池申请object对象。
(2)慢速路径
如果cpu slab缓存池没有空闲的object对象,则需要先检查内存节点slab缓存池是否有空闲object对象,如果有则从slab链表摘除slab缓存池插入cpu freelist缓存池,如果没有则从伙伴系统申请slab缓存池并插入cpu freelist缓存池。
3.3 释放object对象
通过kmem_cache_free函数释放至slab缓存池。

内存释放同样分为:快速路径和慢速路径。释放object之前,需要通过object信息定位到page,通过page定位到kmem_cache对象以及object对象slab缓存池所在的链表。
(1)快速路径
直接将object对象释放至cpu freelist slab缓存池。
(2)慢速路径
如果cpu freelist slab缓存池不符合要求,则依次将object对象释放至cpu partial slab缓存池,内存节点partial slab缓存池,或伙伴系统。
3.4 销毁kmem_cache对象
通过kmem_cache_destroy函数销毁kmem_cache对象,销毁的过程和创建的过程相反,这里就不再赘述。
4.slub分配器调试
4.1 /proc/slabinfo
通过cat /proc/slabinfo命令查看系统所有slab内存池信息。

- name:kmem_cache对象名称。
- active_objs:kmem_cache对象已分配object对象数量。
- num_objs:kmem_cache对象object总数量。
- objsize:slab缓存池object对象大小。
- objperslab:slab缓存池可以容纳的object对象数量。
- pagesperslab:slab缓存池需要的物理页数量。
容纳的对象个数
4.2 slabtop命令
使用slabtop命令可以实时监视和显示系统中当前正在使用的kmem_cahce对象相关信息。

4.3 /proc/meminfo
通过cat /proc/meminfo命令可以查看总的slab内存池内存使用情况。 slab(总内存)= SReclaimalbe(可回收内存) + SUnreclaim(不可回收内存)

