这是<Linux内核内存管理>系列的第六篇
第一篇为内核内存管理过程知识点的的简单梳理
第二篇介绍了内核的数据结构
第三篇介绍了从内核第一行代码加载到跳转到C代码前的内存处理。
第四篇概览了初始化C代码中的内存处理
为了避免晦涩难懂,本文及之后均主要使用图表+文字描述,尽量避免涉及过多代码。专注点会在:
- 背景
- 架构及其思想
- 流程
- 特殊处理及其原因
前言
网络上介绍SLAB/SLUB的文章很多,也都很详细,本文以当前内核版本(5.14.X)来介绍被广泛采用的SLAB内存管理,希望尽可能地做到详尽易理解。一些更多的参考资料见,文中不再另外标注引用:
kmalloc/kfree 大概是内核最常用的内存分配和释放函数,其背后的实现就是SLAB分配器。而SLUB是SLAB分配器的一种实现,另外的两种实现分别是SLAB和SLOB。从命名也可以看出SLAB是鼻祖,随着内核的发展,演进出了SLOB和SLUB分配器。
- SLOB分配器是为了应对嵌入式设备内存管理的特殊需求而生
- SLUB则是在SLAB的基础上演进而来,可以很好地适应各种平台的需求,更为有效地使用内存,同时增强了调试的容易度。
SLAB分配器解决的是什么问题?这个问题可以换种方式来问,为什么有了Buddy System,还要用SLAB分配器? 解释如下:
- 伙伴系统是以页为单位进行管理的,每页的大小一般为4096字节。内核程序在申请内存时,往往不会刚好申请页大小的倍数,如果我们按页进行分配,没过多久系统内存就会被耗尽。
- 因为上述原因,必须采用更小的单位对内存进行管理。这需要考虑频繁内存分配释放造成的内存碎片问题。与此同时,需要考虑如何更有效地利用CPU缓存,以及尽量避免访问同一块内存区域造成的静态,等等。
为什么这三种分配器又都是SLAB分配器的实现?这是因为这三种分配器采用一样的数据结构名称和内存分配/释放API(注意,仅仅是“名称”一样)。例,其管理结构体,都叫struct kmem_cache。
内核配置
前言讲到,SLAB/SLOB/SLUB采用相同的API,相同的结构体,那么他们一定是相互排他的,这从内核定义KConfig也可以看出:
1 | choice |
从中默认选项就是SLUB。
KConfig相关知识可以参考KConfig Language
架构
SLAB(下文中SLAB也统一代表SLUB)在系统中的位置如Figure 1所示.
简单说明如下:
- 内存管理系统的最大管理单位是Node,被划分为多个Memory Zone(ToDo: 上一章Buddy System和第二章补充图片示意)。
- 在进行Page分配时(Page分配还记得吗?可以参考第五篇(上)和第五篇(下)对Buddy System的介绍),是依据分配传入的参数,选择从哪个Memory Zone分配内存。
- SLAB的分配需要kmem_cache管理结构,而这些管理结构所需的内存也是从的kmem_cache来的。这里内核做了很巧妙的设计:
- 建立kmem_cache的slab所需的slab初始管理对象是boot_kmem_cache和boot_kmem_cache_node。它们被__init属性修饰,表示将会被放到.init.data段,并在内核初始化的后半段释放。
- 在整个SLAB系统初始化过程中,会从boot_kmem_cache和boot_kmem_cache_node指向的SLAB,分配出全局SLAB对象kmem_cache和kmem_cache_node。
- 之后将boot_kmem_cache和boot_kmem_cache_node内容拷贝到kmem_cache和kmem_cache_node。
- 至此,使用全局SLAB对象kmem_cache和kmem_cache_node来进行SLUB管理。
- kmalloc的内存也是内核初始化早期分配的。其实质上是建立了大小分别为2, 4, 8, ….的SLAB对象。
源文件
下表介绍SLAB及SLUB相关内核源文件:
文件 | 描述 |
---|---|
slab.c | SLAB分配器(三个分配器之一)的实现 |
slab.h | 所有SLAB分配器的头文件定义 |
slob.c | SLOB分配器的实现 |
slub.c | SLUB分配器的实现 |
slab_common.c | 所有SLAB分配器公用的,与实现无关的函数。大部分都会调用到具体的某个分配器。 |
数据结构
SLAB的重要的数据结构有三个,其内容和相互关系如下图:
其中:
- kmem_cache代表一个SLAB对象
- kmem_cache_cpu里存储了该SLAB对象在CPU本地的资源,这里__percpu修饰表示这是一个Per CPU对象(每个CPU有一份拷贝)
- kmem_cache_node是一个数组,每个数组成员代表该SLAB对象在每个内存结点的内存资源。
管理方式
SLUB的管理方式如下图:
简单描述如下:
- 每个SLUB管理结构分别有多个cpu本地slab和node slab。
- SLUB刚建立时,只有对应的管理结构。
- SLUB分配内存时
- 若此时该对象中没有页面可用,则从伙伴系统中分配页面、挂到cpu本地slab上,从中返回一个所需内存。
- 若此时该对象中有可用页面,则从中分配内存。
- 若当前kmem_cache_cpu已经没有可用页面(kmem_cache_cpu的freelist和partial所指向页都满),则从kmem_cache_node的partial处分配内存
这样处理可以保证总是优先从该cpu的cache区域分配,提升资源的访问速度。
- 释放内存:会先将内存释放到该内存所在页。 释放有如下情况:
场景 | 释放方式 |
---|---|
释放前该页上内存已经全部使用,per cpu partial链表上的空闲可用内存总数 > kmem_cache.cpu_partial | 1. 将kmem_cache_cpu的partial链表上的页挂到per node partial。 2.将该内存所在页放回kmem_cache_cpu的partial链表 |
释放前该页上内存已经全部使用,per cpu partial链表上的空闲可用内存总数 <= kmem_cache.cpu_partial | 将该内存所在页放回kmem_cache_cpu的partial链表 |
1. 该页在per node partial 2.释放后,该页呈未分配状态 3.kmem_cache_node.nr_partial > kmem_cache.min_partial | 将该页归还给伙伴系统 |
其他 | / |
设置阈值的主要目的是为了避免SLAB占用过多的内存页,导致系统中其他对象想要分配内存时拿不到内存。
总结
本文介绍了SLAB内存分配器,其是整个系统运行中,起重要且主要作用的内存分配器。介绍了:
- SLAB分配器的分类
- SLUB分配器的架构
- SLUB分配器的逻辑原理
希望对您分析内核代码有所帮助。