伙伴系统的相关结构
在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_TYPE为5,加入迁移类型来控制同一阶的页,主要是为了避免碎片
unsigned long nr_free; //这里的nr_free表示空闲页块的计数,例如:当前free_area处在2阶,在该阶上有4个struct page 实例,则nr_free为2
};
伙伴系统的初始化(在这里只讨论跟伙伴系统相关的操作)
特定于系统的初始化:将各节点的页帧分配情况保存在全局变量early_node_map,各内存域的最大边界保存在全局max_zone_pfn。
free_area_init_nodes: 计算出各内存域的页帧区间,并累积内存域的页帧数得到节点的页帧数size,通过bootmem开辟size个struct 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个页
-
首先计算将页分配出去以后是否满足水印要求
-
从伙伴系统中分配相应的页
-
如果分配不成功,会根据gfp_mask中相应的值来判断,是否启动负责换出页的kswapd进程,或者降低水印,或者从保留区分配页等等策略
-
释放函数:void__free_pages(struct page *page, unsigned int order)
-
判断order是否为0,如果为0,调用free_hot_page将页放到cpu的高速缓存后,返回
-
order不为0,调用__free_page_ok(对page进行检查,然后关中断),最后到__free_one_page
-
__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;
};