內(nèi)存分配器memblock
背景
在Linux內(nèi)核開發(fā)過程中, 多少都會(huì)存在一個(gè)patch, 引入了遠(yuǎn)超預(yù)期的麻煩. 內(nèi)核2.6.34開發(fā)過程中, 這個(gè)獎(jiǎng)項(xiàng)非CONFIG_NO_BOOTMEM莫屬
bootmem本身是個(gè)簡(jiǎn)單的,低級(jí)的內(nèi)存分配器. 在引導(dǎo)程序的初期用來分配內(nèi)存. 有人可能會(huì)想, 沒有必要再增加一個(gè)內(nèi)存分配器, 但是由于內(nèi)存管理代碼在被調(diào)用前需要很多內(nèi)核功能都準(zhǔn)備好, 要想在啟動(dòng)初期使用內(nèi)存管理代碼會(huì)大大增加內(nèi)存管理的復(fù)雜性. 在x86架構(gòu)上, 會(huì)首先使用early_res機(jī)制接替BIOS e820的工作, 然后再交給架構(gòu)獨(dú)立的bootmem分配器, 最后才是全功能的buddy allocator
ingHai LU認(rèn)為可以把bootmem從這個(gè)過程中去掉, 簡(jiǎn)化這個(gè)過程. 結(jié)果就是, 增加了一堆patch來擴(kuò)展early_res機(jī)制, 把本該交給bootmem做的事情都做了, 然后直接到buddy分配器. 這些修改被合入了2.6.34, 老的基于bootmem的代碼仍然保留. CONFIG_NO_BOOTMEM用來控制使用哪個(gè)分配器, 缺省情況下并不使用bootmem
一切本該非常美好, 但是隨著kernel rc版本的發(fā)布, 新分配器測(cè)試陸續(xù)爆出很多問題, Linus發(fā)了一封郵件要求revert掉整個(gè)patch. 雖然簡(jiǎn)化代碼這個(gè)注意看起來不錯(cuò), 但是rc3仍然導(dǎo)致系統(tǒng)死機(jī), 以及大量使用ifdef, 以及缺省打開CONFIG_NO_BOOTMEM, 導(dǎo)致了社區(qū)的怨氣.
正常情況下, 新功能缺省情況下是不使能的. 新kernel應(yīng)該盡最大可能和之前的kernel保持一致. 而CONFIG_NO_BOOTMEM缺省打開導(dǎo)致了很大的改變和問題. Yinghai在2.6.35基礎(chǔ)上又提交了一組patch, 使用logical memory block分配器替代early_res代碼, 這組patch看起來比刪除bootmem引入了更大的風(fēng)險(xiǎn) --https://lwn.net/Articles/382559/
在Yinghai刪除bootmem patch的review過程中, 一些reviewers質(zhì)疑為什么x86不使用logical memory block(LMB)分配器替換early-res的代碼. 當(dāng)X86使用類似brk()形式的early-res時(shí) Microblaze, PowerPC, SuperH和SPARC架構(gòu)已經(jīng)使用LMB進(jìn)行系統(tǒng)啟動(dòng)初期的內(nèi)存分配, 所以LMB可以看做是一個(gè)generic的解決方案. 使用通用代碼的好處是顯而易見的: 更多的人review代碼, 總體維護(hù)代價(jià)更帶. 所以使用LMB明顯更合理.
因此Yinghai在2.6.35上又提交了一組patch來簡(jiǎn)化啟動(dòng)分配器代碼
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個(gè)人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)? ??


Data structure
struct memblock { ?
? ?bool bottom_up; ?/* is bottom up direction? */
? ?phys_addr_t current_limit;
? ?struct memblock_type memory;
? ?struct memblock_type reserved;
};
bootom_up 設(shè)置為true時(shí), 允許內(nèi)存分配使用bottom-up模式
current_limit memblock分配內(nèi)存時(shí)的上限
memory 描述了當(dāng)前內(nèi)存區(qū)包含的內(nèi)存區(qū)數(shù)目, 總大小, 以及每個(gè)內(nèi)存region
reserved 描述了當(dāng)前內(nèi)存塊已經(jīng)分配的內(nèi)存區(qū)數(shù)目, 總大小,以及每個(gè)內(nèi)存region. 在reserved中描述的地址范圍, 表示不可以再被memblock分配. memory用來描述memblock全部?jī)?nèi)存region(不區(qū)分分配和未分配), reserved用來描述memblock中已經(jīng)分配的內(nèi)存region
struct memblock_type {
? ?unsigned long cnt; ?/* number of regions */
? ?unsigned long max; ?/* size of the allocated array */
? ?phys_addr_t total_size; /* size of all regions */
? ?struct memblock_region *regions;
};
cnt regions數(shù)目
max 最大regions數(shù)目
total_size regions總尺寸
regions regions array
struct memblock_region {
? ?phys_addr_t base;
? ?phys_addr_t size;
? ?unsigned long flags;
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
? ?int nid;
#endif
};
base region 基地址
size region size
flags region
memblock initialization
static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
struct memblock memblock __initdata_memblock = {
? ?.memory.regions ? ? = memblock_memory_init_regions,
? ?.memory.cnt ? ? = 1, ? ?/* empty dummy entry */
? ?.memory.max ? ? = INIT_MEMBLOCK_REGIONS,
? ?.reserved.regions ? = memblock_reserved_init_regions,
? ?.reserved.cnt ? ? ? = 1, ? ?/* empty dummy entry */
? ?.reserved.max ? ? ? = INIT_MEMBLOCK_REGIONS,
? ?.bottom_up ? ? ?= false,
? ?.current_limit ? ? ?= MEMBLOCK_ALLOC_ANYWHERE,
};
.memory.regions 和reserved.regions固定數(shù)組, 最多支持128個(gè)regions
memory.mx和reserved.max的最大值也為128
current_limit 也被定義為最大可能物理地址
memblock API 在include/linux/memblock.h中定義了memblock的API
int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)
{
? ?return memblock_reserve_region(base, size, MAX_NUMNODES, 0);
}
memblock_reserve API主要是為系統(tǒng)啟動(dòng)階段為kernel(text, data and initrd), swapper_pg_dir, reserved-memory, memreserve等預(yù)留內(nèi)存.
int __init_memblock memblock_remove(phys_addr_t base, phys_addr_t size)
{
? ?return __memblock_remove(&memblock.memory, base, size);
}
系統(tǒng)中有兩處會(huì)調(diào)用memblock_remove:
early_init_dt_reserve_memory_arch中, 如果不希望這段內(nèi)存被映射, 那么就調(diào)用memblock_remove, 把這段內(nèi)存從memblock.memory中移除
arm_memblock_steal中, 調(diào)用memblock_remove從memblock.memory中偷一段內(nèi)存空間, memblock.memory的regions中將不再包含這段內(nèi)存空間.
int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
{
? ?return memblock_add_region(&memblock.memory, base, size,
? ? ? ? ? ? ? ? ? MAX_NUMNODES, 0);
}
在memblock的memory type中增加一個(gè)region
base 是新增region的基地址
size 是新增region的尺寸
對(duì)于arm, 僅一處會(huì)調(diào)用memblock_add: arm_memblock_init 中根據(jù)meminfo向memblock.memory中增加region
memory和reserved region memblock有兩個(gè)memblock_type成員: memory和reserved
memblock.memory 描述memblock所有內(nèi)存區(qū)(已分配的+未分配的) memblock.reserved 描述已經(jīng)分配的內(nèi)存區(qū), 所有分配和釋放操作都是通過修改reserved來實(shí)現(xiàn)的. 分配操作在memblock.reserved上增加region, 釋放操作則memblock.reserved上釋放region. memblock_reserved
int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)
{
? ?return memblock_reserve_region(base, size, MAX_NUMNODES, 0);
}
static int __init_memblock memblock_reserve_region(phys_addr_t base,
? ? ? ? ? ? ? ? ? ? ? ? ? phys_addr_t size,
? ? ? ? ? ? ? ? ? ? ? ? ? int nid,
? ? ? ? ? ? ? ? ? ? ? ? ? unsigned long flags)
{
? ?struct memblock_type *_rgn = &memblock.reserved;
? ?return memblock_add_region(_rgn, base, size, nid, flags);
}
memblock_reserve -> memblock_reserve_region -> memblock_add_region
調(diào)用memblock_add_region時(shí)第一個(gè)參數(shù)為 memblock.reserved, 也就是說在memblock.reserved增加一個(gè)region memblock_add_region流程如下:
如果type->regions[0].size==0, 表示regions數(shù)組為空, 添加region[0], 設(shè)置region[0]相關(guān)成員,即可返回. 此時(shí)該memblock_type有一個(gè)region了
計(jì)算要插入的region數(shù)目, [base, base + size]可能會(huì)跨越多個(gè)已存在的region, 因此數(shù)目可能不為1.
由于要插入新region, 所以需要先擴(kuò)展regions array.
插入這些regions
執(zhí)行region merge操作
我們可以看到memblock_reserve()操作壓根就沒考慮memblock.memory, 因?yàn)閙emblock_reserve()只是告訴memblock, 這部分內(nèi)存被保留了, 不要再參與memblock分配操作
memblock_alloc
phys_addr_t __init memblock_alloc(phys_addr_t size, phys_addr_t align)
{
? ?return memblock_alloc_base(size, align, MEMBLOCK_ALLOC_ACCESSIBLE);
}
phys_addr_t __init memblock_alloc_try_nid(phys_addr_t size, phys_addr_t align, int nid)
{
? ?phys_addr_t res = memblock_alloc_nid(size, align, nid);
? ?if (res)
? ? ? ?return res;
? ?return memblock_alloc_base(size, align, MEMBLOCK_ALLOC_ACCESSIBLE);
}
memblock_alloc->memblock_alloc_base->memblock_alloc_base_nid
static phys_addr_t __init memblock_alloc_base_nid(phys_addr_t size,
? ? ? ? ? ? ? ? ? ?phys_addr_t align, phys_addr_t max_addr,
? ? ? ? ? ? ? ? ? ?int nid)
{
? ?phys_addr_t found;
? ?if (!align)
? ? ? ?align = SMP_CACHE_BYTES;
? ?found = memblock_find_in_range_node(size, align, 0, max_addr, nid);
? ?if (found && !memblock_reserve(found, size))
? ? ? ?return found;
? ?return 0;
}
memblock_find_in_range_node查找符合條件的物理地址, 查找過程會(huì)涉及到查看memblock.reserve
如果找到了這個(gè)物理地址, 調(diào)用memblock_reserve進(jìn)行真正的分配(就是在memblock.reserve中添加region) memblock_free 釋放參數(shù)指定的內(nèi)存區(qū)間
int __init_memblock memblock_free(phys_addr_t base, phys_addr_t size)
{
? ?memblock_dbg(" ? memblock_free: [%#016llx-%#016llx] %pF\n",
? ? ? ? ? ? (unsigned long long)base,
? ? ? ? ? ? (unsigned long long)base + size - 1,
? ? ? ? ? ? (void *)_RET_IP_);
? ?return __memblock_remove(&memblock.reserved, base, size);
}
邏輯很簡(jiǎn)單, 從memblock.reserved中刪除[base, base+size]指定范圍的region. 這樣下次調(diào)用memblock_alloc時(shí)再檢查reserved type時(shí), 這段區(qū)域可以再次使用了
