【原创】(十一)Linux内存管理slub分配器
背景
Read the fucking source code!
--By 鲁迅A picture is worth a thousand words.
--By 高尔基
说明:
- Kernel版本:4.14
- ARM64处理器,Contex-A53,双核
- 使用工具:Source Insight 3.5, Visio
1. 概述
之前的文章分析的都是基于页面的内存分配,而小块内存的分配和管理是通过块分配器来实现的。目前内核中,有三种方式来实现小块内存分配:slab, slub, slob
,最先有slab
分配器,slub/slob
分配器是改进版,slob
分配器适用于小内存嵌入式设备,而slub
分配器目前已逐渐成为主流块分配器。接下来的文章,就是以slub
分配器为目标,进一步深入。
先来一个初印象:
2. 数据结构
有四个关键的数据结构:
struct kmem_cache
:用于管理SLAB缓存
,包括该缓存中对象的信息描述,per-CPU/Node管理slab页面等;
关键字段如下:
/*
* Slab cache management.
*/
struct kmem_cache {
struct kmem_cache_cpu __percpu *cpu_slab; //每个CPU slab页面
/* Used for retriving partial slabs etc */
unsigned long flags;
unsigned long min_partial;
int size; /* The size of an object including meta data */
int object_size; /* The size of an object without meta data */
int offset; /* Free pointer offset. */
#ifdef CONFIG_SLUB_CPU_PARTIAL
/* Number of per cpu partial objects to keep around */
unsigned int cpu_partial;
#endif
struct kmem_cache_order_objects oo; //该结构体会描述申请页面的order值,以及object的个数
/* Allocation and freeing of slabs */
struct kmem_cache_order_objects max;
struct kmem_cache_order_objects min;
gfp_t allocflags; /* gfp flags to use on each alloc */
int refcount; /* Refcount for slab cache destroy */
void (*ctor)(void *); // 对象构造函数
int inuse; /* Offset to metadata */
int align; /* Alignment */
int reserved; /* Reserved bytes at the end of slabs */
int red_left_pad; /* Left redzone padding size */
const char *name; /* Name (only for display!) */
struct list_head list; /* List of slab caches */ //kmem_cache最终会链接在一个全局链表中
struct kmem_cache_node *node[MAX_NUMNODES]; //Node管理slab页面
};
struct kmem_cache_cpu
:用于管理每个CPU的slab页面
,可以使用无锁访问,提高缓存对象分配速度;
struct kmem_cache_cpu {
void **freelist; /* Pointer to next available object */ //指向空闲对象的指针
unsigned long tid; /* Globally unique transaction id */
struct page *page; /* The slab from which we are allocating */ //slab缓存页面
#ifdef CONFIG_SLUB_CPU_PARTIAL
struct page *partial; /* Partially allocated frozen slabs */
#endif
#ifdef CONFIG_SLUB_STATS
unsigned stat[NR_SLUB_STAT_ITEMS];
#endif
};
struct kmem_cache_node
:用于管理每个Node的slab页面
,由于每个Node的访问速度不一致,slab
页面由Node来管理;
/*
* The slab lists for all objects.
*/
struct kmem_cache_node {
spinlock_t list_lock;
#ifdef CONFIG_SLUB
unsigned long nr_partial; //slab页表数量
struct list_head partial; //slab页面链表
#ifdef CONFIG_SLUB_DEBUG
atomic_long_t nr_slabs;
atomic_long_t total_objects;
struct list_head full;
#endif
#endif
};
struct page
:用于描述slab页面
,struct page
结构体中很多字段都是通过union
联合体进行复用的。
struct page
结构中,用于slub
的成员如下:
struct page {
union {
...
void *s_mem; /* slab first object */
...
};
/* Second double word */
union {
...
void *freelist; /* sl[aou]b first free object */
...
};
union {
...
struct {
union {
...
struct { /* SLUB */
unsigned inuse:16;
unsigned objects:15;
unsigned frozen:1;
};
...
};
...
};
};
/*
* Third double word block
*/
union {
...
struct { /* slub per cpu partial pages */
struct page *next; /* Next partial slab */
#ifdef CONFIG_64BIT
int pages; /* Nr of partial slabs left */
int pobjects; /* Approximate # of objects */
#else
short int pages;
short int pobjects;
#endif
};
struct rcu_head rcu_head; /* Used by SLAB
* when destroying via RCU
*/
};
...
struct kmem_cache *slab_cache; /* SL[AU]B: Pointer to slab */
...
}
图来了:
3. 流程分析
针对Slub的使用,可以从三个维度来分析:
- slub缓存创建
- slub对象分配
- slub对象释放
下边将进一步分析。
3.1 kmem_cache_create
在内核中通过kmem_cache_create
接口来创建一个slab缓存
。
先看一下这个接口的函数调用关系图:
kmem_cache_create
完成的功能比较简单,就是创建一个用于管理slab缓存
的kmem_cache
结构,并对该结构体进行初始化,最终添加到全局链表中。kmem_cache
结构体初始化,包括了上文中分析到的kmem_cache_cpu
和kmem_cache_node
两个字段结构。在创建的过程中,当发现已有的
slab缓存
中,有存在对象大小相近,且具有兼容标志的slab缓存
,那就只需要进行merge操作并返回,而无需进一步创建新的slab缓存
。calculate_sizes
函数会根据指定的force_order
或根据对象大小去计算kmem_cache
结构体中的size/min/oo
等值,其中kmem_cache_order_objects
结构体,是由页面分配order
值和对象数量
两者通过位域拼接起来的。在创建
slab缓存
的时候,有一个先鸡后蛋的问题:kmem_cache
结构体来管理一个slab缓存
,而创建kmem_cache
结构体又是从slab缓存
中分配出来的对象,那么这个问题是怎么解决的呢?可以看一下kmem_cache_init
函数,内核中定义了两个静态的全局变量kmem_cache
和kmem_cache_node
,在kmem_cache_init
函数中完成了这两个结构体的初始化之后,相当于就是创建了两个slab缓存
,一个用于分配kmem_cache
结构体对象的缓存池,一个用于分配kmem_cache_node
结构体对象的缓存池。由于kmem_cache_cpu
结构体是通过__alloc_percpu
来分配的,因此不需要创建一个相关的slab缓存
。
3.2 kmem_cache_alloc
kmem_cache_alloc
接口用于从slab缓存池中分配对象。
看一下大体的调用流程图:
从上图中可以看出,分配slab对象与Buddy System
中分配页面类似,存在快速路径和慢速路径两种,所谓的快速路径就是per-CPU缓存
,可以无锁访问,因而效率更高。
整体的分配流程大体是这样的:优先从per-CPU缓存
中进行分配,如果per-CPU缓存
中已经全部分配完毕,则从Node
管理的slab页面中迁移slab页
到per-CPU缓存
中,再重新分配。当Node
管理的slab页面也不足的情况下,则从Buddy System
中分配新的页面,添加到per-CPU缓存
中。
还是用图来说明更清晰,分为以下几步来分配:
fastpath
快速路径下,以原子的方式检索per-CPU缓存的freelist列表中的第一个对象,如果freelist为空并且没有要检索的对象,则跳入慢速路径操作,最后再返回到快速路径中重试操作。
slowpath-1
将per-CPU缓存中page指向的slab页中的空闲对象迁移到freelist中,如果有空闲对象,则freeze该页面,没有空闲对象则跳转到slowpath-2
。
slowpath-2
将per-CPU缓存中partial链表中的第一个slab页迁移到page指针中,如果partial链表为空,则跳转到slowpath-3
。
slowpath-3
将Node管理的partial链表中的slab页迁移到per-CPU缓存中的page中,并重复第二个slab页将其添加到per-CPU缓存中的partial链表中。如果迁移的slab中空闲对象超过了kmem_cache.cpu_partial
的一半,则仅迁移slab页
,并且不再重复。
如果每个Node的partial链表都为空,跳转到slowpath-4
。
slowpath-4
从Buddy System
中获取页面,并将其添加到per-CPU的page中。
3.2 kmem_cache_free
kmem_cache_free
的操作,可以看成是kmem_cache_alloc
的逆过程,因此也分为快速路径和慢速路径两种方式,同时,慢速路径中又分为了好几种情况,可以参考kmem_cache_alloc
的过程。
调用流程图如下:
效果如下:
快速路径释放
快速路径下,直接将对象返回到freelist中即可。
put_cpu_partial
put_cpu_partial
函数主要是将一个刚freeze的slab页,放入到partial链表中。
在put_cpu_partial
函数中调用unfreeze_partials
函数,这时候会将per-CPU管理的partial链表中的slab页面添加到Node管理的partial链表的尾部。如果超出了Node的partial链表,溢出的slab页面中没有分配对象的slab页面将会返回到伙伴系统。
add_partial
添加slab页到Node的partial链表中。
remove_partial
从Node的partial链表移除slab页。
具体释放的流程走哪个分支,跟对象的使用情况,partial链表的个数nr_partial/min_partial
等相关,细节就不再深入分析了。
【原创】(十一)Linux内存管理slub分配器的更多相关文章
- Linux内存管理 - slab分配器和kmalloc
本文目的在于分析Linux内存管理机制的slab分配器.内核版本为2.6.31.1. SLAB分配器 内核需要经常分配内存,我们在内核中最常用的分配内存的方式就是kmalloc了.前面讲过的伙伴系统只 ...
- Linux内存管理 (5)slab分配器
专题:Linux内存管理专题 关键词:slab/slub/slob.slab描述符.kmalloc.本地/共享对象缓冲池.slabs_partial/slabs_full/slabs_free.ava ...
- Linux内存管理之slab分配器
slab分配器是什么? 参考:http://blog.csdn.net/vanbreaker/article/details/7664296 slab分配器是Linux内存管理中非常重要和复杂的一部分 ...
- 【原创】(六)Linux内存管理 - zoned page frame allocator - 1
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...
- Linux内存管理原理
本文以32位机器为准,串讲一些内存管理的知识点. 1. 虚拟地址.物理地址.逻辑地址.线性地址 虚拟地址又叫线性地址.linux没有采用分段机制,所以逻辑地址和虚拟地址(线性地址)(在用户态,内核态逻 ...
- Linux内存管理原理【转】
转自:http://www.cnblogs.com/zhaoyl/p/3695517.html 本文以32位机器为准,串讲一些内存管理的知识点. 1. 虚拟地址.物理地址.逻辑地址.线性地址 虚拟地址 ...
- Windows内存管理和linux内存管理
windows内存管理 windows 内存管理方式主要分为:页式管理,段式管理,段页式管理. 页式管理的基本原理是将各进程的虚拟空间划分为若干个长度相等的页:页式管理把内存空间按照页的大小划分成片或 ...
- Linux内存管理专题
Linux的内存管理涉及到的内容非常庞杂,而且与内核的方方面面耦合在一起,想要理解透彻非常困难. 在开始学习之前进行了一些准备工作<如何展开Linux Memory Management学习?& ...
- 伙伴系统之伙伴系统概述--Linux内存管理(十五)
在内核初始化完成之后, 内存管理的责任就由伙伴系统来承担. 伙伴系统基于一种相对简单然而令人吃惊的强大算法. Linux内核使用二进制伙伴算法来管理和分配物理内存页面, 该算法由Knowlton设计, ...
随机推荐
- linux上war包方式安装Jenkins
我的安装环境:jdk1.8, linux系统为: [root@ipha-dev71-1 nmon]# cat /etc/redhat-release # Linux查看版本当前操作系统发行版信息 Ce ...
- 使用JRebel插件实现SpringBoot应用代码热加载
前言 在实际的开发过程中,我们经常修改代码之后,手动的重启项目,查看修改效果.那么有没有一种方式能够快速的.自动的帮我们将修改代码自动更新,避免手动重启,从而提高开发效率呢?是有的,在我之前的文章里面 ...
- 基于docker的mysql8的主从复制
基于docker的mysql8的主从复制 创建mysql的docker镜像 构建docker镜像,其中数据卷配置内容在下面,结构目录如下 version: '3.7' services: db: # ...
- $.ajax.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <link rel= ...
- DirectX9:第一章 初始化Direct3D
一.Direct3D概述 Direct3D是一套底层图形API,可以被视为应用程序与图形设备交互的中介. 应用程序.Direct3D以及硬件之间的交互关系: 在Direct3D和图形设备之间有一个环节 ...
- Flask:项目的准备工作
1.创建虚拟环境 (1)打开Windows命令窗口,输入命令(先确保电脑上安装了Anaconda): conda create -n FlaskPath python=3.5.2 FlaskPath为 ...
- Ajax自我总结
一念起.万水千山皆有情. 一念灭.沧海桑田已无心. ------ 随记 本文主要针对ajax原理介绍,很少涉及实例,主要用于对知识的梳理总结,方便以后学习和查询... Ajax 一.Ajax是 ...
- linux(CentOS release 6.5)环境搭建svn
正文之前,说几句关于svn和git的闲话. 之前用的版本控制工具主要都是svn,随着时间的推移,git以其强大灵活的分支管理功能受到大众喜爱.尤其是多人同时开发时同一项目,且不同部分功能时,git的分 ...
- npm 学习笔记
一.介绍 1.是什么 npm 全称是 Node Package Manager,即 Node 包管理工具. 但是发展到后来,并不仅是适用于 node.js 的包. 所以现在看 node_modules ...
- [考试反思]1026csp-s模拟测试88:发展
不用你们说,我自己来:我颓闪存我没脸. 昨天的想法, 今天的回答. 生存, 发展. 总分榜应该稍有回升,但是和上面的差距肯定还是很大. 继续. 为昨天的谬误,承担代价. T2和T3都值得张记性. T2 ...