在内存子系统初始化以前,即boot阶段也需要进行内存管理,启动内存分配器是专为此而设计的。linux启动内存分配器是在伙伴系统、slab机制实现之前,为满足内核中内存的分配而建立的。本身的机制比较简单,使用位图来进行标志分配和释放。arch/x86/kernel/setup.c:setup_arch()在用init_memory_mapping(0, max_low_pfn<<PAGE_SHIFT)建立完内核页表之后,就会调用arch/x86/mm/init_32.c:initmem_init(0, max_pfn)启动bootmem内存分配器。如下:
#ifndef CONFIG_NEED_MULTIPLE_NODES void __init initmem_init(unsigned long start_pfn, unsigned long end_pfn) { #ifdef CONFIG_HIGHMEM highstart_pfn = highend_pfn = max_pfn; if (max_pfn > max_low_pfn) highstart_pfn = max_low_pfn; /* 注册内存活动区 */ e820_register_active_regions(0, 0, highend_pfn); sparse_memory_present_with_active_regions(0); printk(KERN_NOTICE "%ldMB HIGHMEM available.\n", pages_to_mb(highend_pfn - highstart_pfn)); num_physpages = highend_pfn; /* 计算高端内存地址 */ high_memory = (void *) __va(highstart_pfn * PAGE_SIZE - 1) + 1; #else e820_register_active_regions(0, 0, max_low_pfn); sparse_memory_present_with_active_regions(0); num_physpages = max_low_pfn; high_memory = (void *) __va(max_low_pfn * PAGE_SIZE - 1) + 1; #endif #ifdef CONFIG_FLATMEM max_mapnr = num_physpages; #endif __vmalloc_start_set = true; printk(KERN_NOTICE "%ldMB LOWMEM available.\n", pages_to_mb(max_low_pfn)); setup_bootmem_allocator(); /* 启动内存分配器 */ } #endif /* !CONFIG_NEED_MULTIPLE_NODES */主要工作是调用e820_register_active_regions()在节点0上注册内存活动区,然后调用setup_bootmem_allocator()建立启动内存分配器。Linux的内存活动区域其实就是全局变量e820中的内存块做了相关检查和处理后的区域,它会在管理区初始化等地方被用到。注册时,要根据是否配置了高端内存来决定活动的区的终止地址。
/* 扫描e820内存图,并在一个节点上注册活动区 */ void __init e820_register_active_regions(int nid, unsigned long start_pfn, unsigned long last_pfn) { unsigned long ei_startpfn; unsigned long ei_endpfn; int i; for (i = 0; i < e820.nr_map; i++) /* 从全局变量e820中查找活动区 */ if (e820_find_active_region(&e820.map[i], start_pfn, last_pfn, &ei_startpfn, &ei_endpfn)) /* 添加查找到的活动区 */ add_active_range(nid, ei_startpfn, ei_endpfn); } /* * 在start_pfn到last_pfn的地址范围内查找一个活动区,并在ei_startpfn和ei_endpfn中返回 * 这个e820内存块的范围 */ int __init e820_find_active_region(const struct e820entry *ei, unsigned long start_pfn, unsigned long last_pfn, unsigned long *ei_startpfn, unsigned long *ei_endpfn) { u64 align = PAGE_SIZE; *ei_startpfn = round_up(ei->addr, align) >> PAGE_SHIFT; *ei_endpfn = round_down(ei->addr + ei->size, align) >> PAGE_SHIFT; /* 跳过内存图中比一个页面还小的各个内存块 */ if (*ei_startpfn >= *ei_endpfn) return 0; /* 如果内存图中的所有内存块都在节点范围之外,则跳过 */ if (ei->type != E820_RAM || *ei_endpfn <= start_pfn || *ei_startpfn >= last_pfn) return 0; /* 检查是否有重叠 */ if (*ei_startpfn < start_pfn) *ei_startpfn = start_pfn; if (*ei_endpfn > last_pfn) *ei_endpfn = last_pfn; return 1; }主要的工作是在start_pfn到last_pfn的地址范围内,从e820内存图的各内存块中查找一个物理活动区,若找到,则把其物理地址范围保存到ei_startpfn和ei_endpfn中,然后调用mm/page_alloc.c中的add_active_range()函数在nid节点上注册这块活动区。如下:
/* 添加活动区域,需要对原有的进行检查 */ void __init add_active_range(unsigned int nid, unsigned long start_pfn, unsigned long end_pfn) { int i; mminit_dprintk(MMINIT_TRACE, "memory_register", "Entering add_active_range(%d, %#lx, %#lx) " "%d entries of %d used\n", nid, start_pfn, end_pfn, nr_nodemap_entries, MAX_ACTIVE_REGIONS); mminit_validate_memmodel_limits(&start_pfn, &end_pfn); /* 如果可能,与存在的活动内存区合并 */ for (i = 0; i < nr_nodemap_entries; i++) { if (early_node_map[i].nid != nid) continue; /* 如果一个存在的活动区包含这个要添加的新区,则跳过 */ if (start_pfn >= early_node_map[i].start_pfn && end_pfn <= early_node_map[i].end_pfn) return; /* 如果合适,则向前合并 */ if (start_pfn <= early_node_map[i].end_pfn && end_pfn > early_node_map[i].end_pfn) { early_node_map[i].end_pfn = end_pfn; return; } /* 如果合适,则向后合并 */ if (start_pfn < early_node_map[i].end_pfn && end_pfn >= early_node_map[i].start_pfn) { early_node_map[i].start_pfn = start_pfn; return; } } /* 检查early_node_map是否足够大 */ if (i >= MAX_ACTIVE_REGIONS) { printk(KERN_CRIT "More than %d memory regions, truncating\n", MAX_ACTIVE_REGIONS); return; } early_node_map[i].nid = nid; early_node_map[i].start_pfn = start_pfn; early_node_map[i].end_pfn = end_pfn; nr_nodemap_entries = i + 1; }该函数注册一段页框范围指定的物理内存活动区,参数nid为要注册到的节点编号,start_pfn为可用物理内存的开始PFN(物理页框号),end_pfn为可用物理内存的终止PFN。这些活动被存储在全局的early_node_map[]数组中(该数组也定义在mm/page_alloc.c中),表示内存管理的早期节点显现图。它会被以后的free_area_init_nodes()用来计算管理区大小和空洞数量。如果活动区范围跨越一个内存空洞,则需要确保内存不会被bootmem分配器释放(依赖于体系结构)。如果可能,要注册的活动区可以跟已存在的活动区合并。
/* * Early reserved memory areas. */ #define MAX_EARLY_RES 20 /* 保留空间最大块数 */ struct early_res { /* 保留空间结构 */ u64 start, end; char name[16]; char overlap_ok; }; /* 保留内存空间全局变量 */ static struct early_res early_res[MAX_EARLY_RES] __initdata = { { 0, PAGE_SIZE, "BIOS data page" }, /* BIOS data page */ {} }; bootmem分配器的数据结构bootmem_data_t用于管理启动内存的分配、释放等,在include/linux/bootmem.h中,如下: /* 用于bootmem分配器的节点数据结构 */ typedef struct bootmem_data { unsigned long node_min_pfn; unsigned long node_low_pfn; void *node_bootmem_map; unsigned long last_end_off; unsigned long hint_idx; struct list_head list; } bootmem_data_t;这些域分别为存放bootmem位图的第一个页面(即内核映象结束处的第一个页面)、低端内存最大页面号(物理内存的顶点,最高不超过896MB)、位图(各个位代表节点上的所有物理内存页,包括洞)、前一次分配的最后一个字节相对于last_pos的位移量、hint_idx为前一次分配的最后一个页面号、list是用于内存分配的链表。注意在内存节点pg_data_t数据结构中,用bdata指针批向了这个bootmem分配器的数据结构。
void __init setup_bootmem_allocator(void) { int nodeid; unsigned long bootmap_size, bootmap; /* * 初始化引导时的内存分配器(只是低端内存区): */ /* 计算所需要的映射页面大小一个字节一位,所以需要对总的页面大小除以8 */ bootmap_size = bootmem_bootmap_pages(max_low_pfn)<<PAGE_SHIFT; /* 从e820中查找一个合适的内存块 */ bootmap = find_e820_area(0, max_pfn_mapped<<PAGE_SHIFT, bootmap_size, PAGE_SIZE); if (bootmap == -1L) panic("Cannot find bootmem map of size %ld\n", bootmap_size); /* 将用于位图映射的页面保留 */ reserve_early(bootmap, bootmap + bootmap_size, "BOOTMAP"); printk(KERN_INFO " mapped low ram: 0 - %08lx\n", max_pfn_mapped<<PAGE_SHIFT); printk(KERN_INFO " low ram: 0 - %08lx\n", max_low_pfn<<PAGE_SHIFT); /* 扫描每个在线节点 */ for_each_online_node(nodeid) { unsigned long start_pfn, end_pfn; #ifdef CONFIG_NEED_MULTIPLE_NODES /* 计算出当前节点的起始地址和终止地址 */ start_pfn = node_start_pfn[nodeid]; end_pfn = node_end_pfn[nodeid]; if (start_pfn > max_low_pfn) continue; if (end_pfn > max_low_pfn) end_pfn = max_low_pfn; #else start_pfn = 0; end_pfn = max_low_pfn; #endif /* 对指定节点安装启动分配器 */ bootmap = setup_node_bootmem(nodeid, start_pfn, end_pfn, bootmap); } /* bootmem的分配制度到这里就已经建立完成,把after_bootmem变量置成1 */ after_bootmem = 1; } static unsigned long __init setup_node_bootmem(int nodeid, unsigned long start_pfn, unsigned long end_pfn, unsigned long bootmap) { unsigned long bootmap_size; /* 初始化这个内存节点:将映射位图中的所有位置1。不要触及min_low_pfn */ bootmap_size = init_bootmem_node(NODE_DATA(nodeid), bootmap >> PAGE_SHIFT, start_pfn, end_pfn); printk(KERN_INFO " node %d low ram: %08lx - %08lx\n", nodeid, start_pfn<<PAGE_SHIFT, end_pfn<<PAGE_SHIFT); printk(KERN_INFO " node %d bootmap %08lx - %08lx\n", nodeid, bootmap, bootmap + bootmap_size); /* 将活动内存区对应位图相关位置0,表示可被分配的 */ free_bootmem_with_active_regions(nodeid, end_pfn); /* 将保留内存的相关页面对应位置为1,表示已经分配 或者不可用(不能被分配) */ early_res_to_bootmem(start_pfn<<PAGE_SHIFT, end_pfn<<PAGE_SHIFT); /* 返回映射页面的最后地址,下次映射即可以从这里开始 */ return bootmap + bootmap_size; }设置分配器的主要工作是初始化引导时的内存分配器(只是低端内存区);在e820中查找引导内存块;对每个在线节点计算出其起始和终止地址,然后调用setup_node_bootmem()安装启动分配器。在这个函数中,调用init_bootmem_node()初始化这个节点的映射位图。将活动内存区对应位图相关位置0,表示可用;将保留内存的相关页面对应位置为1,表示已经分配(不可用)。其中初始化映射位图的函数init_bootmem_node()在mm/bootmem.c中,调用链为init_bootmem_node()--->init_bootmem_core()--->link_bootmem(bdata),最终将bdata添加到全局的bdata_list链表中。当所有在线内存节点设置好后,bootmem内存分配器就初始化完毕。
void __init paging_init(void) { pagetable_init(); __flush_tlb_all(); kmap_init(); /* * NOTE: 在这里bootmem分配器完全可用了 */ sparse_init(); zone_sizes_init(); }该函数建立完整的页表,注意起始的8MB已经被head_32.S映射了。该函数也会取消虚拟地址0处的页面映射,以便我们可以在内核中陷入并跟踪那些麻烦的NULL引用错误。它的主要工作包括页表初始化、内核永久映射区初始化、稀疏内存映射初始化、管理区初始化。下面重点讨论该函数。
static void __init pagetable_init(void) { pgd_t *pgd_base = swapper_pg_dir; permanent_kmaps_init(pgd_base); } #ifdef CONFIG_HIGHMEM static void __init permanent_kmaps_init(pgd_t *pgd_base) { unsigned long vaddr; pgd_t *pgd; pud_t *pud; pmd_t *pmd; pte_t *pte; vaddr = PKMAP_BASE; /* 该阶段,也就是永久内存映射区的页表初始化 */ page_table_range_init(vaddr, vaddr + PAGE_SIZE*LAST_PKMAP, pgd_base); pgd = swapper_pg_dir + pgd_index(vaddr); pud = pud_offset(pgd, vaddr); pmd = pmd_offset(pud, vaddr); pte = pte_offset_kernel(pmd, vaddr); /* 将永久映射区间映射的第一个页表项保存到pkmap_page_table中 */ pkmap_page_table = pte; } /* ...... */ #else static inline void permanent_kmaps_init(pgd_t *pgd_base) { } #endif /* CONFIG_HIGHMEM */根据上面代码,只有定义了使用高端内存,才会有高端永久映射区。首先用pgd_base保存页全局目录表的起始地址swapper_pg_dir。而后在函数permanent_kmaps_init()中,调用page_table_range_init()建立页表,这个函数在前面分析过,它会先根据永久映射区起始地址PKMAP_BASE,获取pgd表项索引、pmd表项索引,然后建立下一级pmd表,和最终的pte页表。第一个页表项保存到pkmap_page_table中。如果内核不划分高端内存,则permanent_kmaps_init()什么也不做。注意paging_init()初始化完页表后,要用__flush_tlb_all()刷新缓存TLB中的映射内容。
static void __init kmap_init(void) { unsigned long kmap_vstart; /* * Cache the first kmap pte: */ /* 得到高端固定内存映射区域的起始内存的页表,将这个页表 放到kmap_pte变量中。确切的说应该是固定内存中的临时内存映射区域 */ kmap_vstart = __fix_to_virt(FIX_KMAP_BEGIN); kmap_pte = kmap_get_fixmap_pte(kmap_vstart); kmap_prot = PAGE_KERNEL; }该函数首先把高端固定映射区(即高端临时内存映射区)的起始地址FIX_KMAP_BEGIN转换成虚拟地址,然后获取它的pte页表项,并保存到全局的kmap_pte中。
static void __init zone_sizes_init(void) { /* 初始化各种管理区中的最大页面数,在后面用于具体的初始化工作 */ unsigned long max_zone_pfns[MAX_NR_ZONES]; memset(max_zone_pfns, 0, sizeof(max_zone_pfns)); max_zone_pfns[ZONE_DMA] = /* DMA区的最大页面帧号,后面的类似 */ virt_to_phys((char *)MAX_DMA_ADDRESS) >> PAGE_SHIFT; max_zone_pfns[ZONE_NORMAL] = max_low_pfn; #ifdef CONFIG_HIGHMEM max_zone_pfns[ZONE_HIGHMEM] = highend_pfn; #endif /* 内存体系的MMU建立,包括伙伴系统的初步建立 */ free_area_init_nodes(max_zone_pfns); }在“内存描述”一节中对各种管理区类型做了详细介绍,这里首先用数组max_zone_pfns保存各种类型管理区的最大页面数,宏MAX_DMA_ADDRESS在arch/x86/include/asm/dma.h中定义,表示能执行DMA传输的最大地址,其中x86-32非PAE模式下MAX_DMA_ADDRESS为PAGE_OFFSET + 0x1000000,即从内核空间开始处的16MB为DMA区的地址范围,因此DMA区的地址范围为3G~3G+16M这一段空间。把这个最大地址转换成页帧号保存到max_zone_pfns数组,接着保存NORMAL区和HIGHMEM区的最大页面号。最后调用核心函数mm/page_alloc.c:free_area_init_nodes()初始化所有pg_data_t内存节点的各种管理区数据,传入参数为由各管理区最大PFN构成的数组。代码如下:
void __init free_area_init_nodes(unsigned long *max_zone_pfn) { unsigned long nid; int i; /* Sort early_node_map as initialisation assumes it is sorted */ sort_node_map(); /* 将活动区域进行排序 */ /* 记录管理区的界限 */ memset(arch_zone_lowest_possible_pfn, 0, sizeof(arch_zone_lowest_possible_pfn)); memset(arch_zone_highest_possible_pfn, 0, sizeof(arch_zone_highest_possible_pfn)); /* 找出活动内存中最小的页面 */ arch_zone_lowest_possible_pfn[0] = find_min_pfn_with_active_regions(); arch_zone_highest_possible_pfn[0] = max_zone_pfn[0]; for (i = 1; i < MAX_NR_ZONES; i++) { if (i == ZONE_MOVABLE) continue; /* 假定区域连续,下一个区域的最小页面为上一个区的最大页面 */ arch_zone_lowest_possible_pfn[i] = arch_zone_highest_possible_pfn[i-1]; arch_zone_highest_possible_pfn[i] = max(max_zone_pfn[i], arch_zone_lowest_possible_pfn[i]); } /* 对ZONE_MOVABLE区域设置为0 */ arch_zone_lowest_possible_pfn[ZONE_MOVABLE] = 0; arch_zone_highest_possible_pfn[ZONE_MOVABLE] = 0; /* 找出每个节点上ZONE_MOVABLE区的开始页面号 */ memset(zone_movable_pfn, 0, sizeof(zone_movable_pfn)); find_zone_movable_pfns_for_nodes(zone_movable_pfn); /* 打印管理区的范围 */ printk("Zone PFN ranges:\n"); for (i = 0; i < MAX_NR_ZONES; i++) { if (i == ZONE_MOVABLE) continue; printk(" %-8s %0#10lx -> %0#10lx\n", zone_names[i], arch_zone_lowest_possible_pfn[i], arch_zone_highest_possible_pfn[i]); } /* 打印每个节点上ZONE_MOVABLE区开始的页面号 */ printk("Movable zone start PFN for each node\n"); for (i = 0; i < MAX_NUMNODES; i++) { if (zone_movable_pfn[i]) printk(" Node %d: %lu\n", i, zone_movable_pfn[i]); } /* 打印early_node_map[] */ printk("early_node_map[%d] active PFN ranges\n", nr_nodemap_entries); for (i = 0; i < nr_nodemap_entries; i++) printk(" %3d: %0#10lx -> %0#10lx\n", early_node_map[i].nid, early_node_map[i].start_pfn, early_node_map[i].end_pfn); /* 初始化每个节点 */ mminit_verify_pageflags_layout(); /* 调试用 */ setup_nr_node_ids(); for_each_online_node(nid) { pg_data_t *pgdat = NODE_DATA(nid); /* zone中数据的初始化,伙伴系统建立,但是没有页面 和数据,页面在后面的mem_init中得到 */ free_area_init_node(nid, NULL, find_min_pfn_for_node(nid), NULL); /* 对该节点上的任何内存区 */ if (pgdat->node_present_pages) node_set_state(nid, N_HIGH_MEMORY); /* 内存的相关检查 */ check_for_regular_memory(pgdat); } } void __paginginit free_area_init_node(int nid, unsigned long *zones_size, unsigned long node_start_pfn, unsigned long *zholes_size) { pg_data_t *pgdat = NODE_DATA(nid); pgdat->node_id = nid; /* 这个已在前面调用一个函数得到 */ pgdat->node_start_pfn = node_start_pfn; /* 计算系统中节点nid的所有物理页面并保存在数据结构中 */ calculate_node_totalpages(pgdat, zones_size, zholes_size); /* 当节点只有一个时,将节点的map保存到全局变量中 */ alloc_node_mem_map(pgdat); #ifdef CONFIG_FLAT_NODE_MEM_MAP printk(KERN_DEBUG "free_area_init_node: node %d, pgdat %08lx, node_mem_map %08lx\n", nid, (unsigned long)pgdat, (unsigned long)pgdat->node_mem_map); #endif /* zone中相关数据的初始化,包括伙伴系统,等待队列,相关变量, 数据结构、链表等 */ free_area_init_core(pgdat, zones_size, zholes_size); } static void __paginginit free_area_init_core(struct pglist_data *pgdat, unsigned long *zones_size, unsigned long *zholes_size) { enum zone_type j; int nid = pgdat->node_id; unsigned long zone_start_pfn = pgdat->node_start_pfn; int ret; pgdat_resize_init(pgdat); pgdat->nr_zones = 0; init_waitqueue_head(&pgdat->kswapd_wait); pgdat->kswapd_max_order = 0; pgdat_page_cgroup_init(pgdat); for (j = 0; j < MAX_NR_ZONES; j++) { struct zone *zone = pgdat->node_zones + j; unsigned long size, realsize, memmap_pages; enum lru_list l; /* 下面的两个函数会获得指定节点的真实内存大小 */ size = zone_spanned_pages_in_node(nid, j, zones_size); realsize = size - zone_absent_pages_in_node(nid, j, zholes_size); /* * Adjust realsize so that it accounts for how much memory * is used by this zone for memmap. This affects the watermark * and per-cpu initialisations */ memmap_pages = /* 存放页面所需要的内存大小 */ PAGE_ALIGN(size * sizeof(struct page)) >> PAGE_SHIFT; if (realsize >= memmap_pages) { realsize -= memmap_pages; if (memmap_pages) printk(KERN_DEBUG " %s zone: %lu pages used for memmap\n", zone_names[j], memmap_pages); } else printk(KERN_WARNING " %s zone: %lu pages exceeds realsize %lu\n", zone_names[j], memmap_pages, realsize); /* Account for reserved pages */ if (j == 0 && realsize > dma_reserve) { realsize -= dma_reserve; /* 减去为DMA保留的页面 */ printk(KERN_DEBUG " %s zone: %lu pages reserved\n", zone_names[0], dma_reserve); } /* 如果不是高端内存区 */ if (!is_highmem_idx(j)) nr_kernel_pages += realsize; nr_all_pages += realsize; /* 下面为初始化zone结构的相关变量 */ zone->spanned_pages = size; zone->present_pages = realsize; #ifdef CONFIG_NUMA zone->node = nid; zone->min_unmapped_pages = (realsize*sysctl_min_unmapped_ratio) / 100; zone->min_slab_pages = (realsize * sysctl_min_slab_ratio) / 100; #endif zone->name = zone_names[j]; spin_lock_init(&zone->lock); spin_lock_init(&zone->lru_lock); zone_seqlock_init(zone); zone->zone_pgdat = pgdat; zone->prev_priority = DEF_PRIORITY; zone_pcp_init(zone); for_each_lru(l) { /* 初始化链表 */ INIT_LIST_HEAD(&zone->lru[l].list); zone->reclaim_stat.nr_saved_scan[l] = 0; } zone->reclaim_stat.recent_rotated[0] = 0; zone->reclaim_stat.recent_rotated[1] = 0; zone->reclaim_stat.recent_scanned[0] = 0; zone->reclaim_stat.recent_scanned[1] = 0; zap_zone_vm_stats(zone); zone->flags = 0; if (!size) continue; /* 需要定义相关宏 */ set_pageblock_order(pageblock_default_order()); /* zone中变量pageblock_flags,表示从启动分配器中进行内存申请 */ setup_usemap(pgdat, zone, size); /* zone中的任务等待队列和zone的伙伴系统(MAX_ORDER个链表)的初始化 */ ret = init_currently_empty_zone(zone, zone_start_pfn, size, MEMMAP_EARLY); BUG_ON(ret); /* zone中page相关属性的初始化工作 */ memmap_init(size, nid, j, zone_start_pfn); zone_start_pfn += size; } }分析:
#ifdef CONFIG_NUMA #define MAX_ZONELISTS 2 struct zonelist_cache { unsigned short z_to_n[MAX_ZONES_PER_ZONELIST]; /* zone->nid */ DECLARE_BITMAP(fullzones, MAX_ZONES_PER_ZONELIST); /* zone full? */ unsigned long last_full_zap; /* when last zap'd (jiffies) */ }; #else #define MAX_ZONELISTS 1 struct zonelist_cache; #endif struct zoneref { struct zone *zone; /* Pointer to actual zone */ int zone_idx; /* zone_idx(zoneref->zone) */ }; struct zonelist { struct zonelist_cache *zlcache_ptr; // NULL or &zlcache struct zoneref _zonerefs[MAX_ZONES_PER_ZONELIST + 1]; #ifdef CONFIG_NUMA struct zonelist_cache zlcache; // optional ... #endif };从前面“内存描述”介绍中可知,zonelist在节点的pg_data_t结构中维护,以作为节点的备用内存区,当节点没有可用内存时,就从队列中分配内存。一个zonelist表示一个管理区的一个队列,队列中的第一个管理区是分配的目标,其他则为备用管理区,以优先级递减的方式存放在队列中。zonelist_cache结构缓存了每个zonelist中的一些关键信息,以便在get_page_from_freelist()中扫描可用页面时,有更小的开销。其中位图fullzones用来跟踪当前zonelist中哪些管理区开始内存不足了;数组z_to_n[]把zonelist中的每个管理区映射到它的节点id,以便我们能估计在当前进程允许的内存范围内节点是否被设置。zoneref则包含了zonelist中实际的zone信息,封装成一个结构是为了避免解引用时进入一个大的结构体内并且搜索表格。
static void zoneref_set_zone(struct zone *zone, struct zoneref *zoneref) { zoneref->zone = zone; zoneref->zone_idx = zone_idx(zone); } /* * 构建管理区环形分配队列,把节点上的所有管理区添加到队列中 */ static int build_zonelists_node(pg_data_t *pgdat, struct zonelist *zonelist, int nr_zones, enum zone_type zone_type) { struct zone *zone; BUG_ON(zone_type >= MAX_NR_ZONES); zone_type++; do { zone_type--; zone = pgdat->node_zones + zone_type; if (populated_zone(zone)) { /* 如果以页面为单位的管理区的总大小不为0 */ zoneref_set_zone(zone, /* 将管理区添加到链表中 */ &zonelist->_zonerefs[nr_zones++]); check_highest_zone(zone_type); } } while (zone_type); return nr_zones; } #ifdef CONFIG_NUMA /* ...... */ static void build_zonelists(pg_data_t *pgdat) { /* ...... */ } static void build_zonelist_cache(pg_data_t *pgdat) { /* ...... */ } #else /* non CONFIG_NUMA */ /* ...... */ static void build_zonelists(pg_data_t *pgdat) { int node, local_node; enum zone_type j; struct zonelist *zonelist; local_node = pgdat->node_id; zonelist = &pgdat->node_zonelists[0]; /* 将zone添加到zone链表中,这样,zone中page的 分配等操作将依靠这个环形的链表 */ j = build_zonelists_node(pgdat, zonelist, 0, MAX_NR_ZONES - 1); /* * Now we build the zonelist so that it contains the zones * of all the other nodes. * We don't want to pressure a particular node, so when * building the zones for node N, we make sure that the * zones coming right after the local ones are those from * node N+1 (modulo N) */ /* 对其他在线的节点创建zonelist */ for (node = local_node + 1; node < MAX_NUMNODES; node++) { if (!node_online(node)) continue; j = build_zonelists_node(NODE_DATA(node), zonelist, j, MAX_NR_ZONES - 1); } for (node = 0; node < local_node; node++) { if (!node_online(node)) continue; j = build_zonelists_node(NODE_DATA(node), zonelist, j, MAX_NR_ZONES - 1); } zonelist->_zonerefs[j].zone = NULL; zonelist->_zonerefs[j].zone_idx = 0; } /* 构建zonelist缓存:对非NUMA的zonelist信息,只是把zlcache_ptr设成NULL */ static void build_zonelist_cache(pg_data_t *pgdat) { pgdat->node_zonelists[0].zlcache_ptr = NULL; } #endif /* CONFIG_NUMA */ /* 返回int值,因为可能通过stop_machine()调用本函数 */ static int __build_all_zonelists(void *dummy) { int nid; #ifdef CONFIG_NUMA memset(node_load, 0, sizeof(node_load)); #endif for_each_online_node(nid) { pg_data_t *pgdat = NODE_DATA(nid); /* 创建zonelists,这个队列用来在分配内存时回绕,循环访问 */ build_zonelists(pgdat); /* 创建zonelist缓存信息:在非NUMA中,仅仅是把相关缓存变量设成NULL */ build_zonelist_cache(pgdat); } return 0; } void build_all_zonelists(void) { /* 设置全局变量current_zonelist_order */ set_zonelist_order(); /* 对所有节点创建zonelists */ if (system_state == SYSTEM_BOOTING) { /* 系统正在引导时 */ __build_all_zonelists(NULL); mminit_verify_zonelist(); /* 调试用 */ cpuset_init_current_mems_allowed(); } else { /* 非引导时要停止所有cpu以确保没有使用zonelist */ stop_machine(__build_all_zonelists, NULL, NULL); /* cpuset refresh routine should be here */ } /* 计算所有zone中可分配的页面数之和 */ vm_total_pages = nr_free_pagecache_pages(); /* * Disable grouping by mobility if the number of pages in the * system is too low to allow the mechanism to work. It would be * more accurate, but expensive to check per-zone. This check is * made on memory-hotadd so a system can start with mobility * disabled and enable it later */ if (vm_total_pages < (pageblock_nr_pages * MIGRATE_TYPES)) page_group_by_mobility_disabled = 1; else page_group_by_mobility_disabled = 0; printk("Built %i zonelists in %s order, mobility grouping %s. " "Total pages: %ld\n", nr_online_nodes, zonelist_order_name[current_zonelist_order], page_group_by_mobility_disabled ? "off" : "on", vm_total_pages); #ifdef CONFIG_NUMA printk("Policy zone: %s\n", zone_names[policy_zone]); #endif }分析: