本文kernel代码分析基于以下
1.linux-4.14.159
2.64bit代码处理逻辑
伙伴系统介绍
linux把物理内存通常分为4K大小用page结构体进行表示,叫做页帧/页框,如果频繁申请或释放不同大小的页帧,长时间后会造成剩余很多物理地址不连续的页帧,称为碎片,这样要是再申请一个较大的地址连续页帧的时候,则因为碎片问题不能满足申请。
为了避免这种碎片(其实伙伴只能避免外部碎片)问题,linux系统引入了一种伙伴系统算法对这些页帧进行管理。
当linux系统启动的过程中此时伙伴系统还未初始化完成,此时仍需要内存,linux会通过之前内存组织架构介绍的bootmem_data来管理,当linux系统启动成功后,会把剩余的那些空闲page释放到伙伴系统中,这也可以看作是伙伴系统最开始的初始化。此节我们不关注初始化,主要介绍伙伴系统算法以及申请和释放的过程。
伙伴系统: 把内存空闲页框共分为11个阶(order),每个阶的内存【块】大小分别为2^n(0,1,…,10)个连续的页框,即每个阶内存块的大小为1,2,4,8,…,1024个连续page,可以看出最大一次可以申请1024*4k=4M连续内存空间。
下面重点看下伙伴系统的代码数据表示,以及伙伴系统申请和释放的核心思想,因这块内核代码复杂冗长,如果理解这些概念即思想则能更快好理解源码的实现,下一节再看相关的具体代码。
伙伴系统结构体表示
之前内存组织架构章节了解了通过zone来管理page,那么对于page如何的进行组织管理这其实就是伙伴系统干的事,自然而然伙伴系统的结构体表示就应该在zone的定义中,可以说伙伴系统其实是zone的page对象的管家。
include\linux\mmzone.h
struct zone {
/* free areas of different sizes */
struct free_area free_area[MAX_ORDER]; //@1
}
struct free_area {
struct list_head free_list[MIGRATE_TYPES];//@2
unsigned long nr_free; //@3
};
@1. 数组大小MAX_ORDER=11,对象取值free_area[index] (index=0…10), 每个数组表示大小为2^index个连续页的内存块组成的链表,这些内存块内存大小都相同。
例:free_area[3],表示大小为8个连续页的内存块组成的链表。
@2. MIGRATE_TYPES :页的迁移类型,参考后面讲解。
free_area[index].free_list[type] :每个数组元素表示迁移类型为type且大小为2^index个连续页的内存块组成的链表。
例: free_area[3].free_list[0],表示迁移类型为MIGRATE_UNMOVABLE的内存块组成的链表,其中内存块大小为8个连续页。
注意:这些内存块有一个特点 ,即内存块里所有页的地址都是连续的,因此这些内存块链表连接时只需把内存块的第一个页关联即可,这个使用的是页page结构体的lru成员(类型为list_head)
@3. nr_free表示这种内存块(包括所有迁移类型)的数量 。
例:free_area[3].nr_free ,表示内存块的数量,其中内存块的大小为2^3(8)个连续页,内存块的迁移类型可以是任一迁移类型
页的迁移类型
在上面伙伴系统结构中,我们看到了迁移类型,页的迁移类型是干什么用的呢?其实就是为了解决内存碎片的。
简单描述它如何解决碎片的,假设下面是一段内存,在伙伴系统中【0】-【6】是空闲的,【7】非空闲。如果【7】后续伙伴系统可以回收,则这一段内存可以组成8个页的内存块,如果7的类型为特殊用处一直不被释放到伙伴,则这块内存永远不会作为连续8个页的内存块进行分配。 如果我们把系统中像【7】这种类型的页帧同一管理(统一使用一种迁移类型),那么就可以避免上面这种碎片case。
【0】【1】【2】【3】【4】【5】【6】【7】
enum migratetype {
MIGRATE_UNMOVABLE, //@1
MIGRATE_MOVABLE, //@2
MIGRATE_RECLAIMABLE, //@3
MIGRATE_PCPTYPES, //@4 /* the number of types on the pcp lists */
MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,//@5