1. 概述
接著上一篇《Linux核心原始碼分析之setup_arch (一)》繼續分析,本文首先分析arm_memblock_init函式,然後分析核心啟動階段的是如何進行記憶體管理的。
2. arm_memblock_init
該函式的功能比較簡單,主要就是把meminfo中記錄的記憶體條資訊新增到memblock.memory中,然後把核心映象所在記憶體區域新增到memblock.reserved中,arm_mm_memblock_reserve把頁表所在記憶體區域新增到memblock.reserved中;如果使用了裝置樹,則使用arm_dt_memblock_reserve來保留所佔用的記憶體,最後則是呼叫CPU相關的mdesc->reserve,其對應的呼叫為cpu_mem_reserve,該函式定義在cpu.c中。
/* arch/arm/mm/init.c */
void __init arm_memblock_init(...) {
for (i = 0; i < mi->nr_banks; i++)
memblock_add(mi->bank[i].start, mi->bank[i].size);
memblock_reserve(__pa(_stext), _end - _stext);
arm_mm_memblock_reserve();
arm_dt_memblock_reserve();
if (mdesc->reserve)
mdesc->reserve();
arm_memblock_steal_permitted = false;
memblock_allow_resize();
memblock_dump_all();
}
/* include/kernel/memblock.h */
struct memblock {
phys_addr_t current_limit;
struct memblock_type memory;
struct memblock_type reserved;
};
3. memblock_alloc
接下來就該執行paging_init函式了,在分析paging_init之前先來點核心啟動階段的記憶體管理相關的內容。從arm_memblock_init開始引入memblock資料結構,其作用是實現核心啟動初期的記憶體管理功能,嚴格來說,其生命週期到paging_init::bootmem_init為止,memblock_alloc呼叫流程如下。
實際查詢空閒記憶體的函式為memblock_find_in_range_node,而該函式中真正實現空閒記憶體查詢的是for_each_free_mem_range_reverse這個巨集定義。
/* mm/memblock.c */
phys_addr_t memblock_find_in_range_node(...)
{
...
for_each_free_mem_range_reverse(i, nid, &this_start, &this_end, NULL) {
...
if (cand >= this_start)
return cand;
}
return 0;
}
該巨集定義如下,然而其中又巢狀了一個函式Orz...
/* include/linux/memblock.h */
#define for_each_free_mem_range_reverse(i, nid, p_start, p_end, p_nid) \
for (i = (u64)ULLONG_MAX, \
__next_free_mem_range_rev(&i, nid, p_start, p_end, p_nid); \
i != (u64)ULLONG_MAX; \
__next_free_mem_range_rev(&i, nid, p_start, p_end, p_nid))
首先需要說明的是,memblock.reserved標識的區域表示的是已被佔用的記憶體區域,memblock.memory中記錄的是記憶體條資訊。現在回到__next_free_mem_range_rev函式,程式碼段(1)(2)的目的是找出記憶體條上兩個reserved區域之間的記憶體區域,即空閒區域。找到之後再經過程式碼段(3)對空閒區域的起始地址和結束地址進行修正,因為程式碼段(1)(2)只能保證空閒區與當前記憶體條存在交集,並不能保證該空閒區域完全處於當前記憶體條之中,主要原因在於無法保證這兩個reserved區域都在當前記憶體條上。
/* mm/memblock.c */
void __init_memblock __next_free_mem_range_rev(...)
{
struct memblock_type *mem = &memblock.memory;
struct memblock_type *rsv = &memblock.reserved;
...
/* (1) */
for ( ; mi >= 0; mi--) {
struct memblock_region *m = &mem->regions[mi];
phys_addr_t m_start = m->base;
phys_addr_t m_end = m->base + m->size;
...
/* (2) */
for ( ; ri >= 0; ri--) {
struct memblock_region *r = &rsv->regions[ri];
phys_addr_t r_start = ri ? r[-1].base + r[-1].size : 0;
phys_addr_t r_end = ri < rsv->cnt ? r->base : ULLONG_MAX;
...
/* (3) */
if (m_end > r_start) {
if (out_start)
*out_start = max(m_start, r_start);
if (out_end)
*out_end = min(m_end, r_end);
if (out_nid)
*out_nid = memblock_get_region_node(m);
...
return;
}
}
}
*idx = ULLONG_MAX;
}
至此,空閒區域的查詢基本就結束了,回到memblock_find_in_range_node函式中,再檢查一下該區域的起始地址和結束地址是否合法等等,最終就申請到了所請求大小的記憶體區域,最後只需要將這塊記憶體區域標記為reserved狀態就結束了記憶體分配的整個過程了。
/* mm/memblock.c */
int memblock_reserve(phys_addr_t base, phys_addr_t size)
{
struct memblock_type *_rgn = &memblock.reserved;
return memblock_add_region(_rgn, base, size, MAX_NUMNODES);
}
4. 總結
- arm_memblock_init函式首先把記錄在meminfo記錄的記憶體條資訊轉移到memblock.memory中,然後把已經使用的記憶體區域記錄到memblock.reserved中,主要包括核心映象所佔用區域、頁表區域以及裝置樹;
- memblock_alloc通過memblock中的memory和reserved中記錄的資訊進行記憶體管理,每次申請到記憶體之後都在memblock.reserved中進行記錄。