天天看點

linux 3個記憶體模型(flat memory model、discontiguous memory model、sparse memory model)

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這兩個宏,就好了解了。

繼續閱讀