0. 前言

bootmem_init初始化的时候,已经初始化了内存节点的zone成员,该成员是struct zone数组,存放该内存节点的zone信息。在linux的内存管理中,分几个阶段进行抽象,用数据结构来管理。先用结点集合管理内存,然后用zone管理结点,再用页的管理zone。此时使用的数据结构分别为pglist_datazonepage结构体,本章的主要是来分析内核是如何完成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 的初始化就基本都分析完成!