linux 源代码分析5:伙伴系统初始化

伙伴系统初始化

启动

计算机在启动时都是先加电,然后进行硬件检测并加载引导程序。
引导程序把Linux系统内核装载到内存,加载内核后引导程序跳转到
arch/x86/boot/compressed/head_32.S的startup_32标号处执行。
在arch/x86/boot/compressed/head_32.S中会调用arch/x86/boot/main.c中的main函数。
main函数执行完后会跳转到arch/x86/kernel/head_32.S的标号startup_32处执行。
在arch/x86/kernel/head_32.S中会调用arch/x86/kernel/head32.c中的i386_start_kernel
i386_start_kernel调用init/main.c中的start_kernel函数,start_kernel是用来启动内核的主函数。
start_kernel函数会调用arch/x86/kernel/setup.c中的setup_arch函数
setup_arch函数会调用arch/x86/mm/init_32.c中的paging_init函数

初始化

初始化后释放初始化内存分配器的内存到伙伴系统的流程是:
start_kernel ()at init/main.c:524
mm_init() at init/main.c:458
mem_init() at arch/x86/mm/init_32.c:752
free_all_bootmem() at mm/nobootmem.c:168
free_low_memory_core_early() at mm/nobootmem.c:130
__free_memory_core() at mm/nobootmem.c:118
__free_pages_memory() at mm/nobootmem.c:99
__free_pages_bootmem() at mm/page_alloc.c:749
__free_pages() at mm/page_alloc.c:2506

__free_pages 函数

最终由伙伴系统的内存释放函数__free_pages来吧初始化内存分配器的内存释放到伙伴系统。
伙伴系统的初始化实质是从初始化内存分配器接管内存管理的权限。而伙伴初始化也分成两个步骤,第一步是伙伴系统各种结构和管理数据的初始化,第二个步骤是把初始化内存分配器中的空闲内存释放到伙伴系统,之后就可以正式使用伙伴系统分配内存了。第一个步骤关键由zone_sizes_init和build_all_zonelists函数完成。第二个步骤为的执行流程我们在上面已经列出,具体代码将在初始内存分配器的实现代码。
我们知道在numa系统中,包含若干节点,而每个节点包含若干区域,每个区域包含若干空闲区域,每个空闲区域包含若干迁移类型,对每个迁移类型,都有一个空闲链表。空闲链表链接的是空闲块。
在初始化过程中关键的是节点和区域的初始化,因为空闲区域的初始化只是对包含的空闲链表数组的每个链表初始化为空链表。并对空闲块计数初始化为0而已。
节点初始化重要的部分是找到第一个可用的页的页帧,节点包含也页面数和节点的可用页面数,还有初始化节点中page结构数组。
对区域的初始化的关键也查找也是第一个可用的页的页帧,以及区域页面数和可用页面数的初始化。实际节点的可用页面数就是节点的所有区域可用页面数的和,节点的页面数是节点的所有区域包含的页面数的和。
为了在后面的分析避免嵌套过深,下面先介绍一个函数,包括计算区域页面数和可用页面数的函数和计算节点范围的函数:

zone_spanned_pages_in_node函数

zone_spanned_pages_in_node函数计算区域的的包含的页面数,包含中间可能存在的空洞。计算区域总页面数要考虑两个因数:
1:在系统中包含一个数组arch_zone_lowest_possible_pfn,保存了每种类型的区域可能的最小的页帧号,另外一个数组arch_zone_highest_possible_pfn,保存了每种类型的区域可能的最大的页帧号。
2:另外有一种区域类型是ZONE_MOVABLE,这是系统为了防止内存碎片退出的一种区域类型,其他类型的区域不能包含ZONE_MOVABLE区域的页面。一个节点中的区域是按顺序存放的,ZONE_MOVABLE存放在节点的最高端。
zone_spanned_pages_in_node在mm/page_alloc.c中实现代码如下:
4090 staticunsigned long __meminit zone_spanned_pages_in_node(int nid,
4091                                        unsigned long zone_type,
4092                                        unsigned long *ignored)
4093 {
4094unsigned long node_start_pfn,node_end_pfn;
4095unsigned long zone_start_pfn,zone_end_pfn;
4096
4097/* Get the start and end of the nodeand zone */
4098get_pfn_range_for_nid(nid,&node_start_pfn, &node_end_pfn);
4099zone_start_pfn = arch_zone_lowest_possible_pfn[zone_type];
4100zone_end_pfn =arch_zone_highest_possible_pfn[zone_type];
4101        adjust_zone_range_for_zone_movable(nid, zone_type,
4102                                node_start_pfn, node_end_pfn,
4103                                 &zone_start_pfn,&zone_end_pfn);
4104
4105/* Check that this node has pageswithin the zone's required range */
4106if (zone_end_pfn < node_start_pfn|| zone_start_pfn > node_end_pfn)
4107        return 0;
4108
4109/* Move the zone boundaries inside thenode if necessary */
4110zone_end_pfn = min(zone_end_pfn,node_end_pfn);
4111zone_start_pfn = max(zone_start_pfn,node_start_pfn);
4112
4113/* Return the spanned pages */
4114return zone_end_pfn - zone_start_pfn;
4115 }
4098行调用get_pfn_range_for_nid函数遍历初始化内存分配器的每个空闲段,取得最小的空闲页帧和最大的空闲页帧。
4098-4099行获得系统允许的区域最大页帧和最小页帧。
在区域中可能于ZONE_MOVABLE类型区域有重合,4101行调用adjust_zone_range_for_zone_movable函数去掉与区域ZONE_MOVABLE类型重合的部分。
4106-4107行如果区域不在所在的节点的页范围内,返回0.
4110-4111行区域的页面范围只能在所在节点的范围内。

adjust_zone_range_for_zone_movable函数

adjust_zone_range_for_zone_movable函数是用来保留ZONE_MOVABLE类型区域的页面的。在mm/page_alloc.c中实现代码如下:
4060static void __meminit adjust_zone_range_for_zone_movable(int nid,
4061                                        unsigned long zone_type,
4062                                        unsigned long node_start_pfn,
4063                                        unsigned long node_end_pfn,
4064                                unsigned long *zone_start_pfn,
4065                                        unsigned long *zone_end_pfn)
4066 {
4067        /* Only adjust if ZONE_MOVABLE is on this node */
4068        if (zone_movable_pfn[nid]) {
4069        /* Size ZONE_MOVABLE */
4070        if (zone_type == ZONE_MOVABLE){
4071                *zone_start_pfn =zone_movable_pfn[nid];
4072                *zone_end_pfn =min(node_end_pfn,
4073                                arch_zone_highest_possible_pfn[movable_zone]);
4074
4075        /* Adjust for ZONE_MOVABLEstarting within this range */
4076        } else if (*zone_start_pfn< zone_movable_pfn[nid] &&
4077                        *zone_end_pfn> zone_movable_pfn[nid]) {
4078                *zone_end_pfn =zone_movable_pfn[nid];
4079
4080        /* Check if this whole rangeis within ZONE_MOVABLE */
4081        } else if (*zone_start_pfn>= zone_movable_pfn[nid])
4082                *zone_start_pfn = *zone_end_pfn;
4083        }
4084 }
4068行只有在zone_movable_pfn[nid]数组中的先不为0,才考虑ZONE_MOVABLE类型区域。
4070-4073行处理的是zone_typ等于ZONE_MOVABLE的情况。ZONE_MOVABLE区域范围的求法是:在系统中有个zone_movable_pfn数组,以节点号为下标可以确定每个节点的ZONE_MOVABLE类型区域的首页帧号,另外有个变量movable_zone,用来保存一个区域类型,表示ZONE_MOVABLE类型区域的最大页帧号和movable_zone类型的最大页帧号相等。
全局变量movable_zone表示一个区域类型,表示ZONE_MOVABLE类型区域的最大页帧号和movable_zone类型的最大页帧号相等,也就数说ZONE_MOVAB区域的最大页帧号和同一节点的其他类型的一个区域的最大页帧号相等,这样如果zone_type不等于ZONE_MOVABLE,想像ZONE_MOVABLE区域从最大页帧向下扩展,则会出现三种情况:ZONE_MOVABLE在zone_typ区域内,zone_typ区域在ZONE_MOVABLE区域内,两个区域不相交。4076-4078处理的是ZONE_MOVABLE在区域zone_type内的情景,4081-4082行处理的是zone_typ区域在ZONE_MOVABLE区域内的情景。

absent_pages_in_range函数

__absent_pages_in_range函数
absent_pages_in_range函数计算区间不可用的页面数。absent_pages_in_range是调用__absent_pages_in_range来实现的, __absent_pages_in_range在mm/page_alloc.c中实现代码如下:
4121unsigned long __meminit __absent_pages_in_range(int nid,
4122                        unsigned longrange_start_pfn,
4123                        unsigned longrange_end_pfn)
4124 {
4125        unsigned long nr_absent = range_end_pfn - range_start_pfn;
4126        unsigned long start_pfn, end_pfn;
4127        int i;
4128
4129        for_each_mem_pfn_range(i, nid, &start_pfn, &end_pfn, NULL) {
4130        start_pfn = clamp(start_pfn,range_start_pfn, range_end_pfn);
4131        end_pfn = clamp(end_pfn,range_start_pfn, range_end_pfn);
4132        nr_absent -= end_pfn -start_pfn;
4133        }
4134        return nr_absent;
4135 }
用区间的总页面数减去在区间中所有空闲的页面数,就获得了区间中不可用的页面数。4125求的区间的页面数,4129-4133遍历每个在初始化分配器中的区段,减去区段在区间中的页面数。clamp函数返回三个参数的中间值。

get_pfn_range_for_nid函数

get_pfn_range_for_nid函数获得节点的页帧范围,在mm/page_alloc.c中实现,代码如下:
4011 void __meminitget_pfn_range_for_nid(unsigned int nid,
4012                unsigned long*start_pfn, unsigned long *end_pfn)
4013 {
4014        unsigned long this_start_pfn, this_end_pfn;
4015        int i;
4016
4017        *start_pfn = -1UL;
4018        *end_pfn = 0;
4019
4020        for_each_mem_pfn_range(i, nid, &this_start_pfn, &this_end_pfn,NULL) {
4021        *start_pfn = min(*start_pfn,this_start_pfn);
4022        *end_pfn = max(*end_pfn,this_end_pfn);
4023        }
4024
4025        if (*start_pfn == -1UL)
4026                *start_pfn = 0;
4027 }

get_pfn_range_for_nid函数遍历memblock分配器在节点的每个空闲区域,获得最大和最小页帧号。

zone_absent_pages_in_node函数
zone_absent_pages_in_node函数获得区域的不可用的页面数
4151 static unsigned long __meminitzone_absent_pages_in_node(int nid,
4152                                unsigned long zone_type,
4153                                        unsigned long *ignored)
4154 {
4155        unsigned long zone_low = arch_zone_lowest_possible_pfn[zone_type];
4156        unsigned long zone_high = arch_zone_highest_possible_pfn[zone_type];
4157        unsigned long node_start_pfn, node_end_pfn;
4158        unsigned long zone_start_pfn, zone_end_pfn;
4159
4160        get_pfn_range_for_nid(nid, &node_start_pfn, &node_end_pfn);
4161        zone_start_pfn = clamp(node_start_pfn, zone_low, zone_high);
4162        zone_end_pfn = clamp(node_end_pfn, zone_low, zone_high);
4163
4164        adjust_zone_range_for_zone_movable(nid, zone_type,
4165                node_start_pfn,node_end_pfn,
4166                &zone_start_pfn,&zone_end_pfn);
4167        return __absent_pages_in_range(nid, zone_start_pfn, zone_end_pfn);
4168 }
4155-4156行从arch_zone_lowest_possible_pfn数组获得区域最小页帧号,从arch_zone_highest_possible_pfn数组获得区域最大页帧号。
4160-4162行把区域范围限制到节点范围中。
4167调用__absent_pages_in_range求得区域范围中不可用的页面数。

zone_sizes_init函数

伙伴系统的初始化主要是指zone_sizes_init函数中完成的,调用zone_sizes_init函数的流程是:
start_kernel () at init/main.c:496
setup_arch () atarch/x86/kernel/setup.c:972
paging_init () at arch/x86/mm/init_32.c:700
zone_sizes_init () atarch/x86/mm/init.c:398
zone_sizes_init函数在arch/x86/mm/init.c中实现,代码如下:
397 void __init zone_sizes_init(void)
398 {
399        unsigned long max_zone_pfns[MAX_NR_ZONES];
400
401        memset(max_zone_pfns, 0, sizeof(max_zone_pfns));
402
403 #ifdef CONFIG_ZONE_DMA
404        max_zone_pfns[ZONE_DMA]=MAX_DMA_PFN;
405 #endif
406 #ifdef CONFIG_ZONE_DMA32
407        max_zone_pfns[ZONE_DMA32]       =MAX_DMA32_PFN;
408 #endif
409        max_zone_pfns[ZONE_NORMAL]      =max_low_pfn;
410 #ifdef CONFIG_HIGHMEM
411        max_zone_pfns[ZONE_HIGHMEM]     =max_pfn;
412 #endif
413
414        free_area_init_nodes(max_zone_pfns);
415 }
max_zone_pfns是一数组定义了每个区域类型范围。max_low_pfn和max_pfn在前面已经确定。

free_area_init_nodes函数

free_area_init_nodes在mm/page_alloc.c中实现,代码如下:
4734 void __initfree_area_init_nodes(unsigned long *max_zone_pfn)
4735 {
4736        unsigned long start_pfn, end_pfn;
4737        int i, nid;
4738
4739        /* Record where the zone boundaries are */
4740        memset(arch_zone_lowest_possible_pfn, 0,
4741                                sizeof(arch_zone_lowest_possible_pfn));
4742        memset(arch_zone_highest_possible_pfn, 0,
4743                                sizeof(arch_zone_highest_possible_pfn));
4744        arch_zone_lowest_possible_pfn[0] = find_min_pfn_with_active_regions();
4745        arch_zone_highest_possible_pfn[0] = max_zone_pfn[0];
4746        for (i = 1; i < MAX_NR_ZONES; i++) {
4747        if (i == ZONE_MOVABLE)
4748                continue;
4749                arch_zone_lowest_possible_pfn[i] =
4750                arch_zone_highest_possible_pfn[i-1];
4751                arch_zone_highest_possible_pfn[i] =
4752                max(max_zone_pfn[i],arch_zone_lowest_possible_pfn[i]);
4753        }
4754        arch_zone_lowest_possible_pfn[ZONE_MOVABLE] = 0;
4755        arch_zone_highest_possible_pfn[ZONE_MOVABLE] = 0;
4756
4757        /* Find the PFNs that ZONE_MOVABLE begins at in each node */
4758        memset(zone_movable_pfn, 0, sizeof(zone_movable_pfn));
4759        find_zone_movable_pfns_for_nodes();
4760
4761        /* Print out the zone ranges */
4762        printk("Zone PFN ranges:\n");
4763        for (i = 0; i < MAX_NR_ZONES; i++) {
4764        if (i == ZONE_MOVABLE)
4765                continue;
4766        printk("  %-8s ", zone_names[i]);
4767        if(arch_zone_lowest_possible_pfn[i] ==
4768                                arch_zone_highest_possible_pfn[i])
4769                        printk("empty\n");
4770        else
4771                        printk("%0#10lx ->%0#10lx\n",
4772                                arch_zone_lowest_possible_pfn[i],
4773                                arch_zone_highest_possible_pfn[i]);
4774        }
4775
4776        /* Print out the PFNs ZONE_MOVABLE begins at in each node */
4777        printk("Movable zone start PFN for each node\n");
4778        for (i = 0; i < MAX_NUMNODES; i++) {
4779        if (zone_movable_pfn[i])
4780                printk("  Node %d: %lu\n", i, zone_movable_pfn[i]);
4781        }
4782
4783        /* Print out the early_node_map[] */
4784        printk("Early memory PFN ranges\n");
4785        for_each_mem_pfn_range(i, MAX_NUMNODES, &start_pfn, &end_pfn,&nid)
4786        printk("  %3d: %0#10lx -> %0#10lx\n", nid,start_pfn, end_pfn);
4787
4788        /* Initialise every node */
4789        mminit_verify_pageflags_layout();
4790        setup_nr_node_ids();
4791        for_each_online_node(nid) {
4792        pg_data_t *pgdat = NODE_DATA(nid);
4793        free_area_init_node(nid, NULL,
4794                                find_min_pfn_for_node(nid), NULL);
4795
4796        /* Any memory on that node */
4797        if(pgdat->node_present_pages)
4798                        node_set_state(nid,N_HIGH_MEMORY);
4799                check_for_regular_memory(pgdat);
4800        }
4801 }

free_area_init_nodes的代码比较长,但主要作了两项工作,确定节点的每个区域的上下界,然后对每个节点初始化。

除ZONE_MOVABLE区域类型外,区域范围的确定方法是用两个数组,arch_zone_lowest_possible_pfn确定区域的最小页帧号,arch_zone_highest_possible_pfn确定区域的最大页帧号,一个区域的页帧号pfn所允许的范围是arch_zone_lowest_possible_pfn<= pfn<arch_zone_highest_possible_pfn[zone_type]。ZONE_MOVABLE的区域的范围的确定方法涉及到一个数组zone_movable_pfn和一个变量movable_zone,对一个节点号为nid的节点,ZONE_MOVABLE的页帧号pfn的区域是:zone_movable_pfn[nid]<= pfn < arch_zone_highest_possible_pfn[movable_zone]。
4740-4753行求得从区域除ZONE_MOVABLE区域类型外的区域范围,从区域范围的求法可以知道,区域在节点中是依次连续的。
4754-4759行求ZONE_MOVABLE区域的范围。其中关键是find_zone_movable_pfns_for_nodes函数,分析本函数后分析find_zone_movable_pfns_for_nodes函数。
4762-4786行打印区域范围信息。
4789行mminit_verify_pageflags_layout函数验证位码信息并输出一些调式信息。
4790行调用setup_nr_node_ids函数设置节点总数。保存在变量nr_node_ids中。
4791-4799一个循环,变量每个节点,4793行调用free_area_init_node函数对每个节点初始化,free_area_init_node函数中后面进行分析。4797-4799行主要是设置一些节点是否内存的状态信息。系统定义了一个枚举变量enum node_states,用来记录一个节点是否能用(N_POSSIBLE),是否在线(N_ONLINE),是否具有普通内存区域(N_NORMAL_MEMORY),是否有普通内存或高端内存内存(N_HIGH_MEMORY),是否有连接有cpu(N_CPU)。mm/page_alloc.c中有个节点掩码数组node_states[]对enum node_states的每项都有个节点掩码,来记录节点的状态信息。
find_zone_movable_pfns_for_nodes函数
find_zone_movable_pfns_for_nodes的工作是确定ZONE_MOVABLE区域的范围。在mm/page_alloc.c中实现,代码如下:
4567 static void __initfind_zone_movable_pfns_for_nodes(void)
4568 {
4569        int i, nid;
4570        unsigned long usable_startpfn;
4571        unsigned long kernelcore_node, kernelcore_remaining;
4572        /* save the state before borrow the nodemask */
4573        nodemask_t saved_node_state = node_states[N_HIGH_MEMORY];
4574        unsigned long totalpages = early_calculate_totalpages();
4575        int usable_nodes = nodes_weight(node_states[N_HIGH_MEMORY]);
4576
4577        /*
4578 * If movablecore was specified,calculate what size of
4579 * kernelcore that corresponds so thatmemory usable for
4580 * any allocation type is evenly spread.If both kernelcore
4581 * and movablecore are specified, thenthe value of kernelcore
4582 * will be used forrequired_kernelcore if it's greater than
4583 * what movablecore would haveallowed.
4584 */
4585        if (required_movablecore) {
4586        unsigned long corepages;
4587
4588        /*
4589         * Round-up so thatZONE_MOVABLE is at least as large as what
4590         * was requested by the user
4591         */
4592        required_movablecore =
4593                        roundup(required_movablecore, MAX_ORDER_NR_PAGES);
4594        corepages = totalpages -required_movablecore;
4595
4596        required_kernelcore = max(required_kernelcore,corepages);
4597        }
4598
4599        /* If kernelcore was not specified, there is no ZONE_MOVABLE */
4600        if (!required_kernelcore)
4601        goto out;
4602
4603        /* usable_startpfn is the lowest possible pfn ZONE_MOVABLE can be at */
4604        find_usable_zone_for_movable();
4605        usable_startpfn = arch_zone_lowest_possible_pfn[movable_zone];
4606
4607 restart:
4608        /* Spread kernelcore memory as evenly as possible throughout nodes */
4609        kernelcore_node = required_kernelcore / usable_nodes;
4610        for_each_node_state(nid, N_HIGH_MEMORY) {
4611        unsigned long start_pfn,end_pfn;
4612
4613        /*
4614         * Recalculate kernelcore_nodeif the division per node
4615         * now exceeds what isnecessary to satisfy the requested
4616         * amount of memory for thekernel
4617         */
4618        if (required_kernelcore <kernelcore_node)
4619                kernelcore_node =required_kernelcore / usable_nodes;
4620
4621        /*
4622         * As the map is walked, wetrack how much memory is usable
4623         * by the kernel usingkernelcore_remaining. When it is
4624         * 0, the rest of the node isusable by ZONE_MOVABLE
4625         */
4626        kernelcore_remaining =kernelcore_node;
4627
4628        /* Go through each range ofPFNs within this node */
4629                 for_each_mem_pfn_range(i, nid,&start_pfn, &end_pfn, NULL) {
4630                unsigned longsize_pages;
4631
4632                start_pfn =max(start_pfn, zone_movable_pfn[nid]);
4633                if (start_pfn >=end_pfn)
4634                        continue;
4635
4636                /* Account for what isonly usable for kernelcore */
4637                if (start_pfn <usable_startpfn) {
4638                        unsigned longkernel_pages;
4639                        kernel_pages =min(end_pfn, usable_startpfn)
4640                                                                - start_pfn;
4641
4642                                kernelcore_remaining -= min(kernel_pages,
4643                                                        kernelcore_remaining);
4644                                required_kernelcore -= min(kernel_pages,
4645                                                        required_kernelcore);
4646
4647                        /* Continue ifrange is now fully accounted */
4648                        if (end_pfn<= usable_startpfn) {
4649
4650                                /*
4651                                 * Push zone_movable_pfn to the endso
4652                                 *that if we have to rebalance
4653                                 *kernelcore across nodes, we will
4654                                 * notdouble account here
4655                                 */
4656                                        zone_movable_pfn[nid] = end_pfn;
4657                                        continue;
4658                        }
4659                                start_pfn =usable_startpfn;
4660                }
4661
4662                /*
4663                 * The usable PFNrange for ZONE_MOVABLE is from
4664                 *start_pfn->end_pfn. Calculate size_pages as the
4665                 * number of pagesused as kernelcore
4666                 */
4667                size_pages = end_pfn -start_pfn;
4668                if (size_pages >kernelcore_remaining)
4669                        size_pages =kernelcore_remaining;
4670                zone_movable_pfn[nid]= start_pfn + size_pages;
4671
4672                /*
4673                 * Some kernelcore hasbeen met, update counts and
4674                 * break if thekernelcore for this node has been
4675                 * satisified
4676                 */
4677                required_kernelcore -=min(required_kernelcore,
4678                                                                size_pages);
4679                kernelcore_remaining-= size_pages;
4680                if(!kernelcore_remaining)
4681                        break;
4682        }
4683        }
4684
4685        /*
4686 * If there is stillrequired_kernelcore, we do another pass with one
4687 * less node in the count. This willpush zone_movable_pfn[nid] further
4688 * along on the nodes that still havememory until kernelcore is
4689 * satisified
4690 */
4691        usable_nodes--;
4692        if (usable_nodes && required_kernelcore > usable_nodes)
4693        goto restart;
4694
4695        /* Align start of ZONE_MOVABLE on all nids to MAX_ORDER_NR_PAGES */
4696        for (nid = 0; nid < MAX_NUMNODES; nid++)
4697        zone_movable_pfn[nid] =
4698                        roundup(zone_movable_pfn[nid], MAX_ORDER_NR_PAGES);
4699
4700 out:
4701        /* restore the node_state */
4702        node_states[N_HIGH_MEMORY] = saved_node_state;
4703 }
这个函数的目的是计算zone_movable_pfn数组。在系统中有两个变量required_movablecore和required_kernelcore,这两个变量的值是通过命令行传进来的,变量required_movablecore通知内核保留给ZONE_MOVABLE区域的页面数,required_kernelcore是需要保留的非ZONE_MOVABLE区域的页面数。
4585-4601行,由4585行和4600行知道,如果这两个数据都没有通过命令行设置,则直接跳到out标号,也就是ZONE_MOVABLE区域为空。corepages变量由early_calculate_totalpages初始化,是空闲内存的总数,roundup(x, y)是一个宏,返回大于等于x的是y的倍数的第一个数。4592-4593行设置required_movablecore是MAX_ORDER_NR_PAGES的倍数,4596行如果设置为指定的required_kernelcore和剩余的空闲的区域required_movablecore页后的页面,其实也就是让页面优先用做非ZONE_MOVABLE区域的页面数。
在4602行的后面required_movablecore变量没有再出现,后面的代码主要做了两部分工作,先选定一个区域,选的方法是从高到低的第一个不空的非ZONE_MOVABLE区域,然后在这个区域的低端往上收缩,保证非ZONE_MOVABLE区域的页面数达到required_kernelcore。
4604行调用函数find_usable_zone_for_movable设置变量movable_zone,movable_zone被设置的值就是最高不空的非ZONE_MOVABLE区域。
4605行设置usable_startpfn变量的值,usable_startpfn也就是第一个能作为ZONE_MOVABL区域的页帧的值。
4609行设置kernelcore_node变量的值,usable_nodes一个商数,初始化为是具有ZONE_MOVABLE区域的节点数,在第一次扫描中kernelcore_node初始化为对每个节点是均匀保留非ZONE_MOVABLE区域页面的,以后每次扫描会自减usable_node。在计算zone_movable_pfn数组时,会对一个节点集合遍历,kernelcore_node变量是每个节点应该保留给非ZONE_MOVABLE区域的页面数。        
4610行对在节点掩码node_states[N_HIGH_MEMORY]中可用的每个节点进行扫描。
4618-4619行如果required_kernelcore < kernelcore_node重新设置kernelcore_node变量的值
4626行kernelcore_remaining变量是在本次对节点的扫描要变量的页面数,赋值为required_kernelcore。
4629行对每个初始化内存分配器中的空闲区域进行遍历。
4632-4634行zone_movable_pfn[nid]是本次扫描节点ZONE_MOVABLE区域的最小页帧号,如果end_pfn <=zone_movable_pfn[nid]或者end_pfn <=start_pfn就是本次扫描的空闲区段不再ZONE_MOVABLE区域范围内或者是空区段,继续扫描下一个区段。
4637行,start_pfn是本次扫描的空闲区段的首页帧,usable_startpfn是ZONE_MOVABLE区域锁允许的最小帧。start_pfn< usable_startpfn意味着start_pfn -->usable_startpfn的帧是属于非ZONE_MOVABLE区域的。4638-4645在所要保留的页面数中减去这段包含的页面。
4648行end_pfn <= usable_startpfn表示正空闲区段都属于非ZONE_MOVABLE区域。4656行zone_movable_pfn[nid] = end_pfn,如果保留给非ZONE_MOVABLE区域的区域已经足够,用本次扫描的空闲区段尾做本节点的ZONE_MOVABLE区域首页帧号。注意一点区段是包含首页帧号start_pfn,不包含尾帧end_pfn。
代码执行到4659行表示end_pfn >usable_startpfn,执行start_pfn = usable_startpfn把usable_startpfnàend_pfn当成一个空闲区域执行后面的代码。
4667-4681行,执行到这段代码,表示整个区段都在都是可以作为ZONE_MOVABLE页面,这段代码中这个空闲区段中保留非ZONE_MOVABLE区域页面。
4691-4693行自减商数usable_nodes,并测试usable_nodes&& required_kernelcore > usable_nodes,这样可以比较无限循环,并在每个节点需要保留的非ZONE_MOVABLE区域页的数量大于1时,重新扫描。
4696-4698行对齐ZONE_MOVABLE区域的首页帧。
4702恢复node_state数组。

free_area_init_node函数

free_area_init_node函数初始化节点,在mm/page_alloc.c中实现,代码如下:
4420 void __paginginitfree_area_init_node(int nid, unsigned long *zones_size,
4421        unsigned long node_start_pfn,unsigned long *zholes_size)
4422 {
4423        pg_data_t *pgdat = NODE_DATA(nid);
4424
4425        pgdat->node_id = nid;
4426        pgdat->node_start_pfn = node_start_pfn;
4427        calculate_node_totalpages(pgdat, zones_size, zholes_size);
4428
4429        alloc_node_mem_map(pgdat);
4430 #ifdef CONFIG_FLAT_NODE_MEM_MAP
4431        printk(KERN_DEBUG "free_area_init_node: node %d, pgdat %08lx,node_mem_map %08lx\n",
4432        nid, (unsigned long)pgdat,
4433        (unsignedlong)pgdat->node_mem_map);
4434 #endif
4435
4436        free_area_init_core(pgdat, zones_size, zholes_size);
4437 }
free_area_init_node函数调用calculate_node_totalpages对节点长度和节点总可用页面数进行初始化。calculate_node_totalpages函数是通过调用zone_spanned_pages_in_node和
zone_absent_pages_in_node函数实现的,这两个函数上面已经分析过。
alloc_node_mem_map是对节点的page管理数据初始化。其他的初始化工作在free_area_init_core函数中完成。
alloc_node_mem_map函数
alloc_node_mem_map函数分配节点的page管理数组的内存,在mm/page_alloc.c中实现,代码如下:
4379 static void __init_refokalloc_node_mem_map(struct pglist_data *pgdat)
4380 {
4381        /* Skip empty nodes */
4382        if (!pgdat->node_spanned_pages)
4383        return;
4384
4385 #ifdef CONFIG_FLAT_NODE_MEM_MAP
4386        /* ia64 gets its own node_mem_map, before this, without bootmem */
4387        if (!pgdat->node_mem_map) {
4388        unsigned long size, start,end;
4389        struct page *map;
4390
4391        /*
4392         * The zone's endpoints aren'trequired to be MAX_ORDER
4393         * aligned but thenode_mem_map endpoints must be in order
4394                 * for the buddyallocator to function correctly.
4395         */
4396        start =pgdat->node_start_pfn & ~(MAX_ORDER_NR_PAGES - 1);
4397        end = pgdat->node_start_pfn+ pgdat->node_spanned_pages;
4398                end = ALIGN(end,MAX_ORDER_NR_PAGES);
4399        size =  (end - start) * sizeof(struct page);
4400        map =alloc_remap(pgdat->node_id, size);
4401        if (!map)
4402                map = alloc_bootmem_node_nopanic(pgdat,size);
4403        pgdat->node_mem_map = map +(pgdat->node_start_pfn - start);
4404        }
4405 #ifndef CONFIG_NEED_MULTIPLE_NODES
4406        /*
4407 * With no DISCONTIG, the globalmem_map is just set as node 0's
4408 */
4409        if (pgdat == NODE_DATA(0)) {
4410        mem_map =NODE_DATA(0)->node_mem_map;
4411 #ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
4412        if (page_to_pfn(mem_map) !=pgdat->node_start_pfn)
4413                mem_map -= (pgdat->node_start_pfn -ARCH_PFN_OFFSET);
4414 #endif /*CONFIG_HAVE_MEMBLOCK_NODE_MAP */
4415        }
4416 #endif
4417 #endif /* CONFIG_FLAT_NODE_MEM_MAP */
4418 }
在节点结构pglist_data中,成员node_start_pfn是节点的首页帧号,node_spanned_pages是包含中间不可用页面的节点的长度。node_mem_map指向节点page结构管理数组,并且指向节点首页的page结构。
4388-4403行的代码执行逻辑是:计算一个页帧范围,这个范围是包含节点的所有页面的最小范围,并且起始页帧和尾页帧都是按最大块对齐的。然后按这个范围来分配存放page结构数组的内存。分配完后(4403行)让node_mem_map成员指向node_start_pfn页帧的page结构地址。
对page数组的内存是调用alloc_remap和alloc_bootmem_node_nopanic进行分配的,这两个函数中初始化内存分频器章节中介绍。
4410行,在较早的版本,page管理数组的首地址是存放在变量mem_map中的,现在这个变量指向第零个节点的page管理数组
4412-4413行对page管理结构地址到页帧的转换进行校正。

free_area_init_core函数

free_area_init_core是伙伴系统初始化的核心函数,在mm/page_alloc.c中实现,代码如下:
4291 static void __paginginitfree_area_init_core(struct pglist_data *pgdat,
4292        unsigned long *zones_size,unsigned long *zholes_size)
4293 {
4294        enum zone_type j;
4295        int nid = pgdat->node_id;
4296        unsigned long zone_start_pfn = pgdat->node_start_pfn;
4297        int ret;
4298
4299        pgdat_resize_init(pgdat);
4300        pgdat->nr_zones = 0;
4301        init_waitqueue_head(&pgdat->kswapd_wait);
4302        pgdat->kswapd_max_order = 0;
4303        pgdat_page_cgroup_init(pgdat);
4304
4305        for (j = 0; j < MAX_NR_ZONES; j++) {
4306        struct zone *zone =pgdat->node_zones + j;
4307        unsigned long size, realsize,memmap_pages;
4308        enum lru_list lru;
4309
4310        size = zone_spanned_pages_in_node(nid,j, zones_size);
4311        realsize = size -zone_absent_pages_in_node(nid, j,
4312                                                                zholes_size);
4313
4314        /*
4315         * Adjust realsize so that itaccounts for how much memory
4316         * is used by this zone formemmap. This affects the watermark
4317         * and per-cpu initialisations
4318         */
4319        memmap_pages =
4320                PAGE_ALIGN(size * sizeof(structpage)) >> PAGE_SHIFT;
4321        if (realsize >=memmap_pages) {
4322                realsize -=memmap_pages;
4323                if (memmap_pages)
4324                        printk(KERN_DEBUG
4325                                       "  %s zone: %lu pages usedfor memmap\n",
4326                                       zone_names[j], memmap_pages);
4327        } else
4328                printk(KERN_WARNING
4329                        "  %s zone: %lu pages exceeds realsize%lu\n",
4330                        zone_names[j],memmap_pages, realsize);
4331
4332        /* Account for reserved pages*/
4333        if (j == 0 && realsize> dma_reserve) {
4334                realsize -=dma_reserve;
4335                printk(KERN_DEBUG"  %s zone: %lu pagesreserved\n",
4336                                        zone_names[0], dma_reserve);
4337        }
4338
4339        if (!is_highmem_idx(j))
4340                nr_kernel_pages +=realsize;
4341        nr_all_pages += realsize;
4342
4343        zone->spanned_pages = size;
4344        zone->present_pages = realsize;
4345 #ifdef CONFIG_NUMA
4346        zone->node = nid;
4347        zone->min_unmapped_pages =(realsize*sysctl_min_unmapped_ratio)
4348                                                / 100;
4349        zone->min_slab_pages =(realsize * sysctl_min_slab_ratio) / 100;
4350 #endif
4351        zone->name = zone_names[j];
4352                spin_lock_init(&zone->lock);
4353                spin_lock_init(&zone->lru_lock);
4354        zone_seqlock_init(zone);
4355        zone->zone_pgdat = pgdat;
4356
4357        zone_pcp_init(zone);
4358        for_each_lru(lru)
4359                        INIT_LIST_HEAD(&zone->lruvec.lists[lru]);
4360        zone->reclaim_stat.recent_rotated[0]= 0;
4361                zone->reclaim_stat.recent_rotated[1] = 0;
4362                zone->reclaim_stat.recent_scanned[0] = 0;
4363                zone->reclaim_stat.recent_scanned[1] = 0;
4364        zap_zone_vm_stats(zone);
4365                zone->flags = 0;
4366        if (!size)
4367                continue;
4368
4369                set_pageblock_order(pageblock_default_order());
4370        setup_usemap(pgdat, zone,size);
4371        ret =init_currently_empty_zone(zone, zone_start_pfn,
4372                                                size, MEMMAP_EARLY);
4373        BUG_ON(ret);
4374        memmap_init(size, nid, j,zone_start_pfn);
4375        zone_start_pfn += size;
4376        }
4377 }
这个函数的代码比较长,但比较简单,就一些变量,锁和链表的初始化。对这个函数本身就不做分析了,而对函数中调用的memmap_init做些介绍,memmap_init是一个宏定义如下:
#define memmap_init(size, nid, zone,start_pfn) \
memmap_init_zone((size),(nid), (zone), (start_pfn), MEMMAP_EARLY)。
是对memmap_init_zone函数的调用。

memmap_init_zone函数

memmap_init_zone对一个区域的page管理结构的初始化,在mm/page_alloc.c中实现,代码如下:
3619 * done. Non-atomic initialization, single-pass.
3620 */
3621 void __meminitmemmap_init_zone(unsigned long size, int nid, unsigned long zone,
3622                unsigned long start_pfn,enum memmap_context context)
3623 {
3624        struct page *page;
3625        unsigned long end_pfn = start_pfn + size;
3626        unsigned long pfn;
3627        struct zone *z;
3628
3629        if (highest_memmap_pfn < end_pfn - 1)
3630        highest_memmap_pfn = end_pfn -1;
3631
3632        z = &NODE_DATA(nid)->node_zones[zone];
3633        for (pfn = start_pfn; pfn < end_pfn; pfn++) {
3634        /*
3635         * There can be holes inboot-time mem_map[]s
3636         * handed to thisfunction.  They do not
3637         * exist on hotplugged memory.
3638         */
3639        if (context == MEMMAP_EARLY) {
3640                if (!early_pfn_valid(pfn))
3641                        continue;
3642                if(!early_pfn_in_nid(pfn, nid))
3643                        continue;
3644        }
3645        page = pfn_to_page(pfn);
3646        set_page_links(page, zone, nid, pfn);
3647        mminit_verify_page_links(page,zone, nid, pfn);
3648        init_page_count(page);
3649        reset_page_mapcount(page);
3650        SetPageReserved(page);
3651                /*
3652         * Mark the block movable sothat blocks are reserved for
3653         * movable at startup. Thiswill force kernel allocations
3654         * to reserve their blocksrather than leaking throughout
3655         * the address space duringboot when many long-lived
3656         * kernel allocations aremade. Later some blocks near
3657         * the start are markedMIGRATE_RESERVE by
3658         * setup_zone_migrate_reserve()
3659         *
3660         * bitmap is created forzone's valid pfn range. but memmap
3661         * can be created for invalidpages (for alignment)
3662         * check here not to callset_pageblock_migratetype() against
3663         * pfn out of zone.
3664         */
3665        if ((z->zone_start_pfn<= pfn)
3666            && (pfn <z->zone_start_pfn + z->spanned_pages)
3667            && !(pfn &(pageblock_nr_pages - 1)))
3668                        set_pageblock_migratetype(page, MIGRATE_MOVABLE);
3669
3670                INIT_LIST_HEAD(&page->lru);
3671 #ifdef WANT_PAGE_VIRTUAL
3672        /* The shift won't overflowbecause ZONE_NORMAL is below 4G. */
3673        if (!is_highmem_idx(zone))
3674                set_page_address(page,__va(pfn << PAGE_SHIFT));
3675 #endif
3676        }
3677 }
3629-3630行highest_memmap_pfn是存在page管理结构的最大的页帧号,如果本管理区的最大的存在page管理结构的最大的页帧号大于highest_memmap_pfn,就需要更新highest_memmap_pfn。
3632行获得区域结构地址。
3633对区域的所有页帧进行遍历。
3640-3641行检查页帧号是否合法,也就是要小于系统最大的页帧号,大于系统允许的最小的页帧。
3642-3643行检查页帧pfn是否属于节点nid。
3645行获得pfn帧的page管理结构地址。
3646行调用set_page_links函数设置页面的一些链接,主要包含页面所在节点,页面的区域类型,页面所在段。这样信息都是保存在page结构的成员flags中,每种信息占用一些位。3647行对设置的页面所在节点,页面的区域类型,页面所在段的信息进行验证,如果有错误输出一些调试信息。
3648初始引用数信息,3649初始化映射数信息。
3665-3668行,对每个最大块的首帧,调用set_pageblock_migratetype函数设置迁移类型信息,set_pageblock_migratetype函数在伙伴系统的内存迁移一节有分析。
3647行设置页面映射的虚拟地址。
====区域列表的初始化

build_all_zonelists函数

区域列表的初始化由函数build_all_zonelists来完成,build_all_zonelists函数的进入路径是:
start_kernel() at init/main.c:504
build_all_zonelists() at mm/page_alloc.c:3409
build_all_zonelists在mm/page_alloc.c中实现,代码如下:
3408 void __refbuild_all_zonelists(void *data)
3409 {
3410set_zonelist_order();
3411
3412if (system_state == SYSTEM_BOOTING) {
3413        __build_all_zonelists(NULL);
3414        mminit_verify_zonelist();
3415                cpuset_init_current_mems_allowed();
3416} else {
3417        /* we have to stop all cpus toguarantee there is no user
3418           of zonelist */
3419 #ifdefCONFIG_MEMORY_HOTPLUG
3420        if (data)
3421                        setup_zone_pageset((struct zone *)data);
3422 #endif
3423                stop_machine(__build_all_zonelists, NULL, NULL);
3424        /* cpuset refresh routineshould be here */
3425}
3426vm_total_pages =nr_free_pagecache_pages();
3427/*
3428 * Disable grouping by mobility if thenumber of pages in the
3429 * system is too low to allow themechanism to work. It would be
3430 * more accurate, but expensive tocheck per-zone. This check is
3431 * made on memory-hotadd so a system canstart with mobility
3432 * disabled and enable it later
3433 */
3434if (vm_total_pages <(pageblock_nr_pages * MIGRATE_TYPES))
3435                page_group_by_mobility_disabled = 1;
3436else
3437        page_group_by_mobility_disabled= 0;
3438
3439printk("Built %i zonelists in %sorder, mobility grouping %s.  "
3440        "Total pages:%ld\n",
3441                nr_online_nodes,
3442                zonelist_order_name[current_zonelist_order],
3443                        page_group_by_mobility_disabled ? "off" : "on",
3444                vm_total_pages);
3445 #ifdefCONFIG_NUMA
3446printk("Policy zone: %s\n",zone_names[policy_zone]);
3447 #endif
3448 }
在初始化过程中,函数会进入3413-3415行代码运行。
3413行区域列表的初始的主体工作是在__build_all_zonelists中完成的。介绍完本函数后介绍__build_all_zonelists函数。
3414行调用mminit_verify_zonelist函数做一些验证工作。
在伙伴系统的内存分配一节中,我们把伙伴系统内存分为三个阶段,而第一阶段的主要任务是确定区域列表和节点掩码。在进程结构中有个成员mems_allowed,是一个节点掩码,表示进程所允许分配内存的节点,只有一个节点包含在进程的mems_allowed中,并且在内存策略也允许在这个节点进行分配时才会到这个节点进行内存分配。cpuset_init_current_mems_allowed设置进程的mems_allowed成员包含所有节点。
3626行,nr_free_pagecache_pages返回的是对所有区域可用页面数减去高水位线后的的剩余页面数相加的值,这个值作为剩余可用页面数。
3434行,如果剩余可用页面小于pageblock_nr_pages * MIGRATE_TYPES,也就是说如果不能满足每个迁移类型都包含一个迁移块。则禁用迁移类型,禁用迁移类型后所有页面的迁移都会迁移到MIGRATE_UNMOVABLE迁移类型,也就是不可迁移类型。

__build_all_zonelists函数

__build_all_zonelists在mm/page_alloc.c中实现,代码如下:
3356 static__init_refok int __build_all_zonelists(void *data)
3357 {
3358int nid;
3359int cpu;
3360
3361 #ifdefCONFIG_NUMA
3362memset(node_load, 0,sizeof(node_load));
3363 #endif
3364for_each_online_node(nid) {
3365        pg_data_t *pgdat =NODE_DATA(nid);
3366
3367        build_zonelists(pgdat);
3368                 build_zonelist_cache(pgdat);
3369}
3370
3371/*
3372 * Initialize the boot_pagesets thatare going to be used
3373 * for bootstrapping processors. Thereal pagesets for
3374 * each zone will be allocated laterwhen the per cpu
3375 * allocator is available.
3376 *
3377 * boot_pagesets are used also forbootstrapping offline
3378 * cpus if the system is alreadybooted because the pagesets
3379 * are needed to initialize allocatorson a specific cpu too.
3380 * F.e. the percpu allocator needs thepage allocator which
3381 * needs the percpu allocator in orderto allocate its pagesets
3382 * (a chicken-egg dilemma).
3383 */
3384for_each_possible_cpu(cpu) {
3385                setup_pageset(&per_cpu(boot_pageset, cpu), 0);
3386
3387 #ifdefCONFIG_HAVE_MEMORYLESS_NODES
3388        /*
3389         * We now know the "localmemory node" for each node--
3390         * i.e., the node of the firstzone in the generic zonelist.
3391         * Set up numa_mem percpuvariable for on-line cpus.  During
3392         * boot, only the boot cpushould be on-line;  we'll init the
3393                  * secondary cpus' numa_mem as theycome on-line.  During
3394         * node/memory hotplug, we'llfixup all on-line cpus.
3395         */
3396        if (cpu_online(cpu))
3397                set_cpu_numa_mem(cpu,local_memory_node(cpu_to_node(cpu)));
3398 #endif
3399}
3400
3401return 0;
3402 }
区域列表是区域的有序集合,设置区域列表的目的是为了从列表中选择一个区域,在区域中进行内存分配。
有几个因素会影响区域的选择:
1:一个是区域在区域列表中的顺序。
2:还有一个是分配标志位指定的最大区域类型,一些分配只能在低端内存中分配,如一些只支持低端内存访问的设备驱动程序。当选择一个区域时,要考虑区域的类型,只有区域类型小于等于标志位指定的最大区域类型,才选择这个区域。
3:在分配的时候,如果快速通道分配内存失败,在慢速通道中会记录区域内存不充足缓存信息,在内存的时候会检查内存内存是否充足的缓存信息,这会影响区域的选择。
4: 节点掩码也会影响区域的选择,只会选择在节点掩码集合中的区域。
考虑这几个因素,我们就可以解释区域列表的结构zonelist的定义了,为什么在列表中定义一个zoneref数组,而不直接定义一个zone的数组指针?zoneref结构包含一个zone结构指针zone和zone_idx是区域的类型,考虑第二个因素,在我们扫描区域列表的一项,需要的区域类型直接可以从zoneref成员的zone_idx得到。
而zonelist的成员zlcache是个zonelist_cache结构。用来保存区域的内存是否充足信息,对区域列表中的每个区域,zonelist_cache结构的成员fullzones,是个位图数组,和zonelist结构的zoneref数组是对应的,用来表示zoneref数组索引的项内存是否充足,z_to_n用来实现从数组索引到节点号的转换,在zlc_zone_worth_trying函数中会用到这些参数。
zonelist的成员zlcache_ptr指向实际可用的zonelist_cache结构地址,zlcache_ptr不总是指向zonelist的zonelist_cache。
3364-3369行,遍历所有在线的节点,调用函数build_zonelists初始化节点的区域列表,每个节点包含若干个区域列表。调用build_zonelist_cache初始化节点的内存是否充足缓存信息。
3384-3399行,编译所有可用的cpu,调用setup_pageset初始化每cpu页缓存信息。3396-3397行对在线的cpu,调用set_cpu_numa_mem设置cpu所在节点。
在后面只介绍build_zonelists的指向流程,build_zonelist_cache和其他部分不分析了。

build_zonelists函数

build_zonelists初始化一个节点的区域列表,在mm/page_alloc.c中实现,代码如下:
3286 static void build_zonelists(pg_data_t*pgdat)
3287 {
3288        int node, local_node;
3289        enum zone_type j;
3290        struct zonelist *zonelist;
3291
3292        local_node =pgdat->node_id;
3293
3294        zonelist = &pgdat->node_zonelists[0];
3295        j = build_zonelists_node(pgdat, zonelist, 0, MAX_NR_ZONES - 1);
3296
3297        /*
3298 * Now we build the zonelist so thatit contains the zones
3299 * of all the other nodes.
3300 * We don't want to pressure aparticular node, so when
3301 * building the zones for node N, wemake sure that the
3302 * zones coming right after the localones are those from
3303 * node N+1 (modulo N)
3304 */
3305        for (node = local_node + 1; node < MAX_NUMNODES; node++) {
3306        if (!node_online(node))
3307                continue;
3308        j = build_zonelists_node(NODE_DATA(node),zonelist, j,
3309                                                        MAX_NR_ZONES - 1);
3310        }
3311        for (node = 0; node < local_node; node++) {
3312        if (!node_online(node))
3313                continue;
3314        j =build_zonelists_node(NODE_DATA(node), zonelist, j,
3315                                                        MAX_NR_ZONES - 1);
3316        }
3317
3318        zonelist->_zonerefs[j].zone = NULL;
3319        zonelist->_zonerefs[j].zone_idx = 0;
3320 }
build_zonelists_node函数把一个包含的区域编译到区域列表。
这个函数的重点是区域列表初始化的顺序,local_node是本节点的号码,从3295,3305,3311行我们可以知道,对在线的节点点,对节点的初始化顺序是local_node, local_node+1,…,MAX_NR_ZONES – 1,0,…, local_node-1。
3318-3319我们知道对最后一个区域索引项,索引的是空区域,而前面的每个区域索引项都指向非空区域,这样我们可以判断区域列表的结束。

build_zonelists_node函数

build_zonelists_node把一个节点的区域编译到区域列表,把节点pgdat中类型小于等于zone_type的区域以nr_zones项开始编译到区域列表zonelist。build_zonelists_node函数在mm/page_alloc.c中实现,代码如下:
2860 static intbuild_zonelists_node(pg_data_t *pgdat, struct zonelist *zonelist,
2861                        int nr_zones,enum zone_type zone_type)
2862 {
2863        struct zone *zone;
2864
2865        BUG_ON(zone_type >= MAX_NR_ZONES);
2866        zone_type++;
2867
2868        do {
2869        zone_type--;
2870        zone = pgdat->node_zones +zone_type;
2871        if (populated_zone(zone)) {
2872                zoneref_set_zone(zone,
2873                                &zonelist->_zonerefs[nr_zones++]);
2874                        check_highest_zone(zone_type);
2875        }
2876
2877        } while (zone_type);
2878        return nr_zones;
2879 }
区域被编译的顺序和区域类型是一致的,populated_zone是判断区域是否具有可用页面,有可用页返回真,否则返回假。check_highest_zone更新policy_zone变量,policy_zone变量保存在系统中能用的非ZONE_MOVABLE的最大的区域类型。

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 ancjf@163.com

文章标题:linux 源代码分析5:伙伴系统初始化

本文作者:ancjf

发布时间:2013-05-22, 21:59:15

最后更新:2020-04-18, 15:57:38

原始链接:http://ancjf.com/2013/05/22/linux-3-4-10-%E5%86%85%E6%A0%B8%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%905-%E4%BC%99%E4%BC%B4%E7%B3%BB%E7%BB%9F%E5%88%9D%E5%A7%8B%E5%8C%96/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏