物理内存的管理

在内核初始化完成后,内存管理的责任由伙伴系统承担。伙伴系统为内核分配连续内存页,它是一种古老而历经检验的技术。

系统中的空闲内存块总是两两分组,每组中的两个内存块大小一样,都是2n个页,称作伙伴(buddy,我一直觉得叫做couple更适合)。伙伴的分配时彼此独立的。但如果两个伙伴都是空闲的,内核就会将其合并为一个更大的内存块。

伙伴系统的数据结构

系统中每个内存域都对应一个struct zone实例,其中保存了用于管理伙伴数据的主要数组。

struct zone{

         /*不同长度的空闲内存块*/

         struct free_area      free_area[MAX_ORDER];

};

伙伴系统的空闲内存块是按照阶来管理的。阶是伙伴系统的一个非常重要的术语。它描述了内存分配的数量单位。内存块的长度总是为2order,它是free_area[order]所维护的链表中的元素。其中order的范围从0到MAX_ORDER。

free_area[]数组中各个元素是一个链表,连接对应阶的空闲内存块。第0个链表管理的内存区为单页(20 = 1),第1个链表管理的内存区为两页(21 = 2),第2个链表管理的内存区为四页(22= 4),以此类推,如下图所示。


伙伴的分配

下图示范了伙伴系统分配内存的过程。假设系统初始时有一个大小为16页(24 = 16)的空闲内存块,它是由各有8页的一对伙伴组成,挂在free_area[4]的链表上。


在第一个case中,系统需要一个8页的连续内存,16页的内存块被拆分成两个伙伴,前面8页被放置到对应大小内存块的列表中,剩余的8页内存块用于满足应用程序的请求。

下一个请求时系统需要2页的连续内存,则8页的内存块分裂成两个伙伴每个包含4个页帧,前面一块放回伙伴列表,另一块再分裂成两个伙伴,每块包含2页,前面一块放回列表,剩余的分配给应用程序。

伙伴的合并

在应用程序释放内存时,内核可以检查处于空闲状态的两个内存区,判断是否可以创建一组伙伴,并合并为一个更大的内存块放回到伙伴列表中。合并的条件是:

(1)    两个内存块均空闲,且连续,且大小相等。

(2)    第一块的第一个页框的物理地址是合并后块大小的整数倍

第二个条件确保了:如果A和B可以合并,那么AB必然是由一个大块分裂来的,这样能有效的避免外部碎片。这里应该举个例子的。。。

依据可移动组织页避免碎片

系统启动并长期运行之后,物理内存会产生许多碎片,如下图左边的情况。而右边的情形中,空闲页和使用页的数目与左图相同,但所有的空闲页都是连续的,这是我们希望看到的情况。我们来看看内核是怎么避免碎片的。内核将以分配页划分为3种不同的类型。

l  不可移动页:在内存中有固定位置,不能移动到其他地方。核心内核大多数分配的属于这种类型。

l  可回收页:不能直接移动,但可以删除,其内容可以从某些源重新生成。例如映射自文件的数据。

l  可移动页:可以随意的移动。属于用户空间应用程序的页就属于该类型。它们是通过页表映射的。如果移动到新的位置,页表项可以相应的更新,这对应用程序是透明的。

内核使用的反碎片技术,就是基于将具有相同可移动性的页分组的思想。实现起来的话就是讲上文提到free_area类型的定义如下:

struct free_area {

struct list_head free_list[MIGRATE_TYPES];

unsigned long nr_free;

};

即将同一阶的内存块按照页的可移动性分类,每一类连接在一个链表上。而nr_free统计了所有链表上的空闲页的数目。内核还将页的不同的迁移类型定义成以下宏:

#define MIGRATE_UNMOVABLE 0

#define MIGRATE_RECLAIMABLE 1

#define MIGRATE_MOVABLE 2

#define MIGRATE_RESERVE 3

#define MIGRATE_ISOLATE 4

#define MIGRATE_TYPES 5

其中MIGRATE_RESERVE是保留类型,用于紧急情况分配内存;MIGRATE_ISOLATE类型用于跨越NUMA节点移动物理内存页,我们不详细讨论。

有一个问题:当指定的迁移类型的空闲列表耗尽时,会怎样?内核的解决方法是提供一个备用列表。其中规定了当指定列表中无法满足分配请求时,接下来应使用哪种迁移类型:

 

static intfallbacks[MIGRATE_TYPES][MIGRATE_TYPES-1] = {

[MIGRATE_UNMOVABLE] = { MIGRATE_RECLAIMABLE,MIGRATE_MOVABLE, MIGRATE_RESERVE },

[MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE,MIGRATE_MOVABLE, MIGRATE_RESERVE },

[MIGRATE_MOVABLE] = { MIGRATE_RECLAIMABLE,MIGRATE_UNMOVABLE, MIGRATE_RESERVE },

[MIGRATE_RESERVE] = { MIGRATE_RESERVE, MIGRATE_RESERVE,MIGRATE_RESERVE },/* Never used */

};

这样,内核就可以按需分配指定迁移类型的内存了。那么内核如何知道给定的分配内存请求属于哪种迁移类型呢?这是在内存分配过程中调用函数allocflags_to_migratetype通过分配掩码指定的。于是我们知道了,一个分配内存的请求说:“我需要可移动类型的8个页。”内核就从23的列表中找到可移动类型的子列表分配给它。

最后,需要说明两点:

1、  系统初始化时,所有的物理页都标记为可移动的。由上面的备用列表可知,这时分配不可移动等类型的内存时,内核会从可移动列表中分配并转换成不可移动类型。实际上,在启动期间分配可移动内存区的情况很少,那么分配器就有很高的概率获得大块的内存转换为不可移动类型,因此不会向可移动内存中引入碎片。

2、  如果各迁移类型的链表中没有一块较大的连续内存,那么页面迁移不会提供内核好处,因为没有足够迁移空间,这时内核会关闭内存迁移这个特性。如果停用了内存迁移,所有的页都是不可移动的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值