文章目录
背景
前情回顾
[linux kernel]slub内存管理分析(0) 导读
描述方法约定
PS:为了方便描述,这里我们将一个用来切割分配内存的page 称为一个slab page,而struct kmem_cache
我们这里称为slab管理结构,它管理的真个slab 体系成为slab cache,struct kmem_cache_node
这里就叫node。单个堆块称为object或者堆块或内存对象。
slub 简介
linux 内核内存管理算法有管理页面分配与回收的伙伴算法,和对于小块内存的slab、slob、slub算法。其中slab 是slob和slub 的基础,slob 多用于嵌入式设备之中,目前linux 内核中用到的内存管理算法是slub。这次分析slub 的逻辑,本节主要介绍登场的数据结构。
slub 结构总览
slub 数据结构关系图
数据结构简述
对照上面数据结构关系图描述如下:
struct kmem_cache
:每个slab 的管理结构,以该结构体为单位划分不同slab。struct kmem_cache_cpu
:这是一个per_cpu变量,每个cpu为了快速分配都有一个自己的struct kmem_cache_cpu
结构。用于优先分配,可以通过该结构分配到内存就不会继续访问下面的node 结构。struct kmem_cache_node
:在NUMA模式下,会有[系统内存节点数]个node,分别用于管理本节点内分配的内存。struct page
:用于分配的内存来源是内存页,如果一个页被slab系统申请到用于内存分配我们称之为slab页,该页的struct page
结构会使用union 中的slab系列字段,并且该页的内存会被按照该slab管理的内存块大小分割用于分配。
存在一些全局变量用来访问不同的slab管理结构,比如kmalloc_caches
管理若干大小(8-8k)之间的各种不同尺寸slab,用户通常情况下的kmalloc
分配,除此之外一些敏感的结构体会使用自己的slab给自己分配内存,比如struct file
使用名为"filp_cachep"的slab结构分配,"filp_cachep"也只用于分配struct file
结构。不管是通用的slab管理结构还是特殊结构体的slab管理结构都会被slab_caches
双向链表连在一起。
数据结构介绍
我们按照从小到大的顺序,先从页开始介绍,最后介绍slab管理结构struct kmem_cache
。
结构体
page
struct page
用于管理一个内存页,由于操作系统会将内存分割成非常多的页,并且每一个页都要有一个struct page
管理,所以struct page
需要尽可能的小,不能占用太多内存,所以使用了union联合体来代表页在不同用途情况下的信息:
include\linux\mm_types.h : struct page
struct page {
unsigned long flags; /* Atomic flags, some possibly*/
union {
··· ···
struct { /* slab, slob and slub */
union {
struct list_head slab_list; //用于多个slab页双向链表中,如kmem_cache_node->partial
struct { /* Partial pages */
struct page *next; //用在cpu_slab 的partial 中的时候是单向链表
#ifdef CONFIG_64BIT
int pages; /* Nr of pages left */
int pobjects; /* Approximate count */
#else
short int pages;
short int pobjects;
#endif
};
};
struct kmem_cache *slab_cache; /* not slob 指向 自己的slab管理结构kmem_cache */
/* Double-word boundary */
void *freelist; /* first free object 指向该页中第一个空闲堆块 */
union {
void *s_mem; /* slab: first object */
unsigned long counters; /* SLUB 用于快速访问下面的几个字段*/
struct { /* SLUB */
unsigned inuse:16; //有多少堆块正在使用
unsigned objects:15; //该页被分成多少个堆块
unsigned frozen:1; //为1代表该页是否在cpu_slab中
};
};
};
··· ···
};
··· ···
} _struct_page_alignment;
slab_list
:当page被链入比如kmem_cache_node->partial
这种双向链表中时启用next
和上面是联合体,在kmem_cache_cpu
的partial
中的时候是单向链表,使用该字段pages
&pobjects
作为单链表的时候这两个值是统计信息,用于告诉统计者这个页和这个页后面的单链表中的页中有多少页和内存块。
slab_cache
:用于指向自己所属的slab管理结构kmem_cache
,这样可以通过page
结构体找到kmem_cache
freelist
:指向该页中第一个空闲堆块,然后每个空闲堆块中都有next
指针指向下一个空闲堆块形成一个freelist单向链表,但如果该页目前被cpu_slab->page
使用,则freelist
为空,因为cpu_slab->freelist
会指向这个页的freelist
,page
结构体就没必要再维护一个freelist
了。counters
:联合体,代表一些其他信息,使用该字段快速访问inuse
:代表有多少堆块正在使用objects
:该页被分成多少个块用于分配,常用语统计用frozen
:代表该页是否被cpu_slab
使用,如果是1代表正在被kmem_cache_cpu
使用(在kmem_cache_cpu->page
或kmem_cache_cpu->partial
中)
在新版的内核中使用struct slab
来表示上面的这些信息了,但还是复用的struct page
,结构体内容也是一样的,所以不重复介绍了。
kmem_cache_cpu
kmem_cache_cpu
结构体是每个cpu都会申请的用来快速分配内存的结构,如果kmem_cache_cpu->freelist
有可用内存,则直接从freelist
中分配并返回。
linux\include\linux\slub_def.h : kmem_cache_cpu
struct kmem_cache_cpu {
void **freelist; /* 指向下一个可用的free object用于快速分配 */
unsigned long tid; /* Globally unique transaction id */
//用于保证cmpxchg_double计算发生在正确的CPU上,并且可作为一个锁保证不会同时申请这个kmem_cache_cpu的对象
struct page *page; /* 指向slab 对象所处的页结构体 */
#ifdef CONFIG_SLUB_CPU_PARTIAL
struct page *partial; /* 没有分配完的页面(还有空闲但不是全空闲) */
#endif
#ifdef CONFIG_SLUB_STATS
unsigned stat[NR_SLUB_STAT_ITEMS];//一个enum
#endif
};
一个kmem_cache_cpu
结构代表一个CPU的快速分配通道,其中:
freelist
:只是这里指向可以立即使用的空闲堆块,空闲堆块通过单链表进行链接,任何场景下kmalloc
申请都会在kmem_cache_cpu->freelist
中有内容的时候优先分配tid
:类似锁,也有一些统计作用。每次进行一次kmem_cache_cpu
操作之后都会增加page
:该kmem_cache_cpu
管理的页,即指向freelist
指向的内存所在的页的struct page
结构。partial
:开启宏CONFIG_SLUB_CPU_PARTIAL
才会有,默认开启。曾经分配完但是有一些被释放了的,目前没有完全分配完的页面的单链表stat
:一个状态数组,状态内容如下表所示,会把相应的stat[i]
设置1 来确定状态:
enum stat_item {
ALLOC_FASTPATH, /* Allocation from cpu slab */
··· ···
CPU_PARTIAL_DRAIN, /* Drain cpu partial to node partial */
NR_SLUB_STAT_ITEMS };
kmem_cache_node
kmem_cache_node
作为每个NUMA节点的结构体,为了方便NUMA架构的本地内存分配,对每个node都会设置一个管理本地内存中slab 的结构体:
linux-5.13\mm\slab.h : kmem_cache_node
struct kmem_cache_node {
spinlock_t list_lock;
#ifdef CONFIG_SLAB //slub 时不开启
struct list_head slabs_partial; /* partial list first, better asm code */
··· ···
#endif
#ifdef CONFIG_SLUB
unsigned long nr_partial; /*本节点的Partial slab的数目*/
struct list_head partial; /*Partial slab的双向链表*/
#ifdef CONFIG_SLUB_DEBUG
atomic_long_t nr_slabs; //node中总slab 数量
atomic_long_t total_objects;//node中总内存对象数
struct list_head full; /*分配满的list*/
#endif
#endif
};
由于我们分析的是slub,所以可以忽略slab 部分(slub slab slob 不共存)。
nr_partial
:该node中的Partial slab的数目,即下面partial
列表有多少个slab page。partial
:指向page 结构体中的list_head slab_list
;一个链接多个struct page
结构体的双链表。nr_slabs
:该node中所有slab 数量,这里统计所有在node中的,包括partial
、full
和cpu_slab
中的,只要在这个node中分配的slab 就会记录。total_objects
:记录同上,只是记录的是所有的内存块数量,根据slab 管理的内存对象尺寸不同,一个slab中包括不同的内存块数量,和上面nr_slabs
有一定的对应关系。full
:同nr_partial
,已经满了的页面列表,双链表。只在开启CONFIG_SLUB_DEBUG
的时候才存在,默认不开启。
在slub 模式下是没有free 列表的,因为如果一个slab 的内存对象都释放掉了,如果现存slab 数量满足最小要求,就会将空slab 直接释放掉,不满足最小要求,就还在原list里呆着。
kmem_cache
kmem_cache
是slub 的核心管理结构,每一个kmem_cache
代表一个slab 的信息:
linux\include\linux\slub_def.h : kmem_cache
struct kmem_cache {
struct kmem_cache_cpu __percpu *cpu_slab; //每个cpu 都有的结构cpu 用于快速分配
/* Used for retrieving partial slabs, etc. */
slab_flags_t flags;
unsigned long min_partial; //node->partial中需要保留的最小slab数量
unsigned int size; /* slab 中每个object内存对象的大小 */
unsigned int object_size;/* 该slab 分配的对象实际大小,通常情况一般和上面的size一样大 */
struct reciprocal_value reciprocal_size;
unsigned int offset; /* 空闲对象指向下一个对象的指针偏移next=*(object+offset) 一般情况下是size的一半*/
#ifdef CONFIG_SLUB_CPU_PARTIAL
/* Number of per cpu partial objects to keep around */
unsigned int cpu_partial; //每个cpu_slab可以保留的partial slab 内存块数的最大值
#endif
struct kmem_cache_order_objects oo;
/* 存放分配给slab的页框的阶数(高16位)和slab中的对象数量(低16位)
* 高两字节代表一个slab page 的阶数,可以计算出页数
* 低两字节代表一个slab page 切割出的object 内存对象数 */
/* Allocation and freeing of slabs */
struct kmem_cache_order_objects max; //结构同上,只不过代表最大值
struct kmem_cache_order_objects min; //结构同上,只不过代表最小值
gfp_t allocflags; /* 申请slab page时使用的GFP标识 */
int refcount; /* Refcount for slab cache destroy 引用计数*/
void (*ctor)(void *); //构造函数,现在没啥用了
unsigned int inuse; /* Offset to metadata */
unsigned int align; /* Alignment */
unsigned int red_left_pad; /* 检测越界相关,放到objects的左侧检测左越界*/
const char *name; /* slab 名字字符串 */
struct list_head list; /* slab 链表,用来管理所有的kmem_cache */
#ifdef CONFIG_SYSFS
struct kobject kobj; /* 用于sys文件系统 */
#endif
#ifdef CONFIG_SLAB_FREELIST_HARDENED
unsigned long random; //开启CONFIG_SLAB_FREELIST_HARDENED 相关
#endif
#ifdef CONFIG_NUMA
/* NUMA中尽可能在本节点分配对象的倾向值
* Defragmentation by allocating from a remote node.
*/
unsigned int remote_node_defrag_ratio;
#endif
#ifdef CONFIG_SLAB_FREELIST_RANDOM
unsigned int *random_seq;//开启CONFIG_SLAB_FREELIST_RANDOM 相关
#endif
#ifdef CONFIG_KASAN
struct kasan_cache kasan_info; //kasan相关
#endif
unsigned int useroffset; /* Usercopy region offset */
unsigned int usersize; /* Usercopy region size */
struct kmem_cache_node *node[MAX_NUMNODES];
/* 各个NUMA节点的数组
* 每个node对应node数组中的一个
* 不在cpu_slab中的slab 都放在node 节点中管理
*/
};
cpu_slab
:每个cpu都有一个cpu_slab
用于快速分配,就是上面介绍的kmem_cache_cpu
结构体。min_partial
:node->partial
中的slab 数量必须满足这个min_partial
要求的最小数量。size
:代表这个slab分配一个堆块的大小是多少object_size
:代表使用该内存对象的结构体的实际大小,一般情况和size
一样大。但在一些特殊slab中,比如专为分配struct file
准备的slabfilp_cachep
,size
为该slab的堆块分配实际大小,比如256,而object_size
为使用这个slab的struct file
的结构体大小sizeof(struct file)
可能是232,比size
小一些,也就是size
大小是满足这个object_size
的最小2的次方数的大小(slab只能是这种大小)。offset
:空闲内存对象通过单链表的形势链接起来,也就是freelist
列表。offset
代表next
指针在对象中的偏移,一般是size / 2
cpu_partial
:cpu_slab->partial
中的空闲内存块数量最大值oo
:高2字节代表一个slab page 的阶数,低2字节代表一个slab 中内存对象的数量,如分配0x1000 大小内存对象的slab 的oo=0x30008:3
:一个slab page 3阶也就是8页0x8000。8
:一个slab page 可以分配8个0x1000的内存对象
list
:所有的kmem_cache
都会被链接到一个链表中,用全局变量slab_caches
访问。random
:开启CONFIG_SLAB_FREELIST_HARDENED
宏的时候需要用到的random_seq
:开启CONFIG_SLAB_FREELIST_RANDOM
宏是需要用到的额useroffset
&usersize
:使用该slab cache分配的堆块的数据在堆块里的偏移和大小,一般是CONFIG_SLAB_MERGE_DEFAULT
(slab cache 重用)配置相关。node[MAX_NUMNODES]
:最重要的成员之一,除了在cpu_slab
之中的slab 之外,其他的slab 都会放在node中管理,对于NUMA架构的每一个node节点,都会有一个node结构体管理本节点内内存的slab,共有内存节点个数个node(一般也就个位数个node,平时的实验环境一般1个node)。
全局变量
kmalloc_caches
kmem_cache
有一个对应的全局数组:kmalloc_caches
,作为kmalloc分配的slab 列表,声明如下:
include\linux\slab.h : kmalloc_caches
extern struct kmem_cache *
kmalloc_caches[NR_KMALLOC_TYPES][KMALLOC_SHIFT_HIGH + 1];
//其中
enum kmalloc_cache_type {
KMALLOC_NORMAL = 0,
KMALLOC_RECLAIM,
#ifdef CONFIG_ZONE_DMA
KMALLOC_DMA,
#endif
NR_KMALLOC_TYPES
};
#define KMALLOC_SHIFT_HIGH (PAGE_SHIFT + 1)//PAGE_SHIFT 12 总之就是一页
其实可以看出,把slab 按照种类分成了NR_KMALLOC_TYPES
类,其中每类又按照大小有12种。但并不是严格从小到大的,最小的slab 是8字节的,用下标表示2 的x 次幂的大小,那么8是2的3次幂,而下标1 和2 分别表示size 96 和size 192,最大可分配8k大小的堆块。在初始化时由kmalloc_info
数组控制:
const struct kmalloc_info_struct kmalloc_info[] __initconst = {
INIT_KMALLOC_INFO(0, 0),
INIT_KMALLOC_INFO(96, 96),
INIT_KMALLOC_INFO(192, 192),
INIT_KMALLOC_INFO(8, 8),
INIT_KMALLOC_INFO(16, 16),
INIT_KMALLOC_INFO(32, 32),
··· ···
INIT_KMALLOC_INFO(33554432, 32M),
INIT_KMALLOC_INFO(67108864, 64M)
};
这些slab 为通用slab,我们可以通过cat /proc/slabinfo
查看所有slab 的分配情况,其中名为"kmalloc-xx"的就是通用slab。
其他slab管理结构
并不是所有内核的小块内存分配都会使用kmalloc
,有些敏感的结构体不想和其他的堆块连着,因为一旦发生溢出什么的,将会给利用带来极大便利,所以他们使用自己独有的slab 为自己分配内存,这些slab 也只会为这些结构体服务。
fs\file_table.c
static struct kmem_cache *filp_cachep __read_mostly; //用于分配struct file 的slab filp_cachep
void __init files_init(void)//初始化filp_cachep
{
filp_cachep = kmem_cache_create("filp", sizeof(struct file), 0,
SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT, NULL);
percpu_counter_init(&nr_files, 0, GFP_KERNEL);
}
static struct file *__alloc_file(int flags, const struct cred *cred) //专属分配函数
{
struct file *f;
··· ···
f = kmem_cache_zalloc(filp_cachep, GFP_KERNEL);
··· ···
}
slab_caches
用于链接所有slab 的双向链表,每个slab 初始化完毕都会加入它。