Advertisement

linux内核中slab定义,linux内核内存分配(二、struct slab和struct kmem_cache)

阅读量:

前一篇blog

slab思想

该书阐述了slab(页框)分配器的核心概念:首先通过页面分配器获取单个或一组连续的物理页面;在此基础上将其划分为若干个相等的小内存区域;以满足对小型内存空间进行划分的需求。值得指出的是,在此过程中必须确保对这些小型内存区域的有效管理,并能显著提升整体的内存运行速度与效率。

slab分配器结构

先来看下这张图片。这张图片非常经典(同样也是从其他地方截取的)。通常来说,大多数情况下slab都会显示在这一张图片上。总体上大体相似但有些图表可能会有一些细微差别。

18c7e83999161daad3aa20d0b061144b.png

这是有struct kmem_cache 和 struct slab构成的slab分配器;

就整体而言,在整个应用体系中,默认情况下每个kmem_cache都会被连接成一个完整的双向链表结构,并通过引用指定其所属的位置。系统将从Cache_chain节点依次扫描每一个相关的kmem_cache,并识别出最适合当前需求的规模适中的kmem_cache实例;随后系统将从该选定的实例中动态分配所需的资源。

每个kmem_cache(有时也被称作cache)中都有三条链表。当内存管理过程中会出现命中该** cache 的情况时(实际上也是如此),如果 cache 中的对象大小不一(同样适用于CPU上的缓存),那么就会出现以下几种状态:当某条链表的所有 object 都已分配完毕时(即所谓的 slabs_full ),当部分 object 被分配完毕时(即所谓的 slabs_partial ),以及当某个特定区域尚未被任何 object 占用时(即所谓的 slabs_empty **)。

表示该链表中的object对象全部没有分配出去;

其中每一个slab都由一个或多个连续的内存页构成,并且每个slab都会被划分为多个object对象。这些object对象的动态分配与回收过程均在同一个s lab内部进行管理。因此,在内部管理过程中,slab通常会分布在三条不同的链表中进行存储。具体而言,在所有object都被成功分配后会移动至full链表;当部分object被分配时则会被转移到partial链表;而一旦所有object都被释放完毕,则会转移至empty链表中。特别地,在系统内存趋于紧张的情况下,原本处于empty状态下的某些s lab可能会被重新返回给系统资源池以便利用。

kmem_cache分配

基本的概念就是这样,下面说说简单的代码实现;

首先所有kmem_cache结构均来自cache_cache该内存是在系统处于未完全初始化阶段时被生成的(我发现有两种版本但其含义大致相同):

static kmem_cache_t cache_cache = {

slabs_full: LIST_HEAD_INIT(cache_cache.slabs_full),

slabs_partial: LIST_HEAD_INIT(cache_cache.slabs_partial),

slabs_free: LIST_HEAD_INIT(cache_cache.slabs_free),

objsize: sizeof(kmem_cache_t),

flags: SLAB_NO_REAP,

spinlock: SPIN_LOCK_UNLOCKED,

colour_off: L1_CACHE_BYTES,

name: "kmem_cache",

};

系统分配

先看下面两个结构体:

struct cache_size{

size_t cs_size;

struct kmem_cache *cs_cachep;

}

struct cache_size malloc_sizes[] = {

{.cs_size = 32},

{.cs_size = 64},

{.cs_size = 128},

{.cs_size = 256},

................

{.cs_size = ~0UL},

在内核初始化过程中,在系统内启动时, 会首先调用kmem_cache_init函数对malloc_size数组进行遍历操作, 对于其中每一个具体的元素, 都会依次调用kmem_cache_create()函数为该缓存分配一个完整的kmena_cache实例, 同时将该缓存对象的内存地址赋值给cache_size中对应位置cs_cachep指针所指向的位置(malloc_sizes[x]->cs_cachep)

void __init kmem_cache_init(void)

{

struct cache_size *sizes = malloc_sizes;//数组

struct cache_names *names = cache_names;//cache名称

.....

while(sizes->cs_size != ULONG_MAX){//从32到~0UL都遍历每个元素

if(!sizes->cs_cachep)//表示还没有被初始化

{

// kmem_cache_create就是创建一个kmem_cache

sizes->cs_cachep = kmem_cache_create(names->name, sizes->cs_size,

ARCH_KMALLOC_MINALIGN,

ARCH_KMALLOC_FLAGS|SLAB_PANIC,

NULL);

}

sizes++;

names++;

}

.....

} 初始化后的slab分配器如下图(图片来自于《深入linux设备程序机制》):

fe856645f993a7d1a645d10465b69256.png

从图中可以看出,在malloc相关数据中首先涉及的是malloc_sizes[]数组的第一个元素信息。其中每个cache_size->cs_cachep字段都对应一个kmem_cache字段(即cache_info),它负责分配大小为cs_size的对象空间

请注意这个slab分配器仅仅是一个框架结构它位于kmem_cache下面并包含三条空链表字段这意味着在kmem_cache下并没有任何实际存在的slab page或对象这种情况下我们仅仅设置了对象大小的固定策略此时仅是定义了一个规则以确保所有被映射的对象大小一致

手动分配

所谓手动分配就是在自己程序中进行内存资源的自行分配也就是将内存资源按照需求进行动态管理而不仅仅是依赖于系统提供的内存管理机制这其实就是要深入理解kmem_cache_create()函数的作用

A struct of type kmem_cache is created via the function kmem_cache_create. This function requires five parameters: a const character pointer representing the cache name; a size_t specifying the allocated size; another size_t indicating alignment; an unsigned long used for flags; and finally, a void pointer (*ctor) that points to the constructor function requiring no arguments.

变量 name 是一个指向字符串的指针变量,在程序运行时用于生成 kmem_cache 的名称;这个名称会在 /proc/slabinfo 文件中出现;随后创建的 kmem_cache 对象将通过一个指针引用该 name 变量;因此,在 kmem_cache 对象的有效期内必须始终保证 name 变量的有效性。

参数 size 是用来指定slab分配的对象大小;

参数 align 是表示数据对齐的,一般使用0就可以;

参数 flags 是创建kmem_cache标识位掩码,使用0,表示默认;

参数void *ctor(void *)是构造函数,在为新的内存区域分配空间时会对其所管理的内存对象执行相应的初始化操作。

位于 cache_cache 中的功能将会并返回一个指向 kmem_cache 实例的 *cachep 指针;此外, 该 kmem_cache 对象也将被包含进 cache_chain 链表中。

void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);

其中cachep代表上面所述的kmem_cache对象;该函数负责产生一个空闲内存空间供cachep使用;flags则是在无法获取现有空闲空间时用于从物理页中分配新页面。

kmalloc函数

下面让我们对kmalloc函数进行简要分析。代码移除了多余的内容,并且其目的是为了阐述其工作原理。通过查看下方图表可以清楚了解该函数的作用。

fe856645f993a7d1a645d10465b69256.png

void * kmalloc(size_t size, int flags)

{

struct cache_size *csizep = malloc_sizes;//定义好大小的数组

struct kmem_cache *cachep;

当程序执行到此循环节点时//这是一个核心逻辑,在随后的关键路径上,我们需要从内存资源管理机制中获取可用空间。
其中,该内存块不仅位于malloc_sizes数组中,同时该缓存块也存在于kmem_cache链表中。
我们的算法将依次遍历这两个数据结构中的元素,并在此过程中寻找第一个不小于size的缓存块。
这个过程将反复进行直至找到符合要求的空间为止。

csizep++;

cachep = csizep->cs_cachep;

该函数返回kmem_cache_alloc(cachep, flags)的结果//此处表明实际分配对象仍然是由kmem_cache_alloc()函数负责

}

slab的创建

之前已经提到过,在 kmem_cache_create() 的操作中,并不会从 cache_cache 中真正分配出物理内存块(物理页),因此也不会生成 slab(也不会有相关对象)。那么,在什么情况下才会生成一个 slab 呢?

只有满足下面两个条件时,才会给kmem_cache分配Slab:

(1)已发出一个分配新对象的请求;(2)kmem_cache中没有了空闲对象;

其核心在于:必须获取该kmem_cache下一个对象的需求;而当kmem_cache已无可用空闲空间时,则会导致系统为该缓存块预留一个页框空间。因此,在未被请求的情况下,“新生成的缓存块不会主动发起内存页的获取请求;只有当实际需要时,“才会有相关机制来完成这一任务。”

具体的分配流程:

首先会通过kmem_cache_grow()函数为kmem_cache动态分配一个新的Slab空间。具体而言,该过程首先会使用kmem_gatepages()接口从伙伴系统中获取一组连续的物理页面,随后再通过kmem_cache_slabgmt()获取新的Slab结构,接着为新生成的Slab对象预先分配必要的资源,最后完成该Slab结构到缓冲区中现有Slab链表末尾的链接操作

在slab的分配过程中可以看出,在所有内存资源中,最终都要归伙伴系统进行管理配置;由此可知,这些内存块是连续排列在物理存储介质上的。

版权声明:本文为博主原创文章,未经博主允许不得转载。

原文:

全部评论 (0)

还没有任何评论哟~