咱们以前讲了在memblock完成以后, 内存初始化开始进入第二阶段, 第二阶段是一个漫长的过程, 它执行了一系列复杂的操做, 从体系结构相关信息的初始化慢慢向上层展开, 其主要执行了以下操做node
特定于体系结构的设置linux
在完成了基础的内存结点和内存域的初始化工做之后, 咱们必须克服一些硬件的特殊设置算法
在arm64架构下, 内核在start_kernel()
->setup_arch()中经过arm64_memblock_init( )完成了memblock的初始化以后, 接着经过setup_arch()->paging_init()开始初始化分页机制c#
paging_init负责创建只能用于内核的页表, 用户空间是没法访问的. 这对管理普通应用程序和内核访问内存的方式,有深远的影响数组
bootmem_init 始化内存数据结构包括内存节点, 内存域和页帧page | |---->arm64_numa_init(); | 支持numa架构 | |---->zone_sizes_init(min, max); 来初始化节点和管理区的一些数据项 | |---->free_area_init_node | 初始化内存节点 | |---->free_area_init_core | 初始化zone | |---->memmap_init | 初始化page页面 | |---->memblock_dump_all(); | 初始化完成, 显示memblock的保留的全部内存信息
至此,bootmem_init已经完成了节点和管理区的关键数据已完成初始化, 内核在后面为内存管理作得一个准备工做就是将全部节点的管理区都链入到zonelist中,便于后面内存分配工做的进行.缓存
内核在start_kernel()–>build_all_zonelist()中完成zonelist的初始化数据结构
内核setup_arch的最后经过bootmem_init中完成了内存数据结构的初始化(包括内存结点pg_data_t, 内存管理域zone和页面信息page), 数据结构已经基本准备好了, 在后面为内存管理作得一个准备工做就是将全部节点的管理区都链入到zonelist中, 便于后面内存分配工做的进行.架构
asmlinkage __visible void __init start_kernel(void) { setup_arch(&command_line); build_all_zonelists(NULL, NULL); page_alloc_init(); /* * These use large bootmem allocations and must precede * mem_init(); * kmem_cache_init(); */ mm_init(); kmem_cache_init_late(); kmemleak_init(); setup_per_cpu_pageset(); rest_init(); }
下面内核开始经过start_kernel()->build_all_zonelists来设计内存的组织形式app
内存节点pg_data_t中将内存节点中的内存区域zone按照某种组织层次存储在一个zonelist中, 即pglist_data->node_zonelists成员信息less
// http://lxr.free-electrons.com/source/include/linux/mmzone.h?v=4.7#L626 typedef struct pglist_data { struct zone node_zones[MAX_NR_ZONES]; struct zonelist node_zonelists[MAX_ZONELISTS]; }
内核定义了内存的一个层次结构关系, 首先试图分配廉价的内存,若是失败,则根据访问速度和容量,逐渐尝试分配更昂贵的内存.
高端内存最廉价, 由于内核没有任何部分依赖于从该内存域分配的内存, 若是高端内存用尽, 对内核没有反作用, 因此优先分配高端内存
普通内存域的状况有所不一样, 许多内核数据结构必须保存在该内存域, 而不能放置到高端内存域, 所以若是普通内存域用尽, 那么内核会面临内存紧张的状况
DMA内存域最昂贵,由于它用于外设和系统之间的数据传输。
举例来说,若是内核指定想要分配高端内存域。它首先在当前结点的高端内存域寻找适当的空闲内存段,若是失败,则查看该结点的普通内存域,若是还失败,则试图在该结点的DMA内存域分配。若是在3个本地内存域都没法找到空闲内存,则查看其余结点。这种状况下,备选结点应该尽量靠近主结点,以最小化访问非本地内存引发的性能损失。
内核在start_kernel中经过build_all_zonelists完成了内存结点及其管理内存域的初始化工做, 调用以下
build_all_zonelists(NULL, NULL);
build_all_zonelists创建内存管理结点及其内存域的组织形式, 将描述内存的数据结构(结点, 管理域, 页帧)经过必定的算法组织在一块儿, 方便之后内存管理工做的进行. 该函数定义在mm/page_alloc.c?v4.7, line 5029
/* * Called with zonelists_mutex held always * unless system_state == SYSTEM_BOOTING. * * __ref due to (1) call of __meminit annotated setup_zone_pageset * [we're only called with non-NULL zone through __meminit paths] and * (2) call of __init annotated helper build_all_zonelists_init * [protected by SYSTEM_BOOTING]. */ void __ref build_all_zonelists(pg_data_t *pgdat, struct zone *zone) { /* 设置zonelist中节点和内存域的组织形式 * current_zonelist_order变量标识了当前系统的内存组织形式 * zonelist_order_name以字符串存储了系统中内存组织形式的名称 */ set_zonelist_order(); if (system_state == SYSTEM_BOOTING) { build_all_zonelists_init(); } else { #ifdef CONFIG_MEMORY_HOTPLUG if (zone) setup_zone_pageset(zone); #endif /* we have to stop all cpus to guarantee there is no user of zonelist */ stop_machine(__build_all_zonelists, pgdat, NULL); /* cpuset refresh routine should be here */ } 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; pr_info("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 pr_info("Policy zone: %s\n", zone_names[policy_zone]); #endif }
在build_all_zonelists开始, 首先内核经过set_zonelist_order函数设置了zonelist_order
,以下所示, 参见mm/page_alloc.c?v=4.7, line 5031
void __ref build_all_zonelists(pg_data_t *pgdat, struct zone *zone) { set_zonelist_order(); /* ....... */ }
前面咱们讲解内存管理域时候讲解到, 系统中的全部管理域都存储在一个多维的数组zone_table. 内核在初始化内存管理区时, 必需要创建管理区表zone_table. 参见mm/page_alloc.c?v=2.4.37, line 38
/* * * The zone_table array is used to look up the address of the * struct zone corresponding to a given zone number (ZONE_DMA, * ZONE_NORMAL, or ZONE_HIGHMEM). */ zone_t *zone_table[MAX_NR_ZONES*MAX_NR_NODES]; EXPORT_SYMBOL(zone_table);
NUMA系统中存在多个节点, 每一个节点对应一个struct pglist_data结构, 每一个结点中能够包含多个zone, 如: ZONE_DMA, ZONE_NORMAL, 这样就产生几种排列顺序, 以2个节点2个zone为例(zone从高到低排列, ZONE_DMA0表示节点0的ZONE_DMA,其它相似).
Legacy方式, 每一个节点只排列本身的zone;
Node方式, 按节点顺序依次排列,先排列本地节点的全部zone,再排列其它节点的全部zone。
Zone方式, 按zone类型从高到低依次排列各节点的同相类型zone
可经过启动参数”numa_zonelist_order”来配置zonelist order,内核定义了3种配置, 这些顺序定义在mm/page_alloc.c?v=4.7, line 4551
// http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L4551 /* * zonelist_order: * 0 = automatic detection of better ordering. * 1 = order by ([node] distance, -zonetype) * 2 = order by (-zonetype, [node] distance) * * If not NUMA, ZONELIST_ORDER_ZONE and ZONELIST_ORDER_NODE will create * the same zonelist. So only NUMA can configure this param. */ #define ZONELIST_ORDER_DEFAULT 0 /* 智能选择Node或Zone方式 */ #define ZONELIST_ORDER_NODE 1 /* 对应Node方式 */ #define ZONELIST_ORDER_ZONE 2 /* 对应Zone方式 */
注意
在非NUMA系统中(好比UMA), 因为只有一个内存结点, 所以ZONELIST_ORDER_ZONE和ZONELIST_ORDER_NODE选项会配置相同的内存域排列方式, 所以, 只有NUMA能够配置这几个参数
全局的current_zonelist_order变量标识了系统中的当前使用的内存域排列方式, 默认配置为ZONELIST_ORDER_DEFAULT, 参见mm/page_alloc.c?v=4.7, line 4564
// http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L4564 /* zonelist order in the kernel. * set_zonelist_order() will set this to NODE or ZONE. */ static int current_zonelist_order = ZONELIST_ORDER_DEFAULT; static char zonelist_order_name[3][8] = {"Default", "Node", "Zone"};
而zonelist_order_name方式分别对应了Legacy方式, Node方式和Zone方式. 其zonelist_order_name[current_zonelist_order]就标识了当前系统中所使用的内存域排列方式的名称”Default”, “Node”, “Zone”.
宏 | zonelist_order_name宏 | 排列方式 | 描述 |
---|---|---|---|
ZONELIST_ORDER_DEFAULT | Default | 由系统智能选择Node或Zone方式 | |
ZONELIST_ORDER_NODE | Node | Node方式 | 按节点顺序依次排列,先排列本地节点的全部zone,再排列其它节点的全部zone |
ZONELIST_ORDER_ZONE | Zone | Zone方式 | 按zone类型从高到低依次排列各节点的同相类型zone |
内核就经过经过set_zonelist_order函数设置当前系统的内存域排列方式current_zonelist_order, 其定义依据系统的NUMA结构仍是UMA结构有很大的不一样.
// http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L4571 #ifdef CONFIG_NUMA /* The value user specified ....changed by config */ static int user_zonelist_order = ZONELIST_ORDER_DEFAULT; /* string for sysctl */ #define NUMA_ZONELIST_ORDER_LEN 16 char numa_zonelist_order[16] = "default"; // http://lxr.free-electrons.com/source/mm/page_alloc.c#L4571 static void set_zonelist_order(void) { if (user_zonelist_order == ZONELIST_ORDER_DEFAULT) current_zonelist_order = default_zonelist_order(); else current_zonelist_order = user_zonelist_order; } #else /* CONFIG_NUMA */ // http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L4892 static void set_zonelist_order(void) { current_zonelist_order = ZONELIST_ORDER_ZONE; }
其设置的基本流程以下
在UMA结构下, 内存域使用NODE和ZONE两个排列方式会产生相同的效果, 所以系统不用特殊指定, 直接经过set_zonelist_order函数, 将当前系统的内存域排列方式current_zonelist_order配置为为ZONE方式(与NODE效果相同)便可
可是NUMA结构下, 默认状况下(当配置了ZONELIST_ORDER_DEFAULT), 系统须要根据系统自身的环境信息选择一个最优的配置(NODE或者ZONE方式), 这个工做就由default_zonelist_order函数了来完成. 其定义在mm/page_alloc.c?v=4.7, line 4789
#if defined(CONFIG_64BIT) /* * Devices that require DMA32/DMA are relatively rare and do not justify a * penalty to every machine in case the specialised case applies. Default * to Node-ordering on 64-bit NUMA machines */ static int default_zonelist_order(void) { return ZONELIST_ORDER_NODE; } #else /* * On 32-bit, the Normal zone needs to be preserved for allocations accessible * by the kernel. If processes running on node 0 deplete the low memory zone * then reclaim will occur more frequency increasing stalls and potentially * be easier to OOM if a large percentage of the zone is under writeback or * dirty. The problem is significantly worse if CONFIG_HIGHPTE is not set. * Hence, default to zone ordering on 32-bit. */ static int default_zonelist_order(void) { return ZONELIST_ORDER_ZONE; } #endif /* CONFIG_64BIT */
在NUMA结构下, 系统支持用户指定内存域的排列方式, 用户以字符串的形式操做numa_zonelist_order(default, node和zone), 最终被内核转换为user_zonelist_order, 这个变量被指定为字符串numa_zonelist_order指定的排列方式, 他们定义在mm/page_alloc.c?v4.7, line 4573, 注意只有在NUMA结构中才须要这个配置信息.
#ifdef CONFIG_NUMA /* The value user specified ....changed by config */ static int user_zonelist_order = ZONELIST_ORDER_DEFAULT; /* string for sysctl */ #define NUMA_ZONELIST_ORDER_LEN 16 char numa_zonelist_order[16] = "default"; #else /* ......*/ #endif
而接受和处理用户配置的工做, 天然是交给咱们强大的proc文件系统来完成的, 能够经过/proc/sys/vm/numa_zonelist_order动态改变zonelist order的分配方式。
内核经过setup_numa_zonelist_order读取并处理用户写入的配置信息
参见mm/page_alloc.c?v=4.7, line 4578
/* * interface for configure zonelist ordering. * command line option "numa_zonelist_order" * = "[dD]efault - default, automatic configuration. * = "[nN]ode - order by node locality, then by zone within node * = "[zZ]one - order by zone, then by locality within zone */ static int __parse_numa_zonelist_order(char *s) { if (*s == 'd' || *s == 'D') { user_zonelist_order = ZONELIST_ORDER_DEFAULT; } else if (*s == 'n' || *s == 'N') { user_zonelist_order = ZONELIST_ORDER_NODE; } else if (*s == 'z' || *s == 'Z') { user_zonelist_order = ZONELIST_ORDER_ZONE; } else { pr_warn("Ignoring invalid numa_zonelist_order value: %s\n", s); return -EINVAL; } return 0; } static __init int setup_numa_zonelist_order(char *s) { int ret; if (!s) return 0; ret = __parse_numa_zonelist_order(s); if (ret == 0) strlcpy(numa_zonelist_order, s, NUMA_ZONELIST_ORDER_LEN); return ret; } early_param("numa_zonelist_order", setup_numa_zonelist_order);
build_all_zonelists函数在经过set_zonelist_order设置了zonelists中结点的组织顺序后, 首先检查了ssytem_state标识. 若是当前系统处于boot阶段(SYSTEM_BOOTING), 就开始经过build_all_zonelists_init函数初始化zonelist
build_all_zonelists(pg_data_t *pgdat, struct zone *zone) { /* 设置zonelist中节点和内存域的组织形式 * current_zonelist_order变量标识了当前系统的内存组织形式 * zonelist_order_name以字符串存储了系统中内存组织形式的名称 */ set_zonelist_order(); if (system_state == SYSTEM_BOOTING) { build_all_zonelists_init();
其中system_state
变量是一个系统全局定义的用来表示系统当前运行状态的枚举变量, 其定义在include/linux/kernel.h?v=4.7, line 487
/* Values used for system_state */ extern enum system_states { SYSTEM_BOOTING, SYSTEM_RUNNING, SYSTEM_HALT, SYSTEM_POWER_OFF, SYSTEM_RESTART, } system_state;
if (system_state == SYSTEM_BOOTING) { build_all_zonelists_init(); } else { #ifdef CONFIG_MEMORY_HOTPLUG if (zone) setup_zone_pageset(zone); #endif
build_all_zonelists函数在若是当前系统处于boot阶段(system_state == SYSTEM_BOOTING), 就开始经过build_all_zonelists_init函数初始化zonelist
build_all_zonelists_init函数定义在mm/page_alloc.c?v=4.7, line 5013
static noinline void __init build_all_zonelists_init(void) { __build_all_zonelists(NULL); mminit_verify_zonelist(); cpuset_init_current_mems_allowed(); }
build_all_zonelists_init将将全部工做都委托给__build_all_zonelists完成了zonelists的初始化工做, 后者又对系统中的各个NUMA结点分别调用build_zonelists.
函数__build_all_zonelists定义在mm/page_alloc.c?v=4.7, line 4959
/* return values int ....just for stop_machine() */ static int __build_all_zonelists(void *data) { int nid; int cpu; pg_data_t *self = data; /* ...... */ for_each_online_node(nid) { pg_data_t *pgdat = NODE_DATA(nid); build_zonelists(pgdat); } /* ...... */ }
for_each_online_node遍历了系统中全部的活动结点.
因为UMA系统只有一个结点,build_zonelists只调用了一次, 就对全部的内存建立了内存域列表.
NUMA系统调用该函数的次数等同于结点的数目. 每次调用对一个不一样结点生成内存域数据
build_zonelists(pg_data_t *pgdat)完成了节点pgdat上zonelists的初始化工做, 它创建了备用层次结构zonelists. 因为UMA和NUMA架构下结点的层次结构有很大的区别, 所以内核分别提供了两套不一样的接口.
以下所示
// http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7 4571 #ifdef CONFIG_NUMA 4586 static int __parse_numa_zonelist_order(char *s) 4601 static __init int setup_numa_zonelist_order(char *s) 4619 int numa_zonelist_order_handler(struct ctl_table *table, int write, 4620 void __user *buffer, size_t *length, 4678 static int find_next_best_node(int node, nodemask_t *used_node_mask) 4730 static void build_zonelists_in_node_order(pg_data_t *pgdat, int node) 4746 static void build_thisnode_zonelists(pg_data_t *pgdat) 4765 static void build_zonelists_in_zone_order(pg_data_t *pgdat, int nr_nodes) 4789 #if defined(CONFIG_64BIT) 4795 static int default_zonelist_order(void) 4799 #else 4808 static int default_zonelist_order(void) 4812 #endif /* CONFIG_64BIT */ 4822 static void build_zonelists(pg_data_t *pgdat) 4872 #ifdef CONFIG_HAVE_MEMORYLESS_NODES 4879 int local_memory_node(int node) 4888 #endif 4890 #else /* CONFIG_NUMA */ 4897 static void build_zonelists(pg_data_t *pgdat) 4892 static void set_zonelist_order(void) 4931 #endif /* CONFIG_NUMA */
header 1 | header 2
row 1 col 1 | row 1 col 2
row 2 col 1 | row 2 col 2
函数 | NUMA | UMA |
---|---|---|
build_zonelists | build_zonelists -=> mm/page_alloc.c?v=4.7, line 4822 | build_zonelists -=> mm/page_alloc.c?v=4.7, line 4897 build_zonelists_node -=> mm/page_alloc.c?v=4.7, line 4531 |
咱们以UMA结构下的build_zonelists为例, 来说讲内核是怎么初始化备用内存域层次结构的, UMA结构下的build_zonelists函数定义在mm/page_alloc.c?v=4.7, line 4897, 以下所示
node_zonelists的数组元素经过指针操做寻址, 这在C语言中是彻底合法的惯例。实际工做则委托给build_zonelist_node。在调用时,它首先生成本地结点内分配内存时的备用次
内核在build_zonelists中按分配代价从昂贵到低廉的次序, 迭代告终点中全部的内存域. 而在build_zonelists_node中, 则按照分配代价从低廉到昂贵的次序, 迭代了分配代价不低于当前内存域的内存域.
首先咱们来看看build_zonelists_node函数, 该函数定义在mm/page_alloc.c?v=4.7, line 4531
/* * Builds allocation fallback zone lists. * * Add all populated zones of a node to the zonelist. */ static int build_zonelists_node(pg_data_t *pgdat, struct zonelist *zonelist, int nr_zones) { struct zone *zone; enum zone_type zone_type = MAX_NR_ZONES; do { zone_type--; zone = pgdat->node_zones + zone_type; if (populated_zone(zone)) { zoneref_set_zone(zone, &zonelist->_zonerefs[nr_zones++]); check_highest_zone(zone_type); } } while (zone_type); return nr_zones; }
备用列表zonelists的各项是借助于zone_type参数排序的, 该参数指定了最优先选择哪一个内存域, 该参数的初始值是外层循环的控制变量i.
咱们知道其值多是ZONE_HIGHMEM、ZONE_NORMAL、ZONE_DMA或ZONE_DMA32之一.
nr_zones表示从备用列表中的哪一个位置开始填充新项. 因为列表中尚没有项, 所以调用者传递了0.
内核在build_zonelists中按分配代价从昂贵到低廉的次序, 迭代告终点中全部的内存域. 而在build_zonelists_node中, 则按照分配代价从低廉到昂贵的次序, 迭代了分配代价不低于当前内存域的内存域.
在build_zonelists_node的每一步中, 都对所选的内存域调用populated_zone, 确认zone->present_pages大于0, 即确认内存域中确实有页存在. 假若如此, 则将指向zone实例的指针添加到zonelist->zones中的当前位置. 后备列表的当前位置保存在nr_zones.
在每一步结束时, 都将内存域类型zone_type减1.换句话说, 设置为一个更昂贵的内存域类型. 例如, 若是开始的内存域是ZONE_HIGHMEM, 减1后下一个内存域类型是ZONE_NORMAL.
考虑一个系统, 有内存域ZONE_HIGHMEM、ZONE_NORMAL、ZONE_DMA。在第一次运行build_zonelists_node时, 实际上会执行下列赋值
zonelist->zones[0] = ZONE_HIGHMEM; zonelist->zones[1] = ZONE_NORMAL; zonelist->zones[2] = ZONE_DMA;
咱们以某个系统为例, 图中示范了一个备用列表在屡次循环中不断填充的过程. 系统中共有四个结点
其中 A=(NUMA)结点0 0=DMA内存域 B=(NUMA)结点1 1=普通内存域 C=(NUMA)结点2 2=高端内存域 D=(NUMA)结点3
第一步以后, 列表中的分配目标是高端内存, 接下来是第二个结点的普通和DMA内存域.
内核接下来必须确立次序, 以便将系统中其余结点的内存域按照次序加入到备用列表.
如今咱们回到build_zonelists函数, UMA架构下该函数定义在mm/page_alloc.c?v=4.7, line 4897, 以下所示
static void build_zonelists(pg_data_t *pgdat) { int node, local_node; enum zone_type j; struct zonelist *zonelist; /* ...... */ for (node = local_node + 1; node < MAX_NUMNODES; node++) { if (!node_online(node)) continue; j = build_zonelists_node(NODE_DATA(node), zonelist, j); } for (node = 0; node < local_node; node++) { if (!node_online(node)) continue; j = build_zonelists_node(NODE_DATA(node), zonelist, j); } zonelist->_zonerefs[j].zone = NULL; zonelist->_zonerefs[j].zone_idx = 0; }
第一个循环依次迭代大于当前结点编号的全部结点. 在咱们的例子中,有4个结点编号副本为0、一、二、3,此时只剩下结点3。新的项经过build_zonelists_node被加到备用列表。此时j的做用就体现出来了。在本地结点的备用目标找到以后,该变量的值是3。该值用做新项的起始位置。若是结点3也由3个内存域组成,备用列表在第二个循环以后的状况如图3-9的第二步所示
第二个for循环接下来对全部编号小于当前结点的结点生成备用列表项。在咱们的例子中,这些结点的编号为0和1。 若是这些结点也有3个内存域,则循环完毕以后备用列表的状况以下图下半部分所示
备用列表中项的数目通常没法准确知道,由于系统中不一样结点的内存域配置可能并不相同。所以 列表的最后一项赋值为空指针,显式标记列表结束。
对总数N个结点中的结点m来讲,内核生成备用列表时,选择备用结点的顺序老是:m、m+一、 m+二、…、N一、0、一、…、m1。这确保了不过分使用任何结点。例如,对照状况是:使用一个独立 于m、不变的备用列表
前面讲解内存管理域zone的时候, 提到了per-CPU缓存, 即冷热页. 在组织每一个节点的zonelist的过程当中, setup_pageset初始化了per-CPU缓存(冷热页面)
static void setup_pageset(struct per_cpu_pageset *p, unsigned long batch) { pageset_init(p); pageset_set_batch(p, batch); }
在此以前free_area_init_node初始化内存结点的时候, 内核就输出了冷热页的一些信息, 该工做由zone_pcp_init完成, 该函数定义在mm/page_alloc.c?v=4.7, line 5029
static __meminit void zone_pcp_init(struct zone *zone) { /* * per cpu subsystem is not up at this point. The following code * relies on the ability of the linker to provide the * offset of a (static) per cpu variable into the per cpu area. */ zone->pageset = &boot_pageset; if (populated_zone(zone)) printk(KERN_DEBUG " %s zone: %lu pages, LIFO batch:%u\n", zone->name, zone->present_pages, zone_batchsize(zone)); }
start_kernel() |---->page_address_init() | 考虑支持高端内存 | 业务:初始化page_address_pool链表; | 将page_address_maps数组元素按索引降序插入 | page_address_pool链表; | 初始化page_address_htable数组. | |---->setup_arch(&command_line); | |---->setup_per_cpu_areas(); | 为per-CPU变量分配空间 | |---->build_all_zonelist() | 为系统中的zone创建后备zone的列表. | 全部zone的后备列表都在 | pglist_data->node_zonelists[0]中; | | 期间也对per-CPU变量boot_pageset作了初始化. | |---->page_alloc_init() |---->hotcpu_notifier(page_alloc_cpu_notifier, 0); | 不考虑热插拔CPU | |---->pidhash_init() | 详见下文. | 根据低端内存页数和散列度,分配hash空间,并赋予pid_hash | |---->vfs_caches_init_early() |---->dcache_init_early() | dentry_hashtable空间,d_hash_shift, h_hash_mask赋值; | 同pidhash_init(); | 区别: | 散列度变化了(13 - PAGE_SHIFT); | 传入alloc_large_system_hash的最后参数值为0; | |---->inode_init_early() | inode_hashtable空间,i_hash_shift, i_hash_mask赋值; | 同pidhash_init(); | 区别: | 散列度变化了(14 - PAGE_SHIFT); | 传入alloc_large_system_hash的最后参数值为0; |
void pidhash_init(void) |---->pid_hash = alloc_large_system_hash("PID", sizeof(*pid_hash), | 0, 18, HASH_EARLY|HASH_SMALL, &pidhash_shift, NULL, 4096); | 根据nr_kernel_pages(低端内存的页数),分配哈希数组,以及各个哈希 | 数组元素下的哈希链表的空间,原理以下: | number = nr_kernel_pages; | number >= (18 - PAGE_SHIFT) 根据散列度得到数组元素个数 | number = roundup_pow_of_two(number); | pidhash_shift = max{x | 2**x <= number} | size = number * sizeof(*pid_hash); | 使用位图分配器分配size空间,将返回值付给pid_hash; | |---->pidhash_size = 1 << pidhash_shift; | |---->for(i = 0; i < pidhash_size; i++) | INIT_HLIST_HEAD(&pid_hash[i]);
void build_all_zonelists(void) |---->set_zonelist_order() |---->current_zonelist_order = ZONELIST_ORDER_ZONE; | |---->__build_all_zonelists(NULL); | Memory不支持热插拔, 为每一个zone创建后备的zone, | 每一个zone及本身后备的zone,造成zonelist | |---->pg_data_t *pgdat = NULL; | pgdat = &contig_page_data;(单node) | |---->build_zonelists(pgdat); | 为每一个zone创建后备zone的列表 | |---->struct zonelist *zonelist = NULL; | enum zone_type j; | zonelist = &pgdat->node_zonelists[0]; | |---->j = build_zonelists_node(pddat, zonelist, 0, MAX_NR_ZONES - 1); | 为pgdat->node_zones[0]创建后备的zone,node_zones[0]后备的zone | 存储在node_zonelist[0]内,对于node_zone[0]的后备zone,其后备的zone | 链表以下(只考虑UMA体系,并且不考虑ZONE_DMA): | node_zonelist[0]._zonerefs[0].zone = &node_zones[2]; | node_zonelist[0]._zonerefs[0].zone_idx = 2; | node_zonelist[0]._zonerefs[1].zone = &node_zones[1]; | node_zonelist[0]._zonerefs[1].zone_idx = 1; | node_zonelist[0]._zonerefs[2].zone = &node_zones[0]; | node_zonelist[0]._zonerefs[2].zone_idx = 0; | | zonelist->_zonerefs[3].zone = NULL; | zonelist->_zonerefs[3].zone_idx = 0; | |---->build_zonelist_cache(pgdat); |---->pdat->node_zonelists[0].zlcache_ptr = NULL; | UMA体系结构 | |---->for_each_possible_cpu(cpu) | setup_pageset(&per_cpu(boot_pageset, cpu), 0); |详见下文 |---->vm_total_pages = nr_free_pagecache_pages(); | 业务:得到全部zone中的present_pages总和. | |---->page_group_by_mobility_disabled = 0; | 对于代码中的判断条件通常不会成立,由于页数会最够多(内存较大)