Linux内存管理详解(4)

2019-08-31 09:47

#ifdef CONFIG_OUT_OF_LINE_PFN_TO_PAGE struct page;

/* this is useful when inlined pfn_to_page is too big */ extern struct page *pfn_to_page(unsigned long pfn); extern unsigned long page_to_pfn(struct page *page); #else

#define page_to_pfn __page_to_pfn #define pfn_to_page __pfn_to_page

#endif /* CONFIG_OUT_OF_LINE_PFN_TO_PAGE */

在struct page管理数组是线性分布的时候,pfn_to_page被统一定义为

__pfn_to_page。平坦内存中的ARCH_PFN_OFFSET被定义 为0,而mem_map在alloc_node_mem_map中被赋值为node_mem_map,也即管理数组的首地址。 page中flags成员的Field Area由三部分组成,它们从高地址位该是依次分布。段区只有在配置了CONFIG_SPARSEMEM时才有可能存在。 /* ..... *

* No sparsemem or sparsemem vmemmap: | NODE | ZONE | ... | FLAGS |

* classic sparse with space for node:| SECTION | NODE | ZONE | ... | FLAGS | * classic sparse no space for node: | SECTION | ZONE | ... | FLAGS | */

13.7. build_all_zonelists

build_all_zonelists在init/main.c中的start_kernel中被调用,它用来初始化内存分配器使用的存储节点中的管理区链表。 include/linux/kernel.h

extern enum system_states { SYSTEM_BOOTING, SYSTEM_RUNNING, SYSTEM_HALT,

SYSTEM_POWER_OFF, SYSTEM_RESTART,

SYSTEM_SUSPEND_DISK, } system_state;

mm/page_alloc.c

void build_all_zonelists(void) { set_zonelist_order();

}

if (system_state == SYSTEM_BOOTING) { __build_all_zonelists(NULL); mminit_verify_zonelist(); cpuset_init_current_mems_allowed(); } else { /* we have to stop all cpus to guarantee there is no user of zonelist */ stop_machine(__build_all_zonelists, NULL, NULL); /* cpuset refresh routine should be here */ }

vm_total_pages = nr_free_pagecache_pages();

if (vm_total_pages < (pageblock_nr_pages * MIGRATE_TYPES)) page_group_by_mobility_disabled = 1; else page_group_by_mobility_disabled = 0; printk(\ \ \ num_online_nodes(), zonelist_order_name[current_zonelist_order], page_group_by_mobility_disabled ? \ vm_total_pages);

build_all_zonelists依次完成以下功能:

根 据system_state查看系统的运行状态,在内核启动阶段,它的值保持为0,也即SYSTEM_BOOTING,只有在start_kernel执 行到最后一个函数rest_init后,才会进入SYSTEM_RUNNING阶段。如果为内核启动阶段,那么调用 __build_all_zonelists初始化分配器管理区链表,否则挂起系统,显然内存管理功能初始化出现异常为致命错误。

? nr_free_pagecache_pages直接调用nr_free_zone_pages来统计系统中所有内存节点中可用的内存页框数,通常就是对present_pages成员的叠加。 ? 根 据当前系统中的内存页框数目,决定是否启用流动分组(Mobility Grouping)机制,这种机制可以在分配大内存块时减少内存碎片。显然只有内存足够大时才会启用该功能,否则将得不偿失。 pageblock_nr_pages实际上是一个宏,它表示伙伴系统中的最高阶页块所能包含的页面数。

?

pageblock_order在free_area_init_core中被初始化为伙伴系统中的最高阶,

pageblock_nr_pages * MIGRATE_TYPES的用于在于可以确保当前内存可以满足最高阶链表中至少有一个可分配节点的存在。

include/linux/pageblock-flags.h

#define pageblock_nr_pages (1UL << pageblock_order)

一个拥有256MB的内存开发板上的管理区参考链表信息如下: Built 1 zonelists in Zone order, mobility grouping on. Total pages: 65024 build_all_zonelists调用了一些列函数,调用流程如下所示: 图 73. 内存管理区初始化流程图

13.8. __build_all_zonelists

/* return values int ....just for stop_machine() */ static int __build_all_zonelists(void *dummy) { int nid; for_each_online_node(nid) { pg_data_t *pgdat = NODE_DATA(nid); build_zonelists(pgdat); build_zonelist_cache(pgdat); } return 0; }

__build_all_zonelists的dummy参数没有任何意义,并且返回值永远为0,这是为了方便stop_machine对其结果作为参数引用。build_zonelist_cache与CONFIG_NUMA相关,它用来设置zlcache相关的成员。 static void build_zonelist_cache(pg_data_t *pgdat) { pgdat->node_zonelists[0].zlcache_ptr = NULL; }

13.9. build_zonelists

static void build_zonelists(pg_data_t *pgdat) { int node, local_node; enum zone_type j; struct zonelist *zonelist; local_node = pgdat->node_id; zonelist = &pgdat->node_zonelists[0]; j = build_zonelists_node(pgdat, zonelist, 0, MAX_NR_ZONES - 1); for (node = local_node + 1; node < MAX_NUMNODES; node++) { if (!node_online(node)) continue; j = build_zonelists_node(NODE_DATA(node), zonelist, j, MAX_NR_ZONES - 1); } for (node = 0; node < local_node; node++) { if (!node_online(node)) continue; j = build_zonelists_node(NODE_DATA(node), zonelist, j, MAX_NR_ZONES - 1); } zonelist->_zonerefs[j].zone = NULL; zonelist->_zonerefs[j].zone_idx = 0; }

注意到最后一个_zonerefs元素的zone被设置为NULL,zone_idx为0,这是用来遍历时判断结尾。

13.10. build_zonelists_node

static int build_zonelists_node(pg_data_t *pgdat, struct zonelist *zonelist, int nr_zones, enum zone_type zone_type) { struct zone *zone; BUG_ON(zone_type >= MAX_NR_ZONES); zone_type++; 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; }

build_zonelists_node针对单个存储节点初始化分配器的管理区列表。zone_type用来指明当前处理的管理区的个数,通常它就是 MAX_NR_ZONES - 1。nr_zones则指明了将被填充的_zonerefs管理区备用链表的索引,通常该索引之前的成员已经按内存优先级进行了赋值。循环中 zone_type一直递减,而nr_zones索引一直递减,所以在UMA系统中,编号越大的管理区类型优先级越高,将被最先挂载到备用链表。通常 ZONE_HIGHMEM的优先级最高,而ZONE_DMA的优先级最低。所以分配器在分配内存的时候通常在ZONE_HIGHMEM中分配。 populated_zone函数用来确保该区的可用内存页(present_pages)有效,也即大于0。在内存少于896MB的系统 上,ZONE_HIGHMEM的有效内存页就为0,此时只有到ZONE_NORMAL或者ZONE_DMA区分配内存。

? ? ? ? ?

pgdat:当前存储节点对应的pg_data_t类型描述符。

zonelist:当前存储节点对应的管理区列表,通常它通过就是pgdat->node_zonelists + 0。

nr_zones:当前开始处理的管理区在存储节点中的编号,每处理一个管理区那么该值加1。

zone_type:指定处理几个管理区。通常为MAX_NR_ZONES - 1。 返回下一个未处理的管理区的索引值。

build_zonelists_node完成了以下功能,当未开启CONFIG_NUMA时,依次映射管理区到参考链表中。

判定提供的zone_type参数是否正确,它不应该超过一个管理节点所能包含的最大管理区的个数。

? populated_zone是一个简单的宏:!!zone->present_pages,由于present_pages参数在free_area_init_core 被初始化为该节点中可用的内存页数realsize,所以这里的意图就是保证当天节点是否已经被初始化。

? zoneref_set_zone 设置管理节点中的成员zoneref,它记录当前管理区的地址和在管理区数组node_zones中的索引。显然在循环中,所有的初始化过的zone都是 依次从node_zones出来,放入_zonerefs的,这个顺序是倒序的。build_zonelists_node函数跟是否配置 CONFIG_NUMA相关,启用该功能将使用另一个同名函数,此函数将根据Node的各个要素权衡它们在链表中的顺序。

? check_highest_zone函数只在CONFIG_NUMA中有效。

?

static void zoneref_set_zone(struct zone *zone, struct zoneref *zoneref)

{ zoneref->zone = zone; zoneref->zone_idx = zone_idx(zone); }

注意:zoneref同时定义zone的地址和索引,看起来多此一举,这是因为在NUMA中,可能将另一个Node中的zone加入本Node中的参考链表中。


Linux内存管理详解(4).doc 将本文的Word文档下载到电脑 下载失败或者文档不完整,请联系客服人员解决!

下一篇:2018部编人教版语文一年级下册《姓氏歌》说课稿

相关阅读
本类排行
× 注册会员免费下载(下载后可以自由复制和排版)

马上注册会员

注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信: QQ: