天天看点

【原创】(十一)Linux内存管理slub分配器

<code>Read the fucking source code!</code> --By 鲁迅

<code>A picture is worth a thousand words.</code> --By 高尔基

说明:

Kernel版本:4.14

ARM64处理器,Contex-A53,双核

使用工具:Source Insight 3.5, Visio

之前的文章分析的都是基于页面的内存分配,而小块内存的分配和管理是通过块分配器来实现的。目前内核中,有三种方式来实现小块内存分配:<code>slab, slub, slob</code>,最先有<code>slab</code>分配器,<code>slub/slob</code>分配器是改进版,<code>slob</code>分配器适用于小内存嵌入式设备,而<code>slub</code>分配器目前已逐渐成为主流块分配器。接下来的文章,就是以<code>slub</code>分配器为目标,进一步深入。

先来一个初印象:

【原创】(十一)Linux内存管理slub分配器

有四个关键的数据结构:

<code>struct kmem_cache</code>:用于管理<code>SLAB缓存</code>,包括该缓存中对象的信息描述,per-CPU/Node管理slab页面等;

关键字段如下:

<code>struct kmem_cache_cpu</code>:用于管理每个CPU的<code>slab页面</code>,可以使用无锁访问,提高缓存对象分配速度;

<code>struct kmem_cache_node</code>:用于管理每个Node的<code>slab页面</code>,由于每个Node的访问速度不一致,<code>slab</code>页面由Node来管理;

<code>struct page</code>:用于描述<code>slab页面</code>,<code>struct page</code>结构体中很多字段都是通过<code>union</code>联合体进行复用的。

<code>struct page</code>结构中,用于<code>slub</code>的成员如下:

图来了:

【原创】(十一)Linux内存管理slub分配器

针对Slub的使用,可以从三个维度来分析:

slub缓存创建

slub对象分配

slub对象释放

下边将进一步分析。

在内核中通过<code>kmem_cache_create</code>接口来创建一个<code>slab缓存</code>。

先看一下这个接口的函数调用关系图:

【原创】(十一)Linux内存管理slub分配器

<code>kmem_cache_create</code>完成的功能比较简单,就是创建一个用于管理<code>slab缓存</code>的<code>kmem_cache</code>结构,并对该结构体进行初始化,最终添加到全局链表中。<code>kmem_cache</code>结构体初始化,包括了上文中分析到的<code>kmem_cache_cpu</code>和<code>kmem_cache_node</code>两个字段结构。

在创建的过程中,当发现已有的<code>slab缓存</code>中,有存在对象大小相近,且具有兼容标志的<code>slab缓存</code>,那就只需要进行merge操作并返回,而无需进一步创建新的<code>slab缓存</code>。

<code>calculate_sizes</code>函数会根据指定的<code>force_order</code>或根据对象大小去计算<code>kmem_cache</code>结构体中的<code>size/min/oo</code>等值,其中<code>kmem_cache_order_objects</code>结构体,是由页面分配<code>order</code>值和<code>对象数量</code>两者通过位域拼接起来的。

在创建<code>slab缓存</code>的时候,有一个先鸡后蛋的问题:<code>kmem_cache</code>结构体来管理一个<code>slab缓存</code>,而创建<code>kmem_cache</code>结构体又是从<code>slab缓存</code>中分配出来的对象,那么这个问题是怎么解决的呢?可以看一下<code>kmem_cache_init</code>函数,内核中定义了两个静态的全局变量<code>kmem_cache</code>和<code>kmem_cache_node</code>,在<code>kmem_cache_init</code>函数中完成了这两个结构体的初始化之后,相当于就是创建了两个<code>slab缓存</code>,一个用于分配<code>kmem_cache</code>结构体对象的缓存池,一个用于分配<code>kmem_cache_node</code>结构体对象的缓存池。由于<code>kmem_cache_cpu</code>结构体是通过<code>__alloc_percpu</code>来分配的,因此不需要创建一个相关的<code>slab缓存</code>。

<code>kmem_cache_alloc</code>接口用于从slab缓存池中分配对象。

看一下大体的调用流程图:

【原创】(十一)Linux内存管理slub分配器

从上图中可以看出,分配slab对象与<code>Buddy System</code>中分配页面类似,存在快速路径和慢速路径两种,所谓的快速路径就是<code>per-CPU缓存</code>,可以无锁访问,因而效率更高。

整体的分配流程大体是这样的:优先从<code>per-CPU缓存</code>中进行分配,如果<code>per-CPU缓存</code>中已经全部分配完毕,则从<code>Node</code>管理的slab页面中迁移<code>slab页</code>到<code>per-CPU缓存</code>中,再重新分配。当<code>Node</code>管理的slab页面也不足的情况下,则从<code>Buddy System</code>中分配新的页面,添加到<code>per-CPU缓存</code>中。

还是用图来说明更清晰,分为以下几步来分配:

<code>fastpath</code>

快速路径下,以原子的方式检索per-CPU缓存的freelist列表中的第一个对象,如果freelist为空并且没有要检索的对象,则跳入慢速路径操作,最后再返回到快速路径中重试操作。

【原创】(十一)Linux内存管理slub分配器

<code>slowpath-1</code>

将per-CPU缓存中page指向的slab页中的空闲对象迁移到freelist中,如果有空闲对象,则freeze该页面,没有空闲对象则跳转到<code>slowpath-2</code>。

【原创】(十一)Linux内存管理slub分配器

<code>slowpath-2</code>

将per-CPU缓存中partial链表中的第一个slab页迁移到page指针中,如果partial链表为空,则跳转到<code>slowpath-3</code>。

【原创】(十一)Linux内存管理slub分配器

<code>slowpath-3</code>

将Node管理的partial链表中的slab页迁移到per-CPU缓存中的page中,并重复第二个slab页将其添加到per-CPU缓存中的partial链表中。如果迁移的slab中空闲对象超过了<code>kmem_cache.cpu_partial</code>的一半,则仅迁移<code>slab页</code>,并且不再重复。

如果每个Node的partial链表都为空,跳转到<code>slowpath-4</code>。

【原创】(十一)Linux内存管理slub分配器

<code>slowpath-4</code>

从<code>Buddy System</code>中获取页面,并将其添加到per-CPU的page中。

【原创】(十一)Linux内存管理slub分配器

<code>kmem_cache_free</code>的操作,可以看成是<code>kmem_cache_alloc</code>的逆过程,因此也分为快速路径和慢速路径两种方式,同时,慢速路径中又分为了好几种情况,可以参考<code>kmem_cache_alloc</code>的过程。

调用流程图如下:

【原创】(十一)Linux内存管理slub分配器

效果如下:

快速路径释放

快速路径下,直接将对象返回到freelist中即可。

【原创】(十一)Linux内存管理slub分配器

put_cpu_partial

<code>put_cpu_partial</code>函数主要是将一个刚freeze的slab页,放入到partial链表中。

在<code>put_cpu_partial</code>函数中调用<code>unfreeze_partials</code>函数,这时候会将per-CPU管理的partial链表中的slab页面添加到Node管理的partial链表的尾部。如果超出了Node的partial链表,溢出的slab页面中没有分配对象的slab页面将会返回到伙伴系统。

【原创】(十一)Linux内存管理slub分配器

add_partial

添加slab页到Node的partial链表中。

【原创】(十一)Linux内存管理slub分配器

remove_partial

从Node的partial链表移除slab页。

【原创】(十一)Linux内存管理slub分配器

具体释放的流程走哪个分支,跟对象的使用情况,partial链表的个数<code>nr_partial/min_partial</code>等相关,细节就不再深入分析了。

作者:LoyenWang

出处:https://www.cnblogs.com/LoyenWang/

公众号:<b>LoyenWang</b>

版权:本文版权归作者和博客园共有

转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任

继续阅读