0. 前言
在bootmem_init
初始化的时候,已经初始化了内存节点的zone成员,该成员是struct zone数组,存放该内存节点的zone信息。在linux的内存管理中,分几个阶段进行抽象,用数据结构来管理。先用结点集合管理内存,然后用zone管理结点,再用页的管理zone。此时使用的数据结构分别为pglist_data
、zone
、page
结构体,本章的主要是来分析内核是如何完成zonelist的初始化。
{% tip success %}
关于前面初始化内存节点的zone成员,可以参看:[linux内存管理] 第013篇 zone的初始化
{% endtip %}
1. 数据结构
1.1 struct pgdata_list
对于数据结构pg_data_t
在[linux内存管理] 第012章 物理内存管理三大结构体之zone一文中已经简单地阐述了,其中成员变量 node_zonelists 也进行了简单地说明,但是涉及其初始化的过程因为内容比较多,还是另开一篇博文进行记录。
include/linux/mmzone.h
enum {
ZONELIST_FALLBACK, /* zonelist with fallback */
#ifdef CONFIG_NUMA
ZONELIST_NOFALLBACK, /* zonelist without fallback (__GFP_THISNODE) */
#endif
MAX_ZONELISTS
};
typedef struct pglist_data {
struct zone node_zones[MAX_NR_ZONES];
struct zonelist node_zonelists[MAX_ZONELISTS];
//...
}
其他的成员已在前面的博文中说明过,这里不过多叙述,这里重复列举只是为了引出成员变量 node_zonelists
数组,对于UMA 来说数组个数为1,也就是ZONELIST_FALLBACK
。
有一个备用的zonelist,当首选的内存节点和区域不能满足页分配请求,可以从备用的内存区域借用物理页。
同理,对于NUMA 来说,是每个内存节点有两个备用区域列表:
- 一个包含所欲内存节点的区域(ZONELIST_FALLBACK);
- 另一个只包含当前内存节点的区域(ZONELIST_NOFALLBACK);
1.2 struct zonelist
///一个实际的zone
///zone_idx,表示zone编号,0表示最低
struct zoneref {
struct zone *zone; /* Pointer to actual zone */
int zone_idx; /* zone_idx(zoneref->zone) */
};
///该结构体包含一个node的各个zone的信息
//zone的排列循序,由优先级决定
///每一个struct zoneref描述一个zone
struct zonelist {
struct zoneref _zonerefs[MAX_ZONES_PER_ZONELIST + 1];
};
也就是每个 node 的数据结构 pglist_data
中都会存在一个zonelist
的结构体数组,第 1 节也说了,对于UMA 来说这样的结构体数组只有1个 ZONELIST_FALLBACK
,而这个唯一的数组成员中是另外一个 zoneref
数组,此数组的长度所有node 下的所有 zone ,当然对于UMA 来说 _zonerefs
数组包含的是 contig_page_data
中所有的zone。
2. zonelist的初始化
内核在start_kernel
中通过build_all_zonelists
完成了内存结点及其管理内存域的初始化工作
void __ref build_all_zonelists(pg_data_t *pgdat)
{
unsigned long vm_total_pages;
if (system_state == SYSTEM_BOOTING) {
build_all_zonelists_init();
} else {
__build_all_zonelists(pgdat);
/* cpuset refresh routine should be here */
}
/* Get the number of free pages beyond high watermark in all zones. */
//求出可处理的空页数
vm_total_pages = nr_free_zone_pages(gfp_zone(GFP_HIGHUSER_MOVABLE));
/*
* 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
*/
//求出vm_total_pages和页移动性比较,决定是否激活grouping
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 %u zonelists, mobility grouping %s. Total pages: %ld\n",
nr_online_nodes,
page_group_by_mobility_disabled ? "off" : "on",
vm_total_pages);
#ifdef CONFIG_NUMA
pr_info("Policy zone: %s\n", zone_names[policy_zone]);
#endif
}
2.1 build_all_zonelists_init
static noinline void __init
build_all_zonelists_init(void)
{
int cpu;
// 在系统初始化阶段,传入的参数为NULL,hotplug时传入的是pgdat
__build_all_zonelists(NULL);
for_each_possible_cpu(cpu)
per_cpu_pages_init(&per_cpu(boot_pageset, cpu), &per_cpu(boot_zonestats, cpu));
mminit_verify_zonelist();
cpuset_init_current_mems_allowed();
}
static void __build_all_zonelists(void *data)
{
int nid;
int __maybe_unused cpu;
pg_data_t *self = data;
static DEFINE_SPINLOCK(lock);
spin_lock(&lock);
#ifdef CONFIG_NUMA
memset(node_load, 0, sizeof(node_load));
#endif
if (self && !node_online(self->node_id)) {
build_zonelists(self);
} else {
for_each_online_node(nid) {
pg_data_t *pgdat = NODE_DATA(nid);
build_zonelists(pgdat);
}
#ifdef CONFIG_HAVE_MEMORYLESS_NODES
for_each_online_cpu(cpu)
set_cpu_numa_mem(cpu, local_memory_node(cpu_to_node(cpu)));
#endif
}
spin_unlock(&lock);
}
初始化的时候会进入for_each,分别build 每个pgdat,对于UMA 来说就一个node,pgdat 就是
contig_page_data; 对于NUMA 会遍历每个node,下面来看下 build_zonelists()
。
2.2 build_zonelists
static void build_zonelists(pg_data_t *pgdat)
{
int node, local_node;
struct zoneref *zonerefs;
int nr_zones;
//将当前的node_id 记录在local_node 中,下面for 循环需要知道当前的node id
local_node = pgdat->node_id;
// 将pgdata的_zonerefs的首地址赋值给临时变量zonerefs
zonerefs = pgdat->node_zonelists[ZONELIST_FALLBACK]._zonerefs;
// 初始化zonerefs
nr_zones = build_zonerefs_node(pgdat, zonerefs);
// 移到最后进行下一步初始化
zonerefs += nr_zones;
/*
* 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)
*/
// 初始化其他node 的zone,主要针对NUMA
for (node = local_node + 1; node < MAX_NUMNODES; node++) {
if (!node_online(node))
continue;
nr_zones = build_zonerefs_node(NODE_DATA(node), zonerefs);
zonerefs += nr_zones;
}
for (node = 0; node < local_node; node++) {
if (!node_online(node))
continue;
nr_zones = build_zonerefs_node(NODE_DATA(node), zonerefs);
zonerefs += nr_zones;
}
// 初始化结束后将临时变量置空
zonerefs->zone = NULL;
zonerefs->zone_idx = 0;
}
本函数的主要功能是通过 build_zonerefs_node
创建备用zone list,全部记录在ZONELIST_FALLBACK
中。
而本函数的主要逻辑是在最后的两个for循环中:
- 在for 循环之前调用一次
build_zonerefs_node()
将本节点的最受欢迎的 zone 记录到ZONELIST_FALLBACK
中,从最高的 zone 开始记录; - 接着通过第一次for 循环将从本 node 开始之后的node 继续创建备用列表;
- 接着通过第二次for 循环将从0开始至本node 为止的node 创建备用列表;
2.3 build_zonerefs_node
static int build_zonerefs_node(pg_data_t *pgdat, struct zoneref *zonerefs)
{
struct zone *zone;
enum zone_type zone_type = MAX_NR_ZONES;
int nr_zones = 0;
do {
zone_type--;
zone = pgdat->node_zones + zone_type;
if (managed_zone(zone)) {
zoneref_set_zone(zone, &zonerefs[nr_zones++]);
check_highest_zone(zone_type);
}
} while (zone_type);
return nr_zones;
}
注意控制变量为 zone_type,而这个type 是从 MAX_NR_ZONES
开始,这点很重要;
通过 zone_type 找到已经初始化好的zone,通过 zoneref_set_zone()
设置到zonerefs 数组中。这里就知道了当zone_idx 为1 的ZONE_NORMAL 在zonerefs 数组中处于第 0 个,而zone_idx 为0 的ZONE_DMA32 在 zonerefs 数组中处于第 1 个;
函数 managed_zone()
用来确认该zone 中含有可用 pages;
3. 总结
至此, zone 的初始化就基本都分析完成!