由于伙伴系统需要使用到 zone 和 zonelist 结构,所以伙伴系统的初始化和 zone & zonelist 关系密切。这里我们回顾一下 zone 和 zonelist 的结构。
zone 是用来表示 Linux 对内存的区域划分的结构,一般的,物理内存会被划分成为 ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM 区域。
我们先来回顾一下 zone 的数据结构:
struct zone {
/* Read-mostly fields */
/* zone watermarks, access with *_wmark_pages(zone) macros */
unsigned long watermark[NR_WMARK];
unsigned long nr_reserved_highatomic;
long lowmem_reserve[MAX_NR_ZONES];
#ifdef CONFIG_NUMA
int node;
#endif
struct pglist_data *zone_pgdat;
struct per_cpu_pageset __percpu *pageset;
#ifndef CONFIG_SPARSEMEM
unsigned long *pageblock_flags;
#endif /* CONFIG_SPARSEMEM */
/* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */
unsigned long zone_start_pfn;
unsigned long managed_pages;
unsigned long spanned_pages;
unsigned long present_pages;
const char *name;
#ifdef CONFIG_MEMORY_ISOLATION
unsigned long nr_isolate_pageblock;
#endif
#ifdef CONFIG_MEMORY_HOTPLUG
/* see spanned/present_pages for more description */
seqlock_t span_seqlock;
#endif
int initialized;
/* Write-intensive fields used from the page allocator */
ZONE_PADDING(_pad1_)
/* free areas of different sizes */
struct free_area free_area[MAX_ORDER];
/* zone flags, see below */
unsigned long flags;
/* Primarily protects free_area */
spinlock_t lock;
/* Write-intensive fields used by compaction and vmstats. */
ZONE_PADDING(_pad2_)
unsigned long percpu_drift_mark;
#if defined CONFIG_COMPACTION || defined CONFIG_CMA
/* pfn where compaction free scanner should start */
unsigned long compact_cached_free_pfn;
/* pfn where async and sync compaction migration scanner should start */
unsigned long compact_cached_migrate_pfn[2];
#endif
#ifdef CONFIG_COMPACTION
unsigned int compact_considered;
unsigned int compact_defer_shift;
int compact_order_failed;
#endif
#if defined CONFIG_COMPACTION || defined CONFIG_CMA
/* Set to true when the PG_migrate_skip bits should be cleared */
bool compact_blockskip_flush;
#endif
bool contiguous;
ZONE_PADDING(_pad3_)
/* Zone statistics */
atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
atomic_long_t vm_numa_stat[NR_VM_NUMA_STAT_ITEMS];
} ____cacheline_internodealigned_in_smp;
其中:
watermark : 是每个 zone 会计算出 3 个水线,分别是 water_min, water_low, water_high 这些将在回收中用到
lowmem_reserve:zone 预留的内存
zone_pgdat:指向内存节点
zone_start_pfn:zone 中,开始页面的帧号
managed_pages:被伙伴系统管理的页面数量
spanned_pages:zone 包含的页面数量
present_pages:zone 实际管理的页面数量。对于一些体系来说,其等于 spanned_pages
free_area:管理空闲区域的数组,包含链表
lock:保护 zone 结构的自旋锁
lru_lock:LRU 访问的自旋锁
lruvec:LRU 链表集合
vm_stat: zone 计数
标记加粗的部分,均和伙伴系统相关。结构体中的 ZONE_PADDING 是为了性能优化考虑,分布到不同的 cache line。
zone 的初始化参考:
Linux 内存管理窥探(9):内存初始化(bootmem_init)
系统中会有 zonelist 的数据结构,伙伴系统会从 zonelist 开始分配内存,zonelist 有一个 zoneref数组,数组中,有一个成员指向 zone 数据结构。
zoneref 的数组的第一个成员是指向的 zone 是页面分配器的第一个候选者,其他的是第一个候选者失败后才考虑,优先级逐级降低。他的初始化在 build_zonelist_node 中完成,完成后大致为:
先从 HIGHMEM 分配,在去找 NORMAL 分配。
首先了解一下 pageblock 的概念:
一个 pageblock 通常是 2^(MAX_ORDER - 1) 个页面,每一个 pageblock 都有一个对应的 MIGRATE_TYPES 类型。在 zone 的数据结构中,有一个成员指针 pageblock_flags,它用于存放每一个 pageblock 的 MIGRATE_TYPES 类型的内存空间。pageblock_flags 指向的内存空间的大小(即,有多少个 pageblock_flags ),是通过 usemap_size() 函数计算,每一个 pageblock_flags 占用了 4 bits 来存放 MIGRATE_TYPES 类型。
所以呢,Linux 下面的内存,按照 pageblock 进行了 MIGRATE_TYPES 的分类,并存储到了 pageblock_flags 中。
在初始化阶段:
start_kernel() -> setup_arch() -> paging_init() -> bootmem_init() -> zone_sizes_init() -> free_area_init_node() -> free_area_init_core() -> memmap_init() 中
在 memmap_init_zone 函数,对所有的的内存页面全部标记为 MIGRATE_MOVEABLE
Buddy System 的初始化在哪里呢?
start_kernel -> mm_init -> mem_init -> free_all_bootmem -> free_low_memory_core_early
把内存块传递到 __free_pages_memory 函数
static void __init __free_pages_memory(unsigned long start, unsigned long end)
{
int order;
while (start < end) {
order = min(MAX_ORDER - 1UL, __ffs(start));
while (start + (1UL << order) > end)
order--;
memblock_free_pages(pfn_to_page(start), start, order);
start += (1UL << order);
}
}
可以看到,按照 order 来进行添加。
最后通过 __free_pages_boot_core 调用到 __free_pages 函数,将系统的内存,按照 order 的方式,添加到伙伴系统。
系统初始化阶段,没有被使用到的内存,都以最大阶 10 挂到了 10 的那个链表上。