伙伴系统原理及基于Linux 2.6.13源码分析

内核要分配一组连续的页框,必须建立一种健壮、高效的分配策略。为此,必须解决著名的外部碎片(external fragmentation)问题。频繁地请求和释放不同大小的一组连续页框,必然导致在已分配页框的块内分散了许多小块的空闲页框。由此带来的问题是,即使有足够的空闲页框可以满足请求,但要分配一个大块的连续页框就可能无法满足。Linux内核内存管理的一项重要工作就是如何在频繁申请释放内存的情况下,避免碎片的产生。

1、什么是伙伴系统

伙伴系统的宗旨就是用最小的内存块来满足内核的对于内存的请求。在最初,只有一个块,也就是整个内存,假如为1M大小,而允许的最小块为64K,那么当我们申请一块200K大小的内存时,就要先将1M的块分裂成两等分,各为512K,这两分之间的关系就称为伙伴,然后再将第一个512K的内存块分裂成两等分,各位256K,将第一个256K的内存块分配给内存,这样就是一个分配的过程。下面我们结合示意图来了解伙伴系统分配和回收内存块的过程。

1 初始化时,系统拥有1M的连续内存,允许的最小的内存块为64K,图中白色的部分为空闲的内存块,着色的代表分配出去了得内存块。
2 程序A申请一块大小为34K的内存,对应的order为0,即2^0=1个最小内存块
2.1 系统中不存在order 0(64K)的内存块,因此order 4(1M)的内存块分裂成两个order 3的内存块(512K)
2.2 仍然没有order 0的内存块,因此order 3的内存块分裂成两个order 2的内存块(256K)
2.3 仍然没有order 0的内存块,因此order 2的内存块分裂成两个order 1的内存块(128K)
2.4 仍然没有order 0的内存块,因此order 1的内存块分裂成两个order 0的内存块(64K)
2.5 找到了order 0的内存块,将其中的一个分配给程序A,现在伙伴系统的内存为一个order 0的内存块,一个order 1的内存块,一个order 2的内存块以及一个order 3的内存块
3 程序B申请一块大小为66K的内存,对应的order为1,即2^1=2个最小内存块,由于系统中正好存在一个order 1的内存块,所以直接用来分配
4 程序C申请一块大小为35K的内存,对应的order为0,同样由于系统中正好存在一个order 0的内存块,直接用来分配
5 程序D申请一块大小为67K的内存,对应的order为1
5.1 系统不存在order 1的内存块,于是将order 2的内存块分裂成两块order 1的内存块
5.2 找到order 1的内存块,进行分配
6 程序B释放了它申请的内存,即一个order 1的内存块
7 程序D释放了它申请的内存
7.1 一个order 1的内存块回收到内存当中
7.2由于该内存块的伙伴也是空闲的,因此两个order 1合并成一个order 2的内存块
8 程序A释放了它申请的内存,即一个order 0的内存块
9 程序C释放了它申请的内存
9.1 一个order 0的内存块被释放
9.2 两个order 0伙伴块都是空闲的,进行合并,生成一个order 1的内存块m
9.3 两个order 1伙伴块都是空闲的,进行合并,生成一个order 2的内存块
9.4 两个order 2伙伴块都是空闲的,进行合并,生成一个order 3的内存块
9.5 两个order 3伙伴块都是空闲的,进行合并,生成一个order 4的内存块

2.1 内核启动mem_init函数分析

它的调用过程是这样的:mem_init()->free_all_bootmem_node():我们知道这个函数是统计一共释放了多少空闲页。->free_all_bootmem_core()就是在这个函数里面先后多次调用__free_pages()函数。目的就是为了释放每个内存node里面无用的内存孔洞页。我们现在就来好好探讨这个函数。mem_init和free_all_bootmem_core函数请见《mem_init源码分析.c》

如果cold是0的话,表示要把page所指向内存页释放到其所在内存页区中当前处理器的“热区”内存中,如果为1的话,就释放到这页所在页区中当前处理器的“冷区”高速缓存内存中。在这里肯定有不少人不太了解linux在启动后是如何分配和释放内存的。我在这里讲个大概,在以后的文章中会结合代码细讲的。一个核心概念要记住,多页时通过伙伴机制,单页时使用“热冷区”。我们这个函数先涉及后者机制。我们先来说说这个机制。
首先,大家都很了解struct zoon这个结构体,里面涉及了一个与“冷热区”相关的重要成员:struct per_cpu_pageset pageset[NR_CPUS]里面的成员我不打算全讲,NR_CPUS表示这个系统中cpu对应的号。struct per_cpu_pages pcp[2]这个是我要讲的成员我们来看看struct per_cpu_pages 这个结构体的全貌:
struct per_cpu_pages {
int count;
int low;
int high;
int batch;
struct list_head list;
};
这个结构体大家都是用“高速缓存内存”来称呼它的,这样说它也是表示一段内存啦!没错,我们对于一页的内存释放和分配的时候就会先去这两个内存看看。为什么说是两个内存?因为我们每个zone都会有冷区和热区,zone中的struct per_cpu_pageset[]对应着的其中一个cpu的冷区和热区,冷区我们会用pageset[n]->pcp[1],热区我们会用pageset[n]->pcp[0]来表示。我们会想,这个per_cpu_pages是怎么表示一段内存呢?我们会发现这里有个链表list,它是指向一段内存的开头。count就是表示现在有多少内存页。当我们要释放一页内存的时候,我们先看看找到这页所属的内存区zone当前cpu的热区或是冷区。如果此时发现count>high时,我们先把list的前batch数量的内存页释放到这个页区的free_area中(这个和伙伴机制有关,我们等下再讲),然后将要释放的页添加到list中。当我们要分配时,我们先考虑热区或是冷区的count是否大于low,如果不是的话,我们先要从该页区一页一页释放batch数量内存页到list链表中,然后我们才从list中申请一页内存。好了,到这里我们来看看上述过程是如何实现的,但是我们这里只是讨论释放这个过程。

我们现在来看看__free_pages_bulk()函数。

好了,现在已经全部分析了一遍,关于Linux的连续页或是一页内存的释放有了比较深入的理解,我们接下来会说说申请的大概过程。

2.2 内存分配分析

__alloc_pages分析请详见: mem_init源码分析.c

注意:
在linux内核中伙伴系统用来管理物理内存,其分配的单位是页,但是向用户程序一样,内核也需要动态分配内存,而伙伴系统分配的粒度又太大。由于内核无法借助标准的C库,因而需要别的手段来实现内核中动态内存的分配管理,linux采用的是slab分配器。slab分配器不仅可以提供动态内存的管理功能,而且可以作为经常分配并释放的内存的缓存。
kmalloc是基于slab的,所以速度比较快。vmalloc的内部会调用到kmalloc。
Slab最终会调用到__alloc_pages来给分配器申请一些内存。

假设系统的可利用空间容量为2m个字,则系统开始运行时,整个内存区是一个大小为2m的空闲分区。在系统运行过程中,由于不断的划分,可能会形成若干个不连续的空闲分区,将这些空闲分区根据分区的大小进行分类,对于每一类具有相同大小的所有空闲分区,单独设立一个空闲分区双向链表。这样,不同大小的空闲分区形成了k(0≤k≤m)个空闲分区链表。 当需要为进程分配一个长度为n的存储空间时,首先计算一个i值,使2i-1<n≤2i,然后在空闲分区大小为2i的空闲分区链表中查找。若找到,即把该空闲分区分配给进程。否则,表明长度为2i的空闲分区已经耗尽,则在分区大小为2i+1的空闲分区链表中寻找。若存在2i+1的一个空闲分区,则把该空闲分区分为相等的连个分区,这两个分区称为一对伙伴,其中的一个分区用于分配,而把另一个加入分区大小为2i的空闲分区链表中。若大小为2i+1的空闲分区不存在,则需要查找大小为2i+2的空闲分区,若找到则对其进行两次分割:第一次,将其分割为大小为2i+1的两个分区,一个用于分配,一个加入到大小为2i+1空闲分区链表中;第二次,将第一次用于分配的空闲分区分割为2i的两个分区,一个用于分配,一个加入到大小为2i空闲分区链表中。若仍然找不到,则继续查找大小为2i+3的空闲分区,以此类推。由此可见,在最坏的情况下,可能需要对2k的空闲分区进行k次分割才能得到所需分区。 与一次分配可能要进行多次分割一样,一次回收也可能要进行多次合并,如回收大小为2i的空闲分区时,若事先已存在2i的空闲分区时,则应将其与伙伴分区合并为大小为2i+1的空闲分区,若事先已存在2i+1的空闲分区时,又应继续与其伙伴分区合并为大小为2i+2的空闲分区,依此类推。 2.2 伙伴系统的需求 根据伙伴系统算法的思想,我们组对本系统的功能划分为3种: ⑴ 根据伙伴系统算法分配内存 ⑵ 根据伙伴系统算法回收内存 ⑶ 实时查看内存使用的情况
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Big Bear 12

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值