Linux内核学习笔记 -18 物理内存分配与回收机制 - 下

从用户进程发出内存分配请求开始,到内核最终分配到物理内存,这中间内核要做大量的工作。

上一讲中,概要的介绍了vmalloc和kmalloc,最终都要调用伙伴算法,通过get_free_page函数内核获得物理内存

目前有两种计算机,分别以不同的方法来管理物理内存。

NUMA计算机:多处理器计算机,每个CPU都有自己的本地内存,这种划分每个CPU都能以较快的速率访问本地内存,各个CPU之间通过总线连接起来,这样也可访问其它CPU的本地内存,只不过速度略慢而已

UMA计算机:将可用的内存以连续的方式组织起来。

为了兼容UMA模型,内核引入了内存节点,每个节点都关联了一个CPU,各个节点又被划分为几个内存区,每个内存区又包含了若干个页框。

 

UMA计算机中,每个CPU的物理内存我们称为一个内存节点,

系统内的所有节点就形成了一个双链表。UMA模型的物理内存只对应一个节点,即整个物理内存形成一个节点,因此上述的节点链表中,也只有一个元素。

各个节点划分为若干个区,即物理内存的进一步细分,通过下面几个宏来标记物理内存的不同区。zone_normal,zone_highmen,zone_dma

64位或32位系统对内存区管理上有什么差别呢?最大的区别是64位系统不再有高端内存的概念,可用支持大于4GB的内存寻址,而且zone_normal的空间将扩展到64GB或128GB。而且64位系统上的映射也变得更简单了

这三种数据结构之间有什么样的关系?首先,物理内存被划分为内存的节点,用pg_data_t表示。每个节点实际上是关联了一个CPU,对于NUMA结构而言,因为有多个节点,因此各个节点之间就形成了一个链表。每个节点有被划分为几个内存管理区(zones),在一个内存管理区中,则是一个个的页框。页框是内存关联的基本单位,它可以存放任何类型的数据

下面对伙伴算法进行介绍

物理内存如何进行分配?linux内核主要采用了伙伴算法。为什么叫伙伴算法?实际上就是大小相同,物理地址上连续的两个页框就被称为伙伴。

比如,第一个链表里面是2的0次方个页面,第二个是2的1次方个页面,分别是1个页面,2个页面,每个链表中页块的大小是不一样的。与伙伴算法有关的数据结构是什么?

每个物理页框对应一个struct page实例,这是页框的数据结构

每个内存区关联了一个sturct zone实例,该结构中使用free_area数组对空闲页框进行描述

如果申请大小为8的页块,分配阶就为3,但却在页块大小为32的链表中找到了空闲块,则先将这32个页面对半等分,前一半作为分配使用,另一半作为新元素分配给下一级大小为16的链表中。继续将前一半大小为16的页块等分,一般分配,另一半插入大小为8的链表中

页框的分配到底是怎么样来实现的?主要介绍两个函数。

第一个函数__rmqueue_smallest()是在指定的内存区上,从所请求的分配阶对应的链表开始查找所需大小的空闲块,,如果不成功则从高一阶的链表上继续查找

第二个函数是expand(),分裂函数,如果所得到的内存块大于所请求的内存卡,则按照伙伴算法的分配原则,将大的页框块分裂成小的页框块

上面为第一个函数的实现的源码,

static inline
struct page *__rmqueue_smallest(struct zone *zone,unsigned int order,int migratetype)
{
	unsigned int current_order;
	struct free_area * area;
	struct page *page;
	
	/*Find a page of the appropriate size in the preferred list*/
//从当前指定的分配阶到最高分配阶依次先进行遍历//
	for(current_order = order; current_order < MAX_ORDER;++current_order){
	area = &(zone->free_area[current_order]);
	if(list_empty(&area->free_list[migratetype]))  //在每次遍历分配阶的链表中,
//根据参数的迁移类型选择正确的迁移队列。根据这些限定条件,当选定一个页块链表后,
//只要该链表不为空,就说明可以分配该阶对应的页块。
//一旦选定在当前遍历的分配阶链表上分配页框,那么就通过list_entry将该页块从链表中删除。
		continue;
	
	page = list_entry(area->free_list[migratetype].next,struct page,lru);
	list_del(&page->lru);
	rmv_page_order(page);//以上过程,通过rmv_page_order来完成
	area->nr_free--;     //更新页块链表nr_free的值
	expand(zone,page,order,current_order,area,migratetype);
	return page;
	}
	return NULL;
}

以下为分裂函数expand()的源码 

static inline void expand(struct zone *zone, struct page *page, int low, int high, struct free_area *area, int migratetype)
{
	unsigned long size = 1 << high;
	while(high > low){
		area --;
		high --;
		size >>=1;
		VM_BUG_ON(bad_range(zone,&page[size]));
		list_add(&page[size].lru,&area->free_list[migratetype]);
		area->nr_free++;
		set_page_order(&page[size],high);
	}
}

分裂算法的实现完全遵照伙伴算法的分裂原理,上面有两个分配阶,一个是申请页框时指定的低阶low,一个是在上级函数中遍历时所选定的高阶,该函数从高阶分配阶开始,递减向低阶遍历,即从较大的页块开始依次分裂。

什么是物理内存分配器?基于伙伴算法,每CPU高速缓存和slab高速缓存形成了两种内存分配器

分区页块分配器。分区页块管理区分为两大部分,前端的管理区分配器和伙伴系统。

管理区分配器负责搜索一个能满足请求页框大小的管理区,在每个管理区中具体的页框分配工作是由伙伴系统负责的。

为了达到更好的系统性能,单个页框的申请工作直接通过每CPU页框高速缓存来完成。

如下为页框分配函数的关系图。

内核中有6个稍有差别的函数和宏来请求页框,图中四个绿色和两个蓝色就表示这六个函数。

它们将核心的分配函数,即最底层的__alloc_pages_nodemask()进行封装,从而形成满足不同分配需求的分配函数。

从用户态到内核态的内存分配过程

当用户程序通过系统调用申请内存时,首先陷入内核,建立虚拟内存空间的映射,获得一块虚拟内存区VMA,当进程对这块虚存区进行访问时,如果物理内存尚未分配,此时聚会发生一个缺页异常,通过get_free_page申请一个或多个物理页面,并将此物理内存和虚拟内存的映射关系写入页表。

http://edsionte.com/techblog/

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值