本文动画主题是围绕linux内存管理中的slab机制展开
先看这个动画,如果您觉得还不错,希望您能多花十几分钟读完全文。
内核patch
linux内核经过几十年的迭代发展,内核的已经越发复杂了,使得很多人学习起来很痛苦。
如何减少学习内核的痛苦?
一:追本溯源。所有的复杂事物,不是天生就复杂的,必然经过多次版本的迭代,慢慢变复杂的,那么找到其简单版本开始研究,就非常有必要了。
二: 代码设计还原。在研究代码时,一定要边读代码边梳理数据结构与算法,等到弄清楚每个结构体的含义和功能,掌握算法的基本原理,则在脑海中已经还原了作者的设计意图,对每一处的代码细节都无比熟悉。
slab机制是什么?
内存管理的一种算法,其核心思想是以对象的观点来管理内存
slab机制有什么用?
linux内核之所以存在slab机制,是因为它需要管理cpu处理器上的内存。但是内核作为日理万机的boss,自然没法时时为内存管理操心。于是便设置了大小两类管家帮助它进行内存管理。
其中,slab机制就属于小管家,本质上就是专门管理内存的。更具体来说,当你需要在linux内核里面申请4k(一个页大小)以下的内存使用的时候,就可以向slab机制这个小管家要内存。
大家要谨记,slab机制的旨就是避免频繁地分配和释放页,它可以充当linux的各种数据结构的缓存池,这类缓存池我们可以称之为专用缓存池。当然,它还可以作为另外一种缓存池,其特点是以2的n次方为单位,我们称之为通用缓存池。其中,各种数据结构和2的n次方单位,我们抽象地泛称之为slab对象。
slab机制原理
slab机制在内核中经过了多个版本的迭代,我们从最原始版本开始讲解。slab机制在内核mm/slab.c目录下实现,该文件最早由linus在Linux-2.6.12-rc2版本提交,时间为2005-04-16,commitid为1da177e4c3f4。
我们先来梳理一下各个主要的数据结构的含义及其关系,切记不要死记硬背,有个基本印象即可,主要的还是通过不断阅读代码来逐渐加深印象。
相关结构体
slab机制涉及到的主要几个结构体,关系如下图所示:
从图中可以观察出来,一个高速缓存描述符管理负责管理多个slab描述符,而一个slab描述符,就是slab对象最小的管理单位。
图中还有两种slab对象缓存池,这是因为slab描述符在分配slab对象的时候,有可能会涉及到页的分配和链表的移动,为了提供slab对象分配效率,就为每个cpu设置了一个slab对象缓存池,将来在分配slab对象的时候优先从本地cpu缓存池中进行分配。而共享对象缓存池则是为了解决不同cpu上slab对象分配不均的情况,当某个cpu缓存池上,slab对象被耗光的时候,优先从共享缓存池进行分配,而slab对象超过限制数量时,优先释放到共享缓存池中。
高速缓存描述符
kmem_cache_s or kmem_cache_t这个结构体成员变量很多,但是大家不需要担心,在下面的源码分析里面,我们会逐渐弄明白每个变量的含义。
无论是哪种缓存池,它都是通过kmem_cache_s结构体来进行管理,该结构体把它叫做高速缓存描述符。
-
array:struct array_cache的数据结构,每个cpu一个,表示本地cpu的对象缓存池。 -
batchcount:表示当前cpu的本地缓冲池对象为空时,一次性补充的数量 -
limit: 本地高速缓存中空闲对象的最大数目,便于内核回收和销毁slab。 -
lists: 这个字段包含三个链表头,其中链表头对应slab描述符所处的三种状态之一:部分空闲状态、完全空闲状态和满状态。 -
objsize: 缓冲池中对象的大小。 -
flags: 描述缓冲池属性的一组标志。 -
num:一个slab中的对象个数。 -
free_limit: 该高速缓存描述符中所有空闲对象的数量上限,大于此数量的空闲对象将被系统回收内存。 -
spinlock:自旋锁。 -
gfporder:一个slab描述符中占用2^gpforder个页面。 -
gfpflags:分配页框时传递给伙伴系统函数的一组标志 -
colour:可供slab对象数进行偏移处理(此动作称为着色)的数量 -
colour_off:slab对象为了尽可能避免处在在同一cache line,需要做偏移,此值为偏移单位 -
colour_next:slab对象数进行偏移处理时,每个slab需要记录其偏移的编号,便于计算实际偏移值 -
slabp_cache:指针指向slab描述符的普通slab高速缓存(slab描述符可在salb对象内,也可在slab对象之外) -
slab_size:slab描述符管理slab对象所需的大小 -
dflags:描述slab缓存动态属性的一组标志 -
ctor:构造函数 -
dtor:析构函数 -
name: slab描述符的名称 -
next:slab描述符链表节点
这里要把list字段也给大家介绍一下:
-
slabs_partial:包含空闲和非空闲的slab描述符双向循环链表。 -
slabs_full:不包含空闲对象的slab描述符双向循环链表。 -
slabs_free:只包含空闲对象的slab描述符双向循环链表。 -
free_objects:高速缓存中空闲对象的个数。 -
free_touched:空闲链表的使用标志,避免刚分配的slab对象过早被回收 -
next_reap:内核定期回收内存,此值为定时时间。 -
array_cache:所有cpu共享的一个本地高速缓存池。
上面两个结构体都设计到了array_cache缓存池,这里也介绍下这个结构体:
-
avail:指向本地cpu高速缓存池中可使用空闲slab对象的个数。 -
limit:本地cpu高速缓存池的大小。 -
batchcount: 本地高速缓存重新填充或腾空时使用的快大小。 -
touched:如果本地高速缓存最近已经被使用过,则该标志置1.
slab描述符
高速缓存描述符中list涉及到了三个双向循环链表,这部分的链表链接的主要就是slab数据结构,该数据结构我们成为slab描述符。
源码分析
slab机制相关接口
全局变量
slab机制作为一个比较完善且复杂的子模块,自然少不了一些预设的全局变量,下面这些全局变量将起到高瞻远瞩,统领全局的作用。
还记得上面说由两大类缓存池吗?这个cache_cache就是专用缓存池。它是给slab机制本身的kmem_cache_t高速缓存描述符使用的。看它的objsize字段就明白了,之所以要静态定义,是因为此时slab机制还没进行初始化。
本质上就是一个链表节点,用来链接所有的高速缓存描述符
一个信号量,用来控制对cache_chain的访问
该数组元素的设置,是巧妙地通过头文件预处理的方式来设置。我第一次见这种c语言的用法,还是在一个著名的tcp协议栈-lwip里面见到,有兴趣了解可以查看这篇博文:https://blog.csdn.net/zyboy2000/article/details/4305816。这处是设置不同通用缓存池的大小,以2^n为单位。
同上,设置通用缓存池的名称
slab机制初始化
slab机制要对外提供通用缓存池,所以在初始化机制里面,除了初始化全局变量的内容以外,主要就是初始化通用缓存池相关的高速缓存描述符。
初始化接口为kmem_cache_init,这个函数在操作系统初始化的时候,自动被调用。
kmem_cache_init
它的源码主要可以分为以下几个部分:
-
初始化结构相关的成员变量 -
循环创建通用高速缓存描述符(以2的n次方为单位) -
遍历当前所有的高速缓存描述符,初始化相应的本地缓存池
整个函数的执行过程,在linux系统里面的执行动画如下:
创建高速缓存描述符
kmem_cache_create函数用来创建高速缓存描述符,一个高速缓存描述符管理负责管理多个slab描述符,而一个slab描述符,就是slab对象最小的管理单位。
我们先来了解下kmem_cache_create函数的原形:
参数含义如下:
源码分析:
它做的事情主要分为以外几个部分:
- 校验各个参数的合法值
- 计算slab描述符所需要使用的页面数、slab对象大小、着色区大小等
3初始化slab_full/slabs_partial/slabs_free链表
- 把创建的高速缓存描述符链接到cache_chain链表中
执行动画如下:
分配一个slab对象
kmem_cache_alloc负责从指定高速缓存描述符中分配一个slab对象
我们先来了解下kmem_cache_alloc函数的原形:
它做的事情主要分为以外几个部分:
- 先尝试从本地cpu缓存池获取slab对象
- 如果本地cpu缓存池没有slab对象,从共享缓存池尝试获取
- 如果共享缓存池没有slab对象,尝试从slab_partial、slab_free链表中获取
- 如果链表也没有slab对象,则向伙伴系统申请内存,并生成slab描述符和slab对象,挂在到链表上面去,通过goto语句,重走一遍全部流程
执行动画如下:
努力前进本身就是一件孤独的事,他人再分享再加油,要跑到终点也只能倚仗自己的体力和意志,就像生活本来的样子。只是有些弯路,你不必再走,有些苦头,你不必再吃。
个体的力量始终是渺小的,一个人30k不难,一群人30k很酷!
希望在您迈向30k的路上,我能始终陪伴。如对文章内容有困惑,或者有嵌入式行业提薪、转型等需求,可以添加笔者微信,共同探讨!每天晚上九点到11点准时回复信息。添加好友请备注"嵌入式+行业+姓名",期待与您的相识!
|