b2科目四模拟试题多少题驾考考爆了怎么补救
b2科目四模拟试题多少题 驾考考爆了怎么补救

统计内存信息实验的总结实验要求内核在管理内存

电脑杂谈  发布时间:2021-02-12 19:02:28  来源:网络整理

统计记忆信息实验要求的实验总结

当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 */

编译时,已经根据最大支持的物理内存设置了节的总数,但是实际的物理内存通常会比最大支持的物理内存小得多。在这种情况下,只需要超出实际的物理内存即可。内存部分可以看作是普通孔。

工程测量实验实习指导与报告_m7400打印机内存清零_内存清零实验报告

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

image

内核还提供了用于在段号和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结构。

m7400打印机内存清零_工程测量实验实习指导与报告_内存清零实验报告

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中间的物理页,灰色部分表示不可用的物理内存:

image

换句话说,只要某个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()。

工程测量实验实习指导与报告_内存清零实验报告_m7400打印机内存清零

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

image

然后为每个可用部分调用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

    相关阅读
      发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

      热点图片
      拼命载入中...