前言
不是特別懂,但是先把自己目前的理解寫下來(甚至目前的有些理解都是錯的),隨著時間的積累再豐富;
記憶體模型
有三種記憶體模型:
- Flat Memory:所有記憶體都是連續的,透過陣列管理所有的page,陣列的下標就是pfn;所有可以很簡單的pfn下標找到對應的page,page可以很簡單的找到pfn;
- Discontiguous Memory:非連續記憶體;沒有深入研究,透過node管理page,一個node中page都是連續的;
- Sparse Memory:稀疏記憶體模型;集中關注這裡;
sparse memory
管理思想
存在一個struct mem_section **mem_section
的全域性變數,該變數是一個二級指標;
sparse_index_init
函式可以看出mem_section
的組織形式;
sparse_index_init
中使用sparse_index_alloc
分配struct mem_section
的記憶體,然後賦值給mem_section全域性變數進行儲存;
- 所以首先可以確定
*mem_section
指向了一個實際的地址,那*mem_section
是一個簡單的變數地址,還是一個陣列的地址呢?(C語言一個指標比如int *p,這個p可能是一個變數的地址,也有可能是一個陣列的地址)
見sparse_index_alloc
可以瞭解
裡面這個記憶體分配的大小是 SECTIONS_PER_ROOT * sizeof(struct mem_section)
,也就是說這個一級指標指向了一個實際的陣列,這個陣列的長度是 SECTIONS_PER_ROOT;可以同時計算出array_size是一個page的大小;
那麼mem_section
的組織形式就出來了:
如何透過mem_section,進行pfn和page的相互轉化
mem_section的section_mem_map存放了該section的起始page(__section_mem_map_addr)
所以__section_mem_map_addr + pfn就是對應的page;
page-__section_mem_map_addr = pfn;
原始碼實現
建立
arm64_memory_present中遍歷memblock,進行memory_present,memory_present中完成了記憶體的建立
初始化
見sparse_init
,->sparse_init_nid
:
struct page *map,估計就是page,spares_init_one_section,ms->section_mem_map進行了初始page的賦值;
no-map 為什麼會報錯
現象:
- 使用phys_to_page獲取page;再使用page_to_phys將page轉化為phys,出錯;
page_to_phys 核心是 __page_to_pfn->page_to_section,page_to_section獲取section依賴於page->flag;訪問flga成員時候出先空指標; - 使用者空間分配一個較大的全域性變數時,沒有該問題;
原因分析
- no-map的記憶體並沒有加入夥伴系統,可以理解為no-map自己實現了一套記憶體管理方法,它的記憶體管理只是簡單的bitmap置位管理,虛擬地址是透過ioremap獲取的,與實體地址是一個簡單的偏移;
- 在
__fdt_scan_reserved_mem
掃描reserved-memory
->__reserved_mem_reserve_reg
的時候,會把no-map記憶體屬性的memory從memblock移除掉(early_init_dt_reserve_memory_arch
),不是no-map
的進行reserve; arm64_memory_present
只會對memblock管理的地址進行pfn對應section的建立,所以no-map沒有進行管理,進而使用page_to_pfn就會出錯;
可以從兩個方面理解:1是它沒有實際的page;2是它並沒有被memblock管理然後建立;
針對現象進行分析
瞭解了原因之後,分析為什麼有這麼一個現象;
1是沒有實際的page
phys_to_page得到的一個page就是一個錯誤的page,誤打誤撞到了使用者空間的一個地址,所以當使用者空間有一個較大的全域性變數時,就不會報錯;但是結果有錯;
2是它並沒有被memblock管理然後建立
由於page是一個錯誤的page,那後面的訪問flag操作就自然而然錯誤了;
解決方法
直接獲取實體地址
不使用pfn_to_page,直接特殊判斷使用實體地址;