0%

Linux内核内存管理 - SLUB

这是<Linux内核内存管理>系列的第六篇

第一篇为内核内存管理过程知识点的的简单梳理

第二篇介绍了内核的数据结构

第三篇介绍了从内核第一行代码加载到跳转到C代码前的内存处理。

第四篇概览了初始化C代码中的内存处理

第五篇(上)第五篇(下)介绍了Memblock和伙伴系统分配器

为了避免晦涩难懂,本文及之后均主要使用图表+文字描述,尽量避免涉及过多代码。专注点会在:

  1. 背景
  2. 架构及其思想
  3. 流程
  4. 特殊处理及其原因

前言

网络上介绍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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
choice
prompt "Choose SLAB allocator"
default SLUB
help
This option allows to select a slab allocator.

config SLAB
bool "SLAB"
select HAVE_HARDENED_USERCOPY_ALLOCATOR
help
The regular slab allocator that is established and known to work
well in all environments. It organizes cache hot objects in
per cpu and per node queues.

config SLUB
bool "SLUB (Unqueued Allocator)"
select HAVE_HARDENED_USERCOPY_ALLOCATOR
help
SLUB is a slab allocator that minimizes cache line usage
instead of managing queues of cached objects (SLAB approach).
Per cpu caching is realized using slabs of objects instead
of queues of objects. SLUB can use memory efficiently
and has enhanced diagnostics. SLUB is the default choice for
a slab allocator.

config SLOB
depends on EXPERT
bool "SLOB (Simple Allocator)"
help
SLOB replaces the stock allocator with a drastically simpler
allocator. SLOB is generally more space efficient but
does not perform as well on large systems.

endchoice

从中默认选项就是SLUB。

KConfig相关知识可以参考KConfig Language

架构

SLAB(下文中SLAB也统一代表SLUB)在系统中的位置如Figure 1所示.

SLUB在内存管理系统中位置

简单说明如下:

  • 内存管理系统的最大管理单位是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的重要的数据结构有三个,其内容和相互关系如下图:

SLUB数据结构

其中:

  • kmem_cache代表一个SLAB对象
  • kmem_cache_cpu里存储了该SLAB对象在CPU本地的资源,这里__percpu修饰表示这是一个Per CPU对象(每个CPU有一份拷贝)
  • kmem_cache_node是一个数组,每个数组成员代表该SLAB对象在每个内存结点的内存资源。

管理方式

SLUB的管理方式如下图:

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分配器的逻辑原理

希望对您分析内核代码有所帮助。