对于伙伴系统相关数据结构的初始化,在以前介绍的linux内核内存初始化时就基本完成,主要包括内存节点中以下几个方面的初始化:
bootm_init() |--->zone_sizes_init():计算每个zone能够管理的页面数,以及起始pfn号,初始化zone的等待队列hash表, 以zone区域 | | 中free_area中的所有free list链表 | |--->free_area_init_node() | | |--->free_area_init_core() | | | |--->init_current_empty_zone:初始化每个zone的free_area域(free list链表) | | | |--->memmap_init:初始化每个zone中所有页对应的struct page结构,并给设置zone中每个pageblock的 | | | | migrate type(默认都为MIGRATE_MOVABLE) build_all_zonelists():1.给每个内存节点的备用内存域进行初始化(node_zonelists) 2.给每个zone区域的每CPU成员pageset进行初始化(用于伙伴系统单页分配)
伙伴系统相关数据初始化完完成后,就需要将memblock管理的内存空间释放到伙伴系统中去.linux内核(arm64架构 sparse内存模型)通过函数free_all_bootmem来实现.
//start_kernel() -> mm_init() -> mem_init() -> free_all_bootmem() // mm/nobootmem.c:由于linux内核系统初始化前期由memblock管理的内存,因此选择nobootmem.c下的函数 void __init mem_init(void) { ...... #ifndef CONFIG_SPARSEMEM_VMEMMAP free_unused_memmap(); #endif /* this will put all unused low memory onto the freelists */ free_all_bootmem(); ...... } /** * free_all_bootmem - release free pages to the buddy allocator * * Returns the number of pages actually released. */ unsigned long __init free_all_bootmem(void) { unsigned long pages; //将所有node中每个zone的managed_pages设置为0,该函数只会被调用一次 reset_all_zones_managed_pages(); //将memblock中的所有空闲内存释放到伙伴系统中,完成伙伴系统的初始化 pages = free_low_memory_core_early(); //totalram_pages统计内核当前伙伴系统中当闲页的总量 totalram_pages += pages; return pages; } //将membloc中的所有空闲内存释放到伙伴系统中,并返回最终伙伴系统管理的空闲页框数 static unsigned long __init free_low_memory_core_early(void) { unsigned long count = 0; phys_addr_t start, end; u64 i; //清除memblcok的hotplug标志 memblock_clear_hotplug(0, -1); //遍历memblock中所有的reserved region for_each_reserved_mem_region(i, &start, &end) //将该区域的每个页对应的struct page结构体flag成员的reserved标志置位,保留该内存 reserve_bootmem_region(start, end); /* * We need to use NUMA_NO_NODE instead of NODE_DATA(0)->node_id * because in some case like Node0 doesn't have RAM installed * low ram will be on Node1 */ /* *遍历所有在memblock.memory中但没有在memblock.reversed中的内存区域,将这些区域的内存通过 *__free_memory_core函数释放到伙伴系统中 */ for_each_free_mem_range(i, NUMA_NO_NODE, MEMBLOCK_NONE, &start, &end, NULL) /* *(start,end)区域中的页是空闲的未被内核使用,通过__free_memory_core将该区域内存释放到伙伴系统对应的空闲 *链表中 */ count += __free_memory_core(start, end); return count; }
reserve_bootmem_region函数是将memblock模块中一个保留区中所有页属性设置为Reserved(页对应的struct page中flag的PG_reserved位被设置为1),配合for_each_reserved_mem_region函数将处理完memblock模块中所有reversed区域的页(存储内核image,fdt等对应的内存空间)。
//mm/page_alloc.c /* *param: * start 和end分别是memblock中某一个reversed区域首地址和尾地址 */ void __meminit reserve_bootmem_region(phys_addr_t start, phys_addr_t end) { unsigned long start_pfn = PFN_DOWN(start); unsigned long end_pfn = PFN_UP(end); for (; start_pfn < end_pfn; start_pfn++) { if (pfn_valid(start_pfn)) { struct page *page = pfn_to_page(start_pfn); //前面bootm_init已经完成了页框对应struct page的初始化,此处为空 init_reserved_page(start_pfn); /* Avoid false-positive PageTail() */ //将页的struc page中的lru链表指向自己(lru->next = lru,lru->pre = lru) INIT_LIST_HEAD(&page->lru); //置位页框对应struct page中flag成员的PG_reserved位 SetPageReserved(page); } } }
__free_memory_core函数是将memblock模块中未被内核使用的内存区域全部释放到伙伴系统对应的空闲链表中。在伙伴系统初始化阶段,内存中未被使用的内存区域都被挂到伙伴系统对应zone区域的10阶可移动空闲链表上去,只有当剩余内存块不足2^10个页,则挂到较小order阶的空闲链表中。
static unsigned long __init __free_memory_core(phys_addr_t start, phys_addr_t end) { //将物理地址转换为对应页框号 unsigned long start_pfn = PFN_UP(start); unsigned long end_pfn = min_t(unsigned long, PFN_DOWN(end), max_low_pfn); if (start_pfn > end_pfn) return 0; __free_pages_memory(start_pfn, end_pfn); return end_pfn - start_pfn; } //将memblock系统中(start,end)区间的内存区域释放到伙伴系统中 static void __init __free_pages_memory(unsigned long start, unsigned long end) { int order; /* *1.while循环就是将(start,end)内存区间分配成一个个order阶连续内存块(内存块包含2^order个连续页框).然后 * 将每个内存块通过__free_pages_bootmem函数释放到所属zone区域的free_area中对应的free list链表中. *2.order取值尽量保证取MAX_ORDER(10),只有当内存区间被切分后剩余内存块无法满足MAX_ORDER要求后,可以取较小 * 值. */ while (start < end) { order = min(MAX_ORDER - 1UL, __ffs(start)); while (start + (1UL << order) > end) order--; /* *将一个order阶的物理页框释放当到伙伴系统中对应zone区域的free_area(即释放到 *Zone->free_area[order].free_list[migratetype]) */ __free_pages_bootmem(pfn_to_page(start), start, order); start += (1UL << order); } } void __init __free_pages_bootmem(struct page *page, unsigned long pfn, unsigned int order) { if (early_page_uninitialised(pfn)) return; return __free_pages_boot_core(page, order); } /* *将以page为首页,order阶的连续页块从memblock模块释放到伙伴系统中,令ZN为page所示zone区域,则该页块释放到 *ZN->free_area[order].free_list[migratetype]中去,页框首页page的lru成员插入到上述free_list链表表尾. *ps:伙伴系统初始化时所有页块默认都是MOVABLE迁移类型. */ static void __init __free_pages_boot_core(struct page *page, unsigned int order) { unsigned int nr_pages = 1 << order; struct page *p = page; unsigned int loop; prefetchw(p); /* *将order阶页块中的所有页进行如下处理: * 1.struct page中flag成员的PG_reserved位设置为0 * 2.将该页的引用计数设置为0(&page->_refcount = 0) */ for (loop = 0; loop < (nr_pages - 1); loop++, p++) { prefetchw(p + 1); __ClearPageReserved(p); set_page_count(p, 0); } __ClearPageReserved(p); set_page_count(p, 0); //统计该zone区域中伙伴系统空闲页框数 page_zone(page)->managed_pages += nr_pages; set_page_refcounted(page); //调用__free_pages函数将以page为首页,order阶的内存块从memblock模块添加到伙伴系统对应的空闲链表上去 __free_pages(page, order); }
__free_pages函数的实现细节后面分析伙伴系统页框的释放时会详细介绍
伙伴系统初始化阶段,会将memblock系统中所有空闲区域中的内存分块移入到伙伴系统对应的free list中.区域分块都是尽量按MAX_ORDER阶进行的(即每个内存块包含2^MAX_ORDER个连续的物理页).只有当内存区域剩余内存不够时才会将小于MAX_ORDER阶的内存块移入伙伴系统.