linux 3个内存模型(flat memory model、discontiguous memory model、sparse memory model)

    xiaoxiao2023-10-24  170

    linux内核支持3中内存模型,分别是flat memory model、discontiguous memory model和sparse memory model。所谓memory model,就是在操作系统层面,用什么样的方式来管理这些物理内存。

     

    1 flat memory model

    如果从系统中任意一个处理器角度看,当它访问物理内存时,物理地址空间是一个连续的,没有空洞的地址空间,那么这种计算机系统的内存模型就是flat memory。这种内存模型下,物理内存管理比较简单,每一个物理页帧都会有一个page数据结构来抽象,因此系统中存在一个struct page的数组mem_map,每个数组指向一个实际的物理页帧。在flat memory下,PFN(page frame number)和mem_map数组index是线性的(有的系统会有一个固定便宜PFN_OFFSET),如果内存对应的物理地址为0,那PFN就是数组的index。

    对于flat memory model,节点struct pglist_data只有一个(为了和discontiguous memory model采用相同的机制)。下图描述了flat memory的情况:

     

     

    Mem_map 位于内核空间的直接映射区,因此无需为其建立页表。

     

    2 discontiguous memory model

    如果cpu在访问物理内存时,其地址空间有一些空洞,是不连续的,那么这种计算机系统的内存模型就是discontiguous memory。一般而言,numa架构的计算机系统的memory model都选择discontiguous memory。不过这两个概念是不同的,numa强调的是cpu和memory的位置关系,和内存模型模型实际上没有关系,只不过同一个node上的cpu和memory访问速度更快,因此需要多个node来管理。

     

    3 sparse memory model

    Sparse memory model是为了解决memory hotplug而生的。

    下图说明sparse memory是如何管理内存的的。

     

    整个连续的物理地址空间划分成一个个section,内个section内部,其memory是连续的,因此,mem_map的page数组依附于section结构,而不是node结构了。无论哪一种内存模型,都需要处理PFN与page之间的对应关系,只不过sparse多了一个section的概念,让转换变成了PFN<->section<->page.

     

    4 代码分析

    这里主要讨论PFN与page之间的转换,主要代码在include/asm-generic/memory_model.h中。

    Flat memory代码如下:

    #define __pfn_to_page(pfn)        (mem_map + ((pfn) - ARCH_PFN_OFFSET))

    #define __page_to_pfn(page)        ((unsigned long)((page) - mem_map) + \

     ARCH_PFN_OFFSET)

    由代码可知,PFN和struct page数组mem_map index成线性关系,有一个固定的偏移就是ARCH_PFN_OFFSET,这个偏移和架构相关。

    Discontiguous memory代码如下:

    #define __pfn_to_page(pfn)                        \

    ({        unsigned long __pfn = (pfn);                \

    unsigned long __nid = arch_pfn_to_nid(__pfn);  \

    NODE_DATA(__nid)->node_mem_map + arch_local_page_offset(__pfn, __nid);\

    })

     

    #define __page_to_pfn(pg)                                                \

    ({        const struct page *__pg = (pg);                                        \

    struct pglist_data *__pgdat = NODE_DATA(page_to_nid(__pg));        \

    (unsigned long)(__pg - __pgdat->node_mem_map) +                        \

     __pgdat->node_start_pfn;                                        \

    })

    Discontiguous memory model需要获取node id,只要找到node id,就可以找到对应的pglist_data数据结构,该数据结构node_start_pfn记录了该node的第一个page frame number,因此,根据pfn就可以找到在该node中的偏移,从而得到对应的page结构,或者根据page结构,得到对应node的pglist->node_mem_map,从而得到在该node中的偏移(page-pglist->node_mem_map),加上该node的pglist->node_start_pfn,即得到PFN。

    Sparse memory代码如下:

    #define __page_to_pfn(pg)                                        \

    ({        const struct page *__pg = (pg);                                \

    int __sec = page_to_section(__pg);                        \

    (unsigned long)(__pg - __section_mem_map_addr(__nr_to_section(__sec)));        \

    })

     

    #define __pfn_to_page(pfn)                                \

    ({        unsigned long __pfn = (pfn);                        \

    struct mem_section *__sec = __pfn_to_section(__pfn);        \

    __section_mem_map_addr(__sec) + __pfn;                \

    })

    以__page_to_pfn为例,首先,通过pg找到对应的section,然后得到section中分配的mem_map地址,pg-mem_map即得到,这里section的mem_map是经过编码的,即section[i].section_mem_map = mem_map地址-start_pfn.

     

    编码过程是在初始化section时进行的,

    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;

     

    ms->section_mem_map &= ~SECTION_MAP_MASK;

    ms->section_mem_map |= sparse_encode_mem_map(mem_map, pnum) |

    SECTION_HAS_MEM_MAP;

             ms->pageblock_flags = pageblock_bitmap;

     

    return 1;

    }

    由于在初始化编码中,得到的section_mem_map是分配的mem_map地址-start_pfn, 在回过头看__page_to_pfn和__pfn_to_page这两个宏,就好理解了。

     

     

     

    最新回复(0)