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