统计记忆信息实验要求的实验总结
当Linux内核管理内存时,结构页面对应于物理页面框架。 struct page * mem_map管理系统中的所有页面,这些页面可以视为页面数组。当前的要求是获取系统中有多少个结构页,以查看它们是否与您的物理内存相对应;其中有多少是免费的;有多少处于PG_reserved状态;有多少个处于PG_swapcache状态;共享了多少。
Linux内核使用struct页面结构来存储有关物理页面框架的各种信息。结构页的组织和物理页框架的组织与内存模型密切相关。配置linux时,共有三种内存模型可供选择。
因此,应该结合特定的内存模型讨论该实验。下面介绍这三种内存模型,并详细讨论稀疏内存。
1.扁平存储器
平面内存模型意味着只有一个内存节点,并且内存节点内的物理地址是连续的,即没有孔。对于此内存模型,内核通过全局struct page * mem_map数组存储所有页面,以实现管理物理页面的目的。我们将不在这里详细讨论。
2.不连续的记忆
不连续内存是指多个内存节点。内存节点之间的物理地址可能不连续,但是节点内部的物理地址是连续的。对于此内存模型,与每个节点相对应的pg_data_t结构中的node_mem_map成员指向页面数组,在此不再赘述。因为与稀疏内存模型相比,前两个内存模型结构相对简单,所以我们将重点放在最后一个内存模型上。
3.稀疏内存
引入稀疏内存模型的原因与热插拔有关。理论上支持的最大物理内存以节为单位进行分段。每次完成后,与每个节对应的物理内存可能会或可能不会实际存在。热插拔会调整相应的节,例如设置一些标志,分配或回收section_memmap等。
3. 0本节的基本介绍
文件
定义了MAX_PHYSMEM_BITS,SECTION_SIZE_BITS和其他宏,这些宏定义了内存模型支持的最大物理内存和节的大小。
#define SECTION_SIZE_BITS 27 /* matt - 128 is convenient right now */
#define MAX_PHYSADDR_BITS 44
#define MAX_PHYSMEM_BITS 46您只需通过SECTION_SIZE_BITS即可计算出2 ^ 27B或128MB的分区大小。
通过PAGE_SHIFT
可以看到一个部分包含2 ^ 15或0x8000页。
#define PAGE_SHIFT 12使用以下两个宏来定义部分总数:
mmzone.h
#define NR_MEM_SECTIONS (1UL << SECTIONS_SHIFT)page-flags-layout.h
#ifdef CONFIG_SPARSEMEM
#include
/* SECTION_SHIFT #bits space required to store a section # */
#define SECTIONS_SHIFT (MAX_PHYSMEM_BITS - SECTION_SIZE_BITS)
#endif /* CONFIG_SPARSEMEM */
编译时,已经根据最大支持的物理内存设置了节的总数,但是实际的物理内存通常会比最大支持的物理内存小得多。在这种情况下,只需要超出实际的物理内存即可。内存部分可以看作是普通孔。

例如,如果下图中第二节所对应的物理内存不存在或不可用(以下统称为不可用),则将其标记为不存在(特别是如何标记该节) 3. 1和3. 2中的结构将在初始化中提及)。对于每个n + 1节及以后的部分,对超出实际物理内存最大数量的部分执行相同的操作,因此可以理解,n + 1节之后的部分是一个巨大的漏洞。

内核还提供了用于在段号和pfn之间转换的宏:
#define pfn_to_section_nr(pfn) ((pfn) >> PFN_SECTION_SHIFT)
#define section_nr_to_pfn(sec) ((sec) << PFN_SECTION_SHIFT)3. 1使用的数据结构和变量3. 1. 1 mem_section结构
include / linux / mmzone.h
struct mem_section {
unsigned long section_mem_map;
unsigned long *pageblock_flags;
#ifdef CONFIG_PAGE_EXTENSION
struct page_ext *page_ext;
unsigned long pad;
#endif
};
这里,我们主要关心的是section_mem_map成员,该成员不仅包含与该部分相对应的mem_map信息,而且还包含其他一些信息:
/*
* We use the lower bits of the mem_map pointer to store
* a little bit of information. There should be at least
* 3 bits here due to 32-bit alignment.
*/
#define SECTION_MARKED_PRESENT (1UL<<0)
#define SECTION_HAS_MEM_MAP (1UL<<1)
#define SECTION_MAP_LAST_BIT (1UL<<2)
#define SECTION_MAP_MASK (~(SECTION_MAP_LAST_BIT-1))
section_mem_map的bit_0指示与该节对应的物理内存当前是否可用,而bit_1指示该节是否具有对应的mem_map。
在判断pfn或节是否可用时使用SECTION_HAS_MEM_MAP:
static inline int valid_section(struct mem_section *section)
{
return (section && (section->section_mem_map & SECTION_HAS_MEM_MAP));
}
static inline int valid_section_nr(unsigned long nr)
{
return valid_section(__nr_to_section(nr));
}
static inline int pfn_valid(unsigned long pfn)
{
/*先由pfn得到其所在的section号,在通过判断section是否可用来得出pfn是否可用*/
if (pfn_to_section_nr(pfn) >= NR_MEM_SECTIONS)
return 0;
return valid_section(__nr_to_section(pfn_to_section_nr(pfn)));
}可以看出,最后将通过判断与section_nr对应的段结构是否存在以及在该段结构的section_mem_map成员中是否设置了SECTION_HAS_MEMMAP标志来进行判断。
这里涉及一个问题:__nr_to_section()的实现
mmzone.h
static inline struct mem_section *__nr_to_section(unsigned long nr)
{
if (!mem_section[SECTION_NR_TO_ROOT(nr)])
return NULL;
return &mem_section[SECTION_NR_TO_ROOT(nr)][nr & SECTION_ROOT_MASK];
}这导致用于维护节结构的mem_section类型全局二维数组mem_section。
3. 1. 2 mem_section全局数组
根据是否设置了CONFIG_SPARSEMEM_EXTREME选项,mem_section的定义略有不同,代码如下:
mmzone.h
#ifdef CONFIG_SPARSEMEM_EXTREME
#define SECTIONS_PER_ROOT (PAGE_SIZE / sizeof (struct mem_section))
#else
#define SECTIONS_PER_ROOT 1
#endif
#define SECTION_NR_TO_ROOT(sec) ((sec) / SECTIONS_PER_ROOT)
#define NR_SECTION_ROOTS DIV_ROUND_UP(NR_MEM_SECTIONS, SECTIONS_PER_ROOT)
#define SECTION_ROOT_MASK (SECTIONS_PER_ROOT - 1)
#ifdef CONFIG_SPARSEMEM_EXTREME
extern struct mem_section *mem_section[NR_SECTION_ROOTS];
#else
extern struct mem_section mem_section[NR_SECTION_ROOTS][SECTIONS_PER_ROOT];
#endif对于一般的稀疏内存,直接定义一个二维数组。同时,由于SECTIONS_PER_ROOT == 1,因此实际上与一维数组没有区别。关键是所有部分结构都一起应用。
超级稀疏内存和普通稀疏内存之间的区别在于,如果直接定义,则与大多数部分相对应的物理内存可能不可用(对于PC而言,实际物理内存与理论支持相差太大)全局数组显然会导致大量内存浪费。在这种情况下,内核采用的方法是只定义一个mem_section *类型的全局数组,然后根据初始化期间的实际情况决定是否分配一个用于存储mem_struction结构的数组。在这里,我们在3. 2. 1中进行了详细讨论。
简而言之,所有节都分为几个ROOT,可以通过section_nr / SECTIONS_PER_ROOT获得section_nr的ROOT。然后通过section_nr和SECTION_ROOT_MASK获取ROOT中section_nr的下标,然后可以找到相应的mem_section结构。

3. 2初始化内存模型
直接从pageing_init看,先前的内存检测已完成,并且已确定可用的内存段(区域)。 Start_pfn〜end_pfn用于表示区域。
arch / x86 / mm / init_6 4. c
void __init paging_init(void)
{
sparse_memory_present_with_active_regions(MAX_NUMNODES);
sparse_init();
//...
}mem_section的初始化和mem_map的建立由两个函数完成:sparse_memory_present_with_active_regions(MAX_NUMNODES);和sparse_init();。
3. 2. 1 sparse_memory_present_with_active_regions()
此功能主要完成两项任务:
1.完成mem_section数组的应用程序(此步骤仅适用于超稀疏内存)。
2.设置mem_section结构的SECTION_MARKED_PRESENT标志,并且几乎所有后续初始化操作都必须基于此标志。
mm / page_alloc.c
void __init sparse_memory_present_with_active_regions(int nid)
{
unsigned long start_pfn, end_pfn;
int i, this_nid;
/*对内存探测得出的每个region调用memory_present()*/
for_each_mem_pfn_range(i, nid, &start_pfn, &end_pfn, &this_nid)
memory_present(this_nid, start_pfn, end_pfn);
}mm / sparse.c
/* Record a memory area against a node. */
void __init memory_present(int nid, unsigned long start, unsigned long end)
{
unsigned long pfn;
start &= PAGE_SECTION_MASK;
mminit_validate_memmodel_limits(&start, &end); /*确定可用内存的最大最小pfn*/
for (pfn = start; pfn < end; pfn += PAGES_PER_SECTION) { //以section为单位
unsigned long section = pfn_to_section_nr(pfn); //通过pfn计算section号
struct mem_section *ms;
/*对于一般的稀疏内存,sparse_index_init()为空。
对于超稀疏内存:
已经定义了一个全局的mem_section *mem_section[NR_SECTION_ROOTS]的数组,
这里为可用物理内存对应ROOT的mem_section数组申请空间
*/
sparse_index_init(section, nid);
/*建立section和node的对应关系*/
set_section_nid(section, nid);
/*通过section_nr得到对应的mem_section结构体,看了后边mem_section初始化,
再结合__nr_to_section()的实现就会明白如何转换的,感兴趣的自己看看*/
ms = __nr_to_section(section);
/*设置section的标识SECTION_MARKED_PRESENT,表示该section是存在的*/
if (!ms->section_mem_map)
ms->section_mem_map = sparse_encode_early_nid(nid) |
SECTION_MARKED_PRESENT;
}
}
sparse_index_init(section,nid)完成了主要工作:
mm / sparse.c
#ifdef CONFIG_SPARSEMEM_EXTREME
/*
具体的分配细节,有兴趣的可以看看
*/
static struct mem_section noinline __init_refok *sparse_index_alloc(int nid)
{
struct mem_section *section = NULL;
unsigned long array_size = SECTIONS_PER_ROOT *
sizeof(struct mem_section);
if (slab_is_available()) {
if (node_state(nid, N_HIGH_MEMORY))
section = kzalloc_node(array_size, GFP_KERNEL, nid);
else
section = kzalloc(array_size, GFP_KERNEL);
} else {
section = memblock_virt_alloc_node(array_size, nid);
}
return section;
}
/*主要看看这个就行了*/
static int __meminit sparse_index_init(unsigned long section_nr, int nid)
{
unsigned long root = SECTION_NR_TO_ROOT(section_nr);//确定section_nr所在的root
struct mem_section *section;
/*如果root对应的mem_section数组已存在,则返回“已存在”*/
if (mem_section[root])
return -EEXIST;
// 调用sparse_index_alloc()申请分配内存块,用于存放root的mem_section数组.
section = sparse_index_alloc(nid);
if (!section)
return -ENOMEM;
/*将新建的mem_section数组首地址存入与mem_section[root]*/
mem_section[root] = section;
return 0;
}
#else到目前为止,已经申请了mem_section数组。需要注意的一件事是,应用的空间大小固定为SECTIONS_PER_ROOT * sizeof(struct mem_section),即,在分配mem_section数组时,每个SECTIONS_PER_ROOT节都被分配在一起。
这可能会导致情况。如果区域的start_pfn或end_pfn不只是SECTIONS_PER_ROOT节的整数倍,则将为部分不可用节分配mem_section结构。如下图所示,区域的start_pfn是ROOT2中间的物理页,灰色部分表示不可用的物理内存:

换句话说,只要某个ROOT中有可用的物理页面,则具有整个ROOT的节将被分配相应的mem_section结构。并且仅当图中的ROOT1不包含可用的物理页面时,才会为该部分分配mem_section结构。
3. 2. 2 sparse_init()
首先发布代码,删除一些不相关的部分:
mm / sparse.c
sparse_init()中有两个主要函数,alloc_usemap_and_mem_map()和sparse_init_one_section()。

alloc_usemap_and_mem_map()函数用于分配mem_map数组,而实际的分配函数由函数指针指定。这是因为usemap和mem_map的分配是通过alloc_usemap_mem_map()进行的,但是所使用的分配函数不同,这里为了简化问题,省略了usemap的描述。这里我们不深入讨论该功能,下图仅给出执行结果:

然后为每个可用部分调用sparse_init_one_section(),内容相对简单:
mm / sparse.c
static int __meminit sparse_init_one_section(struct mem_section *ms,
unsigned long pnum, struct page *mem_map,
unsigned long *pageblock_bitmap)
{
if (!present_section(ms))
return -EINVAL;
/将section_mem_map成员的“地址段”清零/
ms->section_mem_map &= ~SECTION_MAP_MASK;
/*将之前保存在map_map中的mem_map的地址和section的其实pfn一起编码得到section_mem_map的“地址段”,
从section_mem_map成员获得mem_map的地址可以通过sparse_decode_mem_map()函数;
同时,设置SECTION_HAS_MEM_MAP标志*/
ms->section_mem_map |= sparse_encode_mem_map(mem_map, pnum) |
SECTION_HAS_MEM_MAP;
ms->pageblock_flags = pageblock_bitmap;
return 1;
}3. 2. 3 vmemmap
对于稀疏内存,如果设置了CONFIG_SPARSEMEM_VMEMMAP,则mem_map的处理略有不同。该主题在mm / sparse-vmemmap.c文件中实现。
void __init sparse_mem_maps_populate_node(struct page **map_map,
unsigned long pnum_begin,
unsigned long pnum_end,
unsigned long map_count, int nodeid)
{
//...
}主要任务是建立vmemmap数组中每个元素的地址与实际存储单元之间的页表映射。
vmemmap阵列:内核空间中从VMEMMAP_START开始的1TB空间用于虚拟内存映射,这是一个虚拟页阵列。
此功能完成的功能适用于所有可用节,假设可用节的编号为pnum,则使用vmemmap [pnum]分配实际存储单元并建立页表映射。另0xc8000〜0xcffff部分为例,因为它包含可用区域(0xcafff000、0xcaffffff)(由BIOS-e820提供,然后内核会将其一部分更改为保留区域,但仍有部分可用) ,因此整个部分全部可用。
2016年11月22日
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/shoujiruanjian/article-356455-1.html
就导弹放放吧