linux内存分配器类型,内核早期内存分配器:memblock
原標題:內核早期內存分配器:memblock
本文轉載自Linux愛好者
本文來自 程雪濤的自薦投稿
Linux內核使用伙伴系統管理內存,那么在伙伴系統工作前,如何管理內存?答案是memblock。
memblock在系統啟動階段進行簡單的內存管理,記錄物理內存的使用情況。
在進一步介紹memblock之前,有必要先了解下系統內存的使用情況:
首先,內存中的某些部分是永久的分配給內核的,比如內核代碼段和數據段,ramdisk和fdt占用的空間等,它們是系統內存的一部分,但是不能被侵占,也不參與內存分配,稱之為靜態內存;
其次,GPU,Camera等都需要預留大量連續內存,這部分內存平時不用,但是系統必須提前預留好,稱之為預留內存;
最后,內存的其余部分稱之為動態內存,是需要內核管理的寶貴資源。
memblock把物理內存劃分為若干內存區,按使用類型分別放在memory和reserved兩個集合(數組)中,memory即動態內存的集合,reserved集合包括靜態內存和預留內存。
1. memblock關鍵數據結構
memblock數據結構定義如下:
memblock相關數據結構十分的簡單,內核還為memblock定義了一個全局變量,并為其賦初值,如下:
memory類型的內存集合指向memblock_memory_init_regions數組,最多可以記錄128個內存區。
reserved類型的內存集合指向memblock_reserved_init_regions數組,最多可以記錄128個內存區。
注:內核代碼經常用到類似”__initdata_memblock”的宏定義,通常用來指定變量或函數所在的section,該宏的定義如下:
#define __meminitdata __attribute__ ((__section__(".meminit.data")))
2. memblock基本操作1) 添加內存區
分別為memory和reserved集合添加內存區,如果新加入的內存區與原有內存區重疊,則合并到原有內存區,否則插入新內存區。
實際工作由memblock_add_range()完成,type參數指定內存集合類型。
需要注意的是該函數內部會執行兩次:
第一次計算需要插入幾個內存區,如果超過允許的最大內存區個數,則double內存區數組;
第二次執行內存區的實際插入與合并操作。
2) 移除內存區
intmemblock_remove(phys_addr_tbase,phys_addr_tsize);
從memory集合移除給定物理地址所指的內存區,如果是內存區域的一部分,則涉及到調整region大小,或者將一個region拆分成兩個region。
系統將不會為移除的內存區建立內存映射,這部分內存區后續應該由DMA或CMA管理。
3) 分配內存
phys_addr_t memblock_alloc(phys_addr_tsize,phys_addr_talign);
phys_addr_t memblock_alloc_range(phys_addr_tsize,phys_addr_talign,phys_addr_tstart,phys_addr_tend);
使用該函數向kernel申請一塊可用的物理內存,memblock使用自頂向下(取決于bottom_up的值)的方式查找空閑內存,實際操作是在memory region中查找合適的內存,并加入到reserved region中以標記這塊內存已經被使用。
4) 釋放內存
intmemblock_free(phys_addr_tbase,phys_addr_tsize);
使用該函數來釋放由memblock_alloc申請到的物理內存。
3. 探測系統可用內存
內核是如何知曉物理內存的拓撲結構呢?相信很多人都有過類似的疑問。
通過DDR的模式寄存器(MR8),可以很容易獲得內存密度,進而推斷出內存容量,這部分工作通常由bootloader完成,然后使用fdt或者atag等方式傳遞給內核。
以fdt為例,內核解析memory節點,取得物理內存的拓撲結構(起始地址及大小),并添加到memblock中,代碼如下:
setup_arch()->setup_machine_fdt()->early_init_dt_scan()->early_init_dt_scan_memory()
{
......
reg=of_get_flat_dt_prop(node,"reg",&l);
......
while((endp-reg)>=(dt_root_addr_cells+dt_root_size_cells)){
u64base,size;
base=dt_mem_next_cell(dt_root_addr_cells,®);
size=dt_mem_next_cell(dt_root_size_cells,®);
......
early_init_dt_add_memory_arch(base,size);
}
}
該函數掃描memory節點,并解析reg屬性,注意此時DeviceTree還沒有執行unflattern操作,需要使用”fdt”類型接口解析dtb。
以4G DDR為例,輸出的調試信息如下:
[0.000000]memory scan nodememory,regsize32,data:080080,100000000557e
[0.000000]-80000000,80000000
[0.000000]-100000000,7e550000
reg屬性由addr和size組成,分別占用2個cell(u32類型數據),上面的reg data可以看成:“0 00000080 0 00000080, 01000000 0 0 00557e”。
dtb使用big endian方式存儲數據,需要轉換成cpu字節序。
解析出來的內存包含兩個Rank,起始地址分別是0x80000000和0x100000000,這是系統的可用內存,用來初始化memory region。
voidearly_init_dt_add_memory_arch(base,size)
{
constu64phys_offset=__pa(PAGE_OFFSET);
......
if(base
pr_warning("Ignoring memory range 0x%llx - 0x%llxn",
base,phys_offset);
size-=phys_offset-base;
base=phys_offset;
}
memblock_add(base,size);
}
從fdt解析的內存信息是否可信呢?內核有自己的判斷,在啟動階段,內核會根據自身的運行地址計算內存基地址,即PHYS_OFFSET。
如果base地址小于phys_offset,則內核使用可信的phys_offset做為主存的基地址。
這里要注意區分PHYS_OFFSET, PAGE_OFFSET:
PAGE_OFFSET是內核虛擬地址空間的起始地址,PHYS_OFFSET是RAM在物理空間的起始地址,內核空間的地址映射通常具有固定的偏移量,即:
#define __virt_to_phys(x) (((phys_addr_t)(x) - PAGE_OFFSET + PHYS_OFFSET))
4. 記錄系統預留內存
這里說的系統預留內存,包括靜態內存(內核Image,ramdisk,fdt等占用空間),以及系統為Camera,Display等子系統預留的大量連續內存。
另外,高通平臺通常包含多核,還需要為Modem,TZ/TA等預留運行空間,這部分空間類似靜態內存,都是永久分配給其它核心使用,根據節點屬性,通常由DMA管理。
arm64_memblock_init()函數初始化系統預留內存,代碼如下:
“no-map”屬性決定向reserved region添加內存區,還是從memory region移除內存區,二者差別在于內核不會給”no-map”屬性的內存區建立內存映射,即該內存區不在動態內存管理范圍。
預留內存還會被添加到reserved_mem數組,為后續的初始化做準備,”reg”屬性指定內存區的起始地址和大小,如果沒有”reg”屬性,還需要為內存區分配空間。
至此,memblock的初始化工作已經基本完成了,主要是記錄系統內存的使用情況:
memory region記錄系統了所有可用的動態內存;
reserved region記錄了系統預留內存,這部分內存通常由CMA管理,也屬于動態內存范疇;
reserved_mem數組則記錄系統所有預留內存,包括”no-map”屬性的內存區,為后續進一步初始化工作做準備。
5. 初始化預留內存區
內存向來是系統的寶貴資源,預留內存如果僅做為子系統的專用內存,就有點浪費了。
Linux內核引入CMA(Contiguous Memory Allocator,連續內存分配器)。
其工作原理是:為驅動預留一段內存,當驅動不用時,Memory Allocator(Buddy System)可以分配給用戶進程使用;而當驅動需要使用時,就將進程占用的內存通過回收或者遷移的方式騰出來,供驅動使用。
但是并不是所有的預留內存都由CMA管理,像Modem,TA等永久分配給其它核心使用的內存空間,內核并不為這部分空間建立內存映射,而是交由DMA管理。
通過上面的分析,我們看到所有預留內存信息都記錄在reserved_mem數組,下面先看看該結構體的定義:
“reg”和”no-map”屬性前面介紹過,詳細可以參考reserved-memory.txt,”compatible”屬性使用標準定義,內核注冊兩種不同的處理方法:
RESERVEDMEM_OF_DECLARE(dma,"removed-dma-pool",removed_dma_setup);
RESERVEDMEM_OF_DECLARE(cma,"shared-dma-pool",rmem_cma_setup);
“removed-dma-pool”表示該內存區位于DMA管理區,內核不可見(沒有頁表)。
“shared-dma-pool”表示該內存區位于CMA管理區,平時是可用的,只有需要時才分配給驅動使用。
如果沒有”reg”屬性,即沒有指定預留內存的起始地址,則需要由系統分配預留內存,然后初始化reserved_mem的ops成員:
此處為”shared-dma-pool”類型的內存注冊操作方法,cma_init_reserved_mem初始化cma_area(CMA管理區)。
reserved_mem的ops成員包括init和release兩個操作方法:
structreserved_mem_ops{
int(*device_init)(structreserved_mem *rmem,structdevice *dev);
void(*device_release)(structreserved_mem *rmem,structdevice *dev);
};
驅動注冊預留內存區時調用device_init方法,為設備指定預留內存操作方法(DMA)或預留內存區域(CMA),這些方法包括預留內存的申請,釋放和mmap等。
6. 連續內存分配器(CMA)
CMA的初始化必須在buddy系統工作之前和memblock分配器初始化完成之后。
在ARM中,初始化CMA的接口是:
以命令行參數”cma=32M@0-0xfffffff”為例: size_cmdline = 32M, base_cmdline = 0x0, limit_cmdline = 0xffffffff 。
計算好CMA的size等值以后就進入cma_declare_contiguous中:
以上只是將CMA區域預留下來,并記錄到相關數組,進一步初始化和使用需要等slab等子系統初始化完成后了。
CMA并不直接開放給驅動開發人員,在注冊設備時可以使用”memory-region”屬性指定要操作的內存區域,需要分配DMA內存時,調用DMA相關函數就可以了。
例如dma_alloc_coherent(),最終DMA相關的分配函數會到達CMA的分配函數dma_alloc_from_contiguous()。
7. 進階閱讀
memblock和bootmem的區別
CMA(連續內存分配器)
Contiguous Memory Allocator (CMA) 源碼分析
Linux內核最新連續內存分配器(CMA) — 避免預留大塊內存
內存管理筆記(CMA)返回搜狐,查看更多
責任編輯:
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的linux内存分配器类型,内核早期内存分配器:memblock的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python文本特征选择,机器学习--特
- 下一篇: 超级计算机清华,从清华到华科 名校为何主