定义
用于分配小于一页(4K)的内存,减少内部碎片。Linux 6.x目前slub最大可以分配8K(也就是2页)的内存。
slab、slub、slob
- slub:是对slab分配器的改进,目前Linux里使用的主流。
- slob:是一种轻量级的内存分配器,通常用于嵌入式设备。
由于slub分配器使用最为广泛,重点讨论slub分配器。
slub最小分配单元:字节(object:通常大于8字节)
slub从buddy system批发进货,然后再拆分零售给消费者(使用kmalloc的用户)
数据结构
主要由:kmem_cache,kmem_cache_cpu,kmem_cache_node,slab,object 组成。
- slab_caches
- 所有被Slub的管理的内存叫slab caches。
- 一个slub caches下分为N个slab cache。
- slab cache包含N个slab。
- 每个slab又被划分成N个object。object就是内存分配的对象,也是最小管理单元。
Slab的链表分三种:
- full:slabs_full,完全分配的slab
- partial:slabs_partial,部分分配的slab
- slab:slabs_empty,空slab,或者没有对象被分配。
PS: 在slub的实现中,只剩下了partial
slab caches通过一个叫slab_caches的变量表示,是一个链表,挂载着所有的slab cache。
定义在mm/slab_common.c中:
1. kmem_cache结构体
用于描述一个slub cache,包括slab cache的名称、每个element的size、slab cache的size、etc。
定义于mm/slab.h中:
- name:每个kmem_cache都有自己的名字,可以通过/proc/slabinfo查看。name通过kmem_cache_create函数创建kmem_cache指定。
- list:通过list于其他的kmem_cache连接起来,list head就是slab_caches。
- cpu_slab: 为每个CPU core分配独立的kmem_cache_cpu结构, 每个core通过自己的kmem_cache_cpu直接从本地缓存分配内存对象。
- min_partial :二级回收链表(node[x]->partial)上page数量超过min_partial时,将会把二级回收链表超过的部分,释放回伙伴系统
- object_size:object是slub内存的最小单元,也是管理的基本对象。
- node:是为每个NUMA node保存slab信息struct kmem_cache_node。相当于拥有一个全局的slabs列表,尚未绑定到任何CPU,但是也仍然属于cache,也会包含已经分配的objects。
- cpu_partial: 一级回收链表(cpu_slab->partial)上object超过cpu_partial时,将会把一级回收链表上所有page移动到二级回收链表上
PS:
一级回收链表:s->cpu_slab->partial
二级(回收链表:s->node[x]->partial
2. kmem_cache_cpu结构体
- void **freelist:指向当前cpu slab的空闲object。
- tid:记录最后一个分配/释放对象的CPU id,用于判断是否需要填充空闲对象链表。
- slab:指向正在分配的 slab 页。
- partial:指向所有分配了一部分对象但未完全分配的 slab。
3. kmem_cache_node结构体
是numa node内存节点空闲slab链表。
- nr_partial:当前 NUMA node中 partial slab 链表中 slab 的数量。
- partial:部分已分配的 slab ,包含所有分配了一部分对象但未完全分配的 slab。
4. struct slab
一个slab被一个struct slab结构体所描述,其实就是一个struct page。
在Linux内核5.17版本中,struct slab被引入,目的是将slab相关的字段从struct page中分离出来。在struct slab作为struct page的一个overlay,共享同一块内存,但隐藏了struct page的细节,这样slab分配器只需要处理自己的结构。
一个slab cache可以理解为多个slab的集合,在实现上一个slab有一个或多个连续的物理页组成(通常只有一页),将slab根据size大小划分成一个个object,object就是内存分配的对象。
- slab_cache:指向这个slab属于的slab cache。
- freelist:指向slab中第一个空闲object。
- inuse:slab中已经分配的object数量。
- objects:slab中可以容纳的object数量。
5. object
slub分配器的最小单元。一个object由red left pad,object内存区域,red zone,freepinter,track,padding等部分组成。
- object_size: 真正的object内存区域。
- freelist:指向第一个空闲的object。
- freepointer:指向下一个空闲的object。
- red zone: Linux内核SLAB/SLUB内存中的一种调试和保护机制,用于检测内存越界访问。在object内存区域前后插入red zone,前:red left pad, 后:red zone。如果写入或读取了red zone,内核会触发警告。内核(5.x+)通常通过 CONFIG_SLUB_DEBUG 统一控制红区功能。
总结
函数实现
内核提供4个函数来使用slub:
- kmem_cache_create:创建kmem_cache,成功后返回一个kmem_cache的指针。
- kmem_cache_destroy:销毁kmem_cache。
- kmem_cache_alloc:从slab申请一个object。调用成功后,返回object的虚拟地址。
- kmem_cache_free:释放object到slab。
创建slub cache
1. kmem_cache_init
Slub初始化,只有slub系统已经完成初始化后,才能使用kmalloc。
kmem_cache_init在内核初始化阶段(start_kernel)、伙伴系统启用之后被调用。
2. kmem_cache_create
定义在include/linux/slab.h:
2.1 create_cache
定义在mm/slab_common.c:
(1)kmem_cache也有一个专门的slab,从这个slab申请一块kmem_cache结构体内存。
(2)do_kmem_cache_create:初始化kmem_cache。
(3)把创建好的kmem_cache添加到slab_caches(全局总链表)中。
3. do_kmem_cache_create
初始化分配好的kmem_cache,具体如下:
- 先调用calculate_sizes来计算object中数据的顺序和分布,并计算每一个slab页能容下多少个object。
- 创建并初始化kmem_cache_cpu和kmem_cache_node。
从slub分配内存
分配策略
申请object对象有两种情况:快速路径和慢速路径。
(1)快速路径:如果slab(freelist或partial slab)还有空闲的object对象,则直接从cpu slab申请object。
(2)慢速路径:如果cpu slab没有空闲的object,则需要先检查node slab是否有空闲object,如果有则从slab链表摘除slab插入cpu freelist,如果没有则从伙伴系统申请slab并插入cpu freelist。
总结:object分配优先级:cpu freelist slab>cpu partial slab > node partial slab > buddy system
函数实现
Linux中从slub分配一个object有两个函数,定义在include/linux/slab.h中:
- kmem_cache_alloc:分配一个object
- kmem_cache_alloc_node:从一个指定的NUMA node中分配一个object
这两个函数最终都会调用到slab_alloc_node, kmalloc最终也是调用到这个函数。
1. slab_alloc_node
定义在mm/slub.c中
(1)slab 缓存分配内存之前执行一些必要的检查或操作
(2)kfence是内核引入的一种内存错误检测工具
(3)真正的slub分配函数:__slab_alloc_node
2. __slab_alloc_node
精简函数如下:
(1)从kmem_cache_cpu->free_list里取出object。
(2)如果object为NULL,就走慢速路径分配。
(3)如果object不为空,就把当前这个object分配出去,并把freelist指向下一个可用的object。
3. 快速路径分配
(1)通过object = cpu_slab->freelist,将object取出。
(2)通过get_freepointer_safe寻找下一个可用的object。
kmem_cache->offset指向的就是object中的freeponiter,freepoint中装的就是下一个可用object的地址。将freepointer中的内容copy给p以后,再经过其他加工然后返回。
(3)通过__update_cpu_freelist_fast把新的object更新到cpu_slab->freelist中。
(4)通过prefetch_freepointer,把新object的freepointer指向的地址,预取到cache中。为的是减少cache miss,提高性能。
4. 慢速路径分配:__slab_alloc
__slab_alloc调用___slab_alloc,定义在mm/slub.c 中。
___slab_alloc做的事情可以分为两步:
1. 先检查cpu slab的partital中有没有可用的slab页,如果有就分配。
2. 如果cpu slab的partital为空,就从node slab的partital中找。
通过get_partial从node slab拿,默认如果本node没有,没有经过特殊设置不会从别的node拿。
3. 如果node slab的partital为空,就调用new_slab从buddy system申请。
new_slab的调用栈,一直调用到buddy system的分配函数: