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的情况:

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

 

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

 

2 discontiguous memory model

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

 

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

3 sparse memory model

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

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

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

 

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

 

4 代码分析

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

  1. 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,这个偏移和架构相关。

  1. 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。

  1. 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这两个宏,就好理解了。

 

 

 

相关文章:

  • 2021-10-10
  • 2021-05-10
  • 2022-01-24
  • 2021-06-27
  • 2022-12-23
  • 2022-12-23
  • 2021-06-16
猜你喜欢
  • 2019-09-15
  • 2022-02-23
  • 2021-06-28
  • 2021-07-30
  • 2021-04-11
  • 2022-12-23
  • 2021-06-13
相关资源
相似解决方案