程序员福音!linux内核也可以看动画学习!

图片

本文动画主题是围绕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描述符。

图片

  • list:链表节点。

  • colouroff:slab描述符中第一个slab对象的偏移。

  • s_mem:指向slab对象所在的内存空间。

  • inuse:当前正在使用的slab对象的个数。

  • free:slab描述符中下一个空闲对象的下标。

源码分析

slab机制相关接口
  • kmem_cache_init

    该函数在linux系统初始化的时候自动被调用

  • kmem_cache_create

    负责创建一个高速缓存描述符

  • kmem_cache_alloc

    从指定的高速缓存描述符中分配一个slab对象

  • kmem_cache_size

    查询指定高数缓存描述的slab对象大小

  • kmem_cache_free

    释放一个slab对象到高速缓存描述符中

  • kmem_cache_shrink

    收缩高速缓存描述符中slab对象的数量

  • __kmalloc

    从通用高速缓存描述符中分配一个空闲的slab对象

  • kfree

    释放一个slab对象到通用高速缓存描述符中

  • kmem_cache_destroy

    销毁一个高速缓存描述符,注意:只有当所有slab对象处于空闲状态,该高速缓存描述符才能成功销毁

全局变量

slab机制作为一个比较完善且复杂的子模块,自然少不了一些预设的全局变量,下面这些全局变量将起到高瞻远瞩,统领全局的作用。

  • cache_cache:

图片还记得上面说由两大类缓存池吗?这个cache_cache就是专用缓存池。它是给slab机制本身的kmem_cache_t高速缓存描述符使用的。看它的objsize字段就明白了,之所以要静态定义,是因为此时slab机制还没进行初始化。

  • cache_chain:

图片本质上就是一个链表节点,用来链接所有的高速缓存描述符

  • cache_chain_sem:

图片

一个信号量,用来控制对cache_chain的访问

  • malloc_sizes:

图片该数组元素的设置,是巧妙地通过头文件预处理的方式来设置。我第一次见这种c语言的用法,还是在一个著名的tcp协议栈-lwip里面见到,有兴趣了解可以查看这篇博文:https://blog.csdn.net/zyboy2000/article/details/4305816。这处是设置不同通用缓存池的大小,以2^n为单位。

  • cache_names:

图片同上,设置通用缓存池的名称

slab机制初始化

slab机制要对外提供通用缓存池,所以在初始化机制里面,除了初始化全局变量的内容以外,主要就是初始化通用缓存池相关的高速缓存描述符。

初始化接口为kmem_cache_init,这个函数在操作系统初始化的时候,自动被调用。

kmem_cache_init

它的源码主要可以分为以下几个部分:

  1. 初始化结构相关的成员变量图片

  2. 循环创建通用高速缓存描述符(以2的n次方为单位)图片

  3. 遍历当前所有的高速缓存描述符,初始化相应的本地缓存池

图片

整个函数的执行过程,在linux系统里面的执行动画如下:

图片

创建高速缓存描述符

kmem_cache_create函数用来创建高速缓存描述符,一个高速缓存描述符管理负责管理多个slab描述符,而一个slab描述符,就是slab对象最小的管理单位。

我们先来了解下kmem_cache_create函数的原形:

图片

参数含义如下:

  • name : 设置高速缓存描述符的名字

  • size : slab对象(高速缓存对象)的大小

  • align : 内存对齐偏移

  • flags : 设置slab对像的一些特性

  • ctor、dtor: 构造函数和析构函数,通常不用管

源码分析:

它做的事情主要分为以外几个部分:

  1. 校验各个参数的合法值

图片

  1. 计算slab描述符所需要使用的页面数、slab对象大小、着色区大小等

图片

3初始化slab_full/slabs_partial/slabs_free链表图片

  1. 把创建的高速缓存描述符链接到cache_chain链表中

图片

执行动画如下:

图片

分配一个slab对象

图片

kmem_cache_alloc负责从指定高速缓存描述符中分配一个slab对象

我们先来了解下kmem_cache_alloc函数的原形:

图片

  • cachep: 指定高速缓存描述符

  • flags: 这个标志将用于向伙伴系统申请内存,创建slab对象

它做的事情主要分为以外几个部分:

  1. 先尝试从本地cpu缓存池获取slab对象

图片

  1. 如果本地cpu缓存池没有slab对象,从共享缓存池尝试获取

图片

  1. 如果共享缓存池没有slab对象,尝试从slab_partial、slab_free链表中获取

图片

  1. 如果链表也没有slab对象,则向伙伴系统申请内存,并生成slab描述符和slab对象,挂在到链表上面去,通过goto语句,重走一遍全部流程

图片

执行动画如下:

图片

图片

努力前进本身就是一件孤独的事,他人再分享再加油,要跑到终点也只能倚仗自己的体力和意志,就像生活本来的样子。只是有些弯路,你不必再走,有些苦头,你不必再吃。

个体的力量始终是渺小的,一个人30k不难,一群人30k很酷!

希望在您迈向30k的路上,我能始终陪伴。如对文章内容有困惑,或者有嵌入式行业提薪、转型等需求,可以添加笔者微信,共同探讨!每天晚上九点到11点准时回复信息。添加好友请备注"嵌入式+行业+姓名",期待与您的相识!

图片

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值