3.5 伙伴系统------《深入Linux内核架构》笔记

伙伴系统的相关结构

bootmem完成系统启动阶段的内存管理后,bootmem将会把空闲的内存释放到伙伴系统由其管理,伙伴系统以其简单高效而著称。

struct zone中的free_area成员中存放的是该内存域所包含的所有的空闲页:

struct zone{

…...

struct free_area free_area[MAX_ORDER]; //MAX_ORDER默认为11,所以一次性能从伙伴系统分配的页便是1页,2页,4.....2048

…...

}


struct free_area{

struct list_head free_list[MIGRATE_TYPES];// MIGRATE_TYPE5,加入迁移类型来控制同一阶的页,主要是为了避免碎片

unsigned long nr_free; //这里的nr_free表示空闲页块的计数,例如:当前free_area处在2阶,在该阶上有4struct page 实例,则nr_free2

};


伙伴系统的初始化(在这里只讨论跟伙伴系统相关的操作)

特定于系统的初始化:将各节点的页帧分配情况保存在全局变量early_node_map,各内存域的最大边界保存在全局max_zone_pfn

free_area_init_nodes: 计算出各内存域的页帧区间,并累积内存域的页帧数得到节点的页帧数size,通过bootmem开辟sizestruct page 实例,将首指针赋到struct pglist_data实例的node_mem_map成员,如果是单节点,首指针也将被赋到mem_map。调用zone_init_free_lists方法初始化free_area,这里只是初始化链表,并将nr_free设置为零。

启动伙伴系统:停用bootmem分配器是系统通过调用__free_pages_bootmem(struct page *page, unsigned int order)函数将空闲的页交由伙伴系统管理来完成。__free_pages_bootmem函数底层调用_free_pages来实现。释放的页将会放到free_area里。这里释放的时候需要参数order,至于系统将空闲的页怎样分配到不同的阶,我并没有深究哈。


伙伴系统的功能函数

外层函数不再介绍,这里只简单介绍最底层的实现函数。

分配函数:static inlines truct page *alloc_pages_node(intnid, gfp_t gfp_mask,unsigned int order)

nid:从那个节点分配,gfp_mask:分配掩码,order:分配2^order个页

    1. 首先计算将页分配出去以后是否满足水印要求

    2. 从伙伴系统中分配相应的页

    3. 如果分配不成功,会根据gfp_mask中相应的值来判断,是否启动负责换出页的kswapd进程,或者降低水印,或者从保留区分配页等等策略

释放函数:void__free_pages(struct page *page, unsigned int order)

      1. 判断order是否为0,如果为0,调用free_hot_page将页放到cpu的高速缓存后,返回

      2. order不为0,调用__free_page_ok(page进行检查,然后关中断),最后到__free_one_page

      3. __free_one_page通过函数__page_find_buddy找到相应的伙伴页,然后合并成新的页块,最后添加到阶为order+1的链表中

附录 page_address

用户调用alloc_pages时返回的为struct page 类型的指针,而用户想要的确是指向所分配内存空间的指针。在x86体系中struct page结构是没有成员virtual来作虚拟内存地址,所以需要使用函数void*page_address(struct page *page)实现.另外vir_to_page()只是个逆向的过程这里不再介绍

// get the mapped virtual address of a page
// 得到相应页的虚拟地址
void *page_address(struct page *page)
{
	unsigned long flags;
	void *ret;
	struct page_address_slot *pas;

	if (!PageHighMem(page)) // 直接判断该页所属的内存域是否为高端
		return lowmem_page_address(page);

	pas = page_slot(page); // 得到散列表
	ret = NULL;
	spin_lock_irqsave(&pas->lock, flags); // 加锁
	if (!list_empty(&pas->lh)) {
		struct page_address_map *pam;

		// 遍历链表,找到相应的项,并返回virtual(对于高端内存持久映射的建立,请参阅其它章节)
		list_for_each_entry(pam, &pas->lh, list) {
			if (pam->page == page) {
				ret = pam->virtual;
				goto done;
			}
		}
	}
done:
	spin_unlock_irqrestore(&pas->lock, flags);
	return ret;
}

// from page-flags.h
#define PageHighMem(__p) is_highmem(page_zone(__p)) //判断该内存域是否为高端内存域

// from mm.h
// 取得该page所属的内存域
static inline struct zone *page_zone(struct page *page)
{
	return &NODE_DATA(page_to_nid(page))->node_zones[page_zonenum(page)];
}

// 通过page中的标志位来取得相应的内存域id
static inline enum zone_type page_zonenum(struct page *page)
{
	return (page->flags >> ZONES_PGSHIFT) & ZONES_MASK;
}
// 通过page中的标志位来取得相应的节点id
static inline int page_to_nid(struct page *page)
{
	return (page->flags >> NODES_PGSHIFT) & NODES_MASK;
}

static __always_inline void *lowmem_page_address(struct page *page)
{
	// 页编号左移12位便得到页所管理区间的地址
	return __va(page_to_pfn(page) << PAGE_SHIFT);
}


// from memory_model.h
// mem_map 为系统中page数组的首指针,ARCH_PFN_OFFSET应该是系统的页编号从该处属于内存域
// 通过页编号得到相应的page结构
#define __pfn_to_page(pfn)	(mem_map + ((pfn) – ARCH_PFN_OFFSET))
// 通过page结构得到相应的页编号
#define __page_to_pfn(page)	((unsigned long)((page) – mem_map)+ARCH_PFN_OFFSET)

// from /arch/x86/include/asm/page.h
// 将实地址转换为虚拟地址,该转换过程只对应线性映射,及normal内存区域的映射。
// PAGE_OFFSET为OXC000000
#define __va(x)			((void *)((unsigned long)(x)+PAGE_OFFSET))
// 虚地址转实地址
#define __pa(x)			((void *)((unsigned long)(x)-PAGE_OFFSET))


// 高端内存的持久映射建立所需的数据结构,因为高端内存的映射方式不是线性映射,所以需要一个将   // page和virtual地址关联起来的结构
struct page_address_map {
	struct page *page;
	void *virtual;
	struct list_head list;
};



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值