在bootmem_init初始化的时候,已经初始化了内存节点的zone成员,该成员是struct zone数组,存放该内存节点的zone信息。在linux的内存管理中,分几个阶段进行抽象,用数据结构来管理。先用结点集合管理内存,然后用zone管理结点,再用页的管理zone。此时使用的数据结构分别为pglist_data、zone、page结构体,本章的主要是来分析内核是如何完成zonelist的初始化。
在结点的pglist_data数据结构中有一个node_zone_list[]类型的struct zonelist
typedef struct pglist_data {
...
struct zonelist node_zonelists[MAX_ZONELISTS];
...
}pg_data_t;
struct zonelist {
struct zoneref _zonerefs[MAX_ZONES_PER_ZONELIST + 1];
};
enum {
ZONELIST_FALLBACK, /* zonelist with fallback */
#ifdef CONFIG_NUMA
ZONELIST_NOFALLBACK, /* zonelist without fallback (__GFP_THISNODE) */
#endif
MAX_ZONELISTS
}
#define MAX_ZONES_PER_ZONELIST (MAX_NUMNODES * MAX_NR_ZONES)
node_zonelists[]包含了2个zonelist,一个是由本node的zones组成,另一个是由从本node分配不到内存时可选的备用zones组成,相当于是选择了一个退路,所以叫fallback。而对于本开发板,没有定义NUMA,没有备份。
struct zonelist只有一个_zonerefs[]数组构成,_zonerefs[]数组的大小为MAX_ZONES_PER_ZONELIST,最大的节点数和节点可拥有的ZONE数w为1 *MAX_NR_ZONES
_zonerefs[]数组的类型struct zoneref定义如下,主要是zone指针和索引号构成。
struct zoneref {
struct zone *zone; /* Pointer to actual zone */
int zone_idx; /* zone_idx(zoneref->zone) */
};
内核在start_kernel中通过build_all_zonelists完成了内存结点及其管理内存域的初始化工作, 调用如下
void __ref build_all_zonelists(pg_data_t *pgdat, struct zone *zone)
{
set_zonelist_order(); ----------------(1)
if (system_state == SYSTEM_BOOTING) { ----------------(2)
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(); ----------------(3)
/*
* 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)) ----------------(4)
page_group_by_mobility_disabled = 1;
else
page_group_by_mobility_disabled = 0;
----------------(5)
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
}
1.调用set_zonelist_order函数决定zone排列方式;按照相同区域排列,还是以节点为基准排列
2.不同的系统状态调用的函数不同,系统状态为启动阶段时(SYSTEM_BOOTING)时,就调用build_all_zonelists_init函数,其他状态就调用stop_machine函数。让系统的所有CPU执行停止函数。其系统状态可分为6中,其定义如下
extern enum system_states {
SYSTEM_BOOTING,
SYSTEM_RUNNING,
SYSTEM_HALT,
SYSTEM_POWER_OFF,
SYSTEM_RESTART,
} system_state;
3.调用nr_free_pagecache_pages,从函数名字可以看出,该函数求出可处理的空页数
4.通过nr_free_pagecache_pages求出vm_total_pages和页移动性比较,决定是否激活grouping
5.打印到控制台,打印的信息输出内容为online node、zone列表顺序,是否根据移动性对页面执行集合(grouping)、vm_total_pages、NUMA时输出policy zone
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KWlvYpMv-1592019440924)(D:\学习总结\内存管理单元\image-20200606225539321.png)]
set_zonelist_order函数决定用节点顺序构建还是用zone顺序构建,其定义如下
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;
}
该函数检查user_zonelist_order是否为ZONELIST_ORDER_DEFAULT,user_zonelist_order具有以下3个值之一
#define ZONELIST_ORDER_DEFAULT 0
#define ZONELIST_ORDER_NODE 1
#define ZONELIST_ORDER_ZONE 2
如果是user_zonelist_order,就调用default_zonelist_order,决定将zonelist顺序作为节点顺序还是zone顺序,否则用user_zonelist_order,如果是32位系统,其为ZONELIST_ORDER_ZONE,而如果是64位系统,则为ZONELIST_ORDER_NODE。即为zone优先还是节点优先。
假设节点0的zone类型由ZONE_NORMAL和ZONE_DMA构成,节点1由ZONE_NORMAL构成,可将节点0的zonelist按下面构建
对于类型A,如果节点0的ZONE_NORMAL中无法分配内存,就从节点0的ZONE_DMA开始分配内存,但由于一般ZONE_DMA区域比较小,就会发生ZONE_DMA的OOM(Out of memory)问题
对于类型B,如果节点的ZONE_NORMAL中无法分配内存,就从节点1的ZONE_NORMAL开始分配。
因此对于类型A为节点优先,类型B为zone顺序优先。
构建备用列表的主要工作是在__build_all_zonelists函数中实现的,其主要是遍历每一个节点,然后调用build_zonelists
static int __build_all_zonelists(void *data)
{
int nid;
int cpu;
pg_data_t *self = data;
#ifdef CONFIG_NUMA
memset(node_load, 0, sizeof(node_load));
#endif
if (self && !node_online(self->node_id)) {
build_zonelists(self);
}
for_each_online_node(nid) {
pg_data_t *pgdat = NODE_DATA(nid);
build_zonelists(pgdat);
}
for_each_possible_cpu(cpu) {
setup_pageset(&per_cpu(boot_pageset, cpu), 0);
#ifdef CONFIG_HAVE_MEMORYLESS_NODES
if (cpu_online(cpu))
set_cpu_numa_mem(cpu, local_memory_node(cpu_to_node(cpu)));
#endif
}
return 0;
}
其主要是来分析下build_zonelists的流程
static void build_zonelists(pg_data_t *pgdat)
{
int i, node, load;
nodemask_t used_mask;
int local_node, prev_node;
struct zonelist *zonelist;
unsigned int order = current_zonelist_order;
/* initialize zonelists */
for (i = 0; i < MAX_ZONELISTS; i++) { ------------(1)
zonelist = pgdat->node_zonelists + i;
zonelist->_zonerefs[0].zone = NULL;
zonelist->_zonerefs[0].zone_idx = 0;
}
/* NUMA-aware ordering of nodes */
local_node = pgdat->node_id;
load = nr_online_nodes;
prev_node = local_node;
nodes_clear(used_mask);
memset(node_order, 0, sizeof(node_order));
i = 0;
while ((node = find_next_best_node(local_node, &used_mask)) >= 0) { ------------(2)
/*
* We don't want to pressure a particular node.
* So adding penalty to the first node in same
* distance group to make it round-robin.
*/
if (node_distance(local_node, node) !=
node_distance(local_node, prev_node))
node_load[node] = load;
prev_node = node;
load--;
if (order == ZONELIST_ORDER_NODE) ------------(3)
build_zonelists_in_node_order(pgdat, node);
else
node_order[i++] = node; /* remember order */
}
if (order == ZONELIST_ORDER_ZONE) { ------------(4)
/* calculate node order -- i.e., DMA last! */
build_zonelists_in_zone_order(pgdat, i);
}
build_thisnode_zonelists(pgdat); ------------(5)
}
static void build_zonelists_in_node_order(pg_data_t *pgdat, int node)
{
int j;
struct zonelist *zonelist;
zonelist = &pgdat->node_zonelists[ZONELIST_FALLBACK];
for (j = 0; zonelist->_zonerefs[j].zone != NULL; j++)
;
j = build_zonelists_node(NODE_DATA(node), zonelist, j);
zonelist->_zonerefs[j].zone = NULL;
zonelist->_zonerefs[j].zone_idx = 0;
}
该函数以节点为单位构建备份列表,各节点的zone按顺序构建,具有这些zone的列表的数组就是zonelist的_zonerefs成员变量。_
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--; ------------(1)
zone = pgdat->node_zones + zone_type;
if (managed_zone(zone)) { ------------(2)
zoneref_set_zone(zone,
&zonelist->_zonerefs[nr_zones++]);
check_highest_zone(zone_type);
}
} while (zone_type);
return nr_zones;
}
下面分析mminit_verify_zonelist函数
void __init mminit_verify_zonelist(void)
{
int nid;
if (mminit_loglevel < MMINIT_VERIFY)
return;
for_each_online_node(nid) {
pg_data_t *pgdat = NODE_DATA(nid);
struct zone *zone;
struct zoneref *z;
struct zonelist *zonelist;
int i, listid, zoneid;
BUG_ON(MAX_ZONELISTS > 2);
for (i = 0; i < MAX_ZONELISTS * MAX_NR_ZONES; i++) {
/* Identify the zone and nodelist */
zoneid = i % MAX_NR_ZONES;
listid = i / MAX_NR_ZONES;
zonelist = &pgdat->node_zonelists[listid];
zone = &pgdat->node_zones[zoneid];
if (!populated_zone(zone))
continue;
/* Print information about the zonelist */
printk(KERN_DEBUG "mminit::zonelist %s %d:%s = ",
listid > 0 ? "thisnode" : "general", nid,
zone->name);
/* Iterate the zonelist */
for_each_zone_zonelist(zone, z, zonelist, zoneid) {
#ifdef CONFIG_NUMA
pr_cont("%d:%s ", zone->node, zone->name);
#else
pr_cont("0:%s ", zone->name);
#endif /* CONFIG_NUMA */
}
pr_cont("\n");
}
}
}
该函数,对各个节点进行遍历,对各个节点具有的最大ZONE数,输出zonelist的信息,对各个zonelist输出zone名称。该函数输出系统内所有节点的备份列表信息,只是执行for循环访问节点的备份列表,输出构建备份列表的zone节点号和节点名。
cpuset_init_current_mems_allowed函数只调用nodes_setall函数,在当前任务current的mems_allowed位图中,将系统的所有节点设置为1。mems_allowed位图决定处理当前任务中发生的页分配请求的节点。
void __init cpuset_init_current_mems_allowed(void)
{
nodes_setall(current->mems_allowed);
}
将gfp_zone(GFP_HIGHUSER_MOVABLE)的结果值作为参数传递,gfp_zone函数对传递来的参数标签值进行检查并返回zone类型,并返回zone类型中的可用页数。
unsigned long nr_free_pagecache_pages(void)
{
return nr_free_zone_pages(gfp_zone(GFP_HIGHUSER_MOVABLE));
}
下面来看看nr_free_zone_pages函数
static unsigned long nr_free_zone_pages(int offset)
{
struct zoneref *z;
struct zone *zone;
/* Just pick one node, since fallback list is circular */
unsigned long sum = 0;
struct zonelist *zonelist = node_zonelist(numa_node_id(), GFP_KERNEL);
for_each_zone_zonelist(zone, z, zonelist, offset) {
unsigned long size = zone->managed_pages;
unsigned long high = high_wmark_pages(zone);
if (size > high)
sum += size - high;
}
return sum;
}
该函数主要是对zonelist执行循环,访问zonelist的所有zone,在sum中累积从zone->present_pages减掉zone->pages_high的值。zone->present_pages是相应的zone中的物理页数,zone->pages_high变量用于决定相应zone是否为Idle状态。
该函数主要是用于求出可处理的空页数。
if (vm_total_pages < (pageblock_nr_pages * MIGRATE_TYPES))
page_group_by_mobility_disabled = 1;
else
page_group_by_mobility_disabled = 0;
通过前面的函数求出vm_total_pages,若比(pageblock_nr_pages * MIGRATE_TYPES)小,就不允许以移动性为基准执行。
pageblock_nr_pages和MIGRATE_TYPES定义如下
#define MAX_ORDER 11
#define pageblock_order (MAX_ORDER-1)
#define pageblock_nr_pages (1UL << pageblock_order)
MIGRATE_TYPES表示移动类型的宏,其值为5,其定义为
enum {
MIGRATE_UNMOVABLE, //不可以动
MIGRATE_MOVABLE, //可回收
MIGRATE_RECLAIMABLE, //可移动
MIGRATE_PCPTYPES,
MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,
MIGRATE_TYPES
}
通过以上方式,最终构建了free_list以移动性为基准执行的页集合,其主要有以下好处
防止内存碎片:以顺序为单位对具有相同移动属性的页执行集合,防止内存碎片
分配大内存的方法:将具有相同移动属性的页集合在一处,使其能够在大内存分配中使用,例如
build_all_zonelists()用来初始化内存分配器使用的存储节点中的管理区链表,是为内存管理算法(伙伴管理算法)做准备工作的,对Linux管理的各内存结构体进行初始化和设置操作,如下图所示
ARM Linux内核源码剖析