網上有很多關于pos機引導者,引導內存分配器的知識,也有很多人為大家解答關于pos機引導者的問題,今天pos機之家(www.www690aa.com)為大家整理了關于這方面的知識,讓我們一起來看下吧!
本文目錄一覽:
1、pos機引導者
pos機引導者
Linux內存三大分配器:引導內存分配器,伙伴分配器,slab分配器
一、引導內存分配器1.引導內存分配器的作用因為內核里面有很多內存結構體,不可能在靜態編譯階段就靜態初始化所有的這些內存結構體。另外,在系統啟動過程中,系統啟動后的物理內存分配器本身也需要初始化,如伙伴分配器,那么伙伴分配器如何獲取內存來初始化自己呢 ?為了達到這個目標,我們先實現一個滿足要求的但是可能效率不高的笨家伙,引導內存分配器。用它來負責系統初始化初期的內存管理, 最重要的, 用它來初始化我們內存的數據結構, 直到我們真正的內存管理器被初始化完成并能投入使用, 我們將舊的內存管理器丟掉。
2.引導內存分配器的原理在Linux內核中使用struct bootmem_data來描述一個引導內存分配,其節點結構下的一個成員,也就是說每一個節點都有一個引導內存分配。 引導內存分配使用struct bootmem_data結構中的node_bootmem_map這個bitmap來呈現memory的狀態,一個bit代表一個物理頁框,也就是用struct page,如果一個bit為1,表示該page已經被分配了,如果bit是0,則表示該page未被分配。為了能夠滿足比一個page還小的內存塊的分配,引導內存分配器會使用last_pos來記住上次分配所使用的PFN以及上次分配所使用的page內的偏移:last_offset,下次分配的時候結合last_pos和last_offset將細小的內存塊分配盡量集中在相同的page中。
3引導內存分配器的缺點盡管引導內存分配器不會造成嚴重的內存碎片,但是每次分配過程需要線性掃描搜索內存來滿足當前的分配。因為是檢查bitmap,所以代價比較昂貴,尤其是最先適配(first fit)算法傾向將小塊內存放置在物理內存開頭,但是這些內存區域在分配大塊內存時,也需要掃描,所以該過程十分浪費。所以早期內存分配器在系統啟動后就被棄用的原因。
4.bootmem和memblock的比較但是bootmem也有很多問題. 最明顯的就是外碎片的問題, 因此內核維護了memblock內存分配器, 同時用memblock實現了一份bootmem相同的兼容API, 即nobootmem, Memblock以前被定義為Logical Memory Block( 邏輯內存塊),但根據Yinghai Lu的補丁, 它被重命名為memblock. 并最終替代bootmem成為初始化階段的內存管理器。 bootmem是通過位圖來管理,位圖存在地地址段, 而memblock是在高地址管理內存, 維護兩個鏈表, 即memory和reserved。 memory鏈表維護系統的內存信息(在初始化階段通過bios獲取的), 對于任何內存分配, 先去查找memory鏈表, 然后在reserve鏈表上記錄(新增一個節點,或者合并) bootmem和memblock都是就近查找可用的內存, bootmem是從低到高找, memblock是從高往低找。 在boot傳遞給kernel memory bank相關信息后,kernel這邊會以memblcok的方式保存這些信息,當伙伴系統沒有起來之前,在內核中也是要有一套機制來管理memory的申請和釋放。linux內核可以通過宏定義選擇nobootmem 或者bootmem 來在伙伴起來之前管理內存。這兩種機制對提供的API是一致的,因此對用戶是透明的
5.bootmem小分析bootmem結構體位于文件include/linux/bootmem.h:
typedef struct bootmem_data {unsigned long node_min_pfn;//節點內存的起始物理頁號unsigned long node_low_pfn;//節點內存的結束物理頁號void *node_bootmem_map;//位圖指針,每個物理頁對應一位,如果物理頁被分配則對應位置一。unsigned long last_end_off;//最后一次分配的頁面內的偏移量(字節);如果為0,則使用的頁面已滿unsigned long hint_idx;//最后一次分配的物理頁,下次優先考慮從這個物理頁分配struct list_head list;//按內存地址排序鏈表頭} bootmem_data_t;
bootmem接口函數: 1)bootmem分配內存函數:alloc_bootmem 2)bootmem釋放內存函數:free_bootmem
#define alloc_bootmem(x) \\__alloc_bootmem(x, SMP_CACHE_BYTES, BOOTMEM_LOW_LIMIT)void __init free_bootmem(unsigned long physaddr, unsigned long size){unsigned long start, end;kmemleak_free_part_phys(physaddr, size);//釋放映射的內存start = PFN_UP(physaddr);//查找到起始位置的物理頁end = PFN_DOWN(physaddr + size);//查找到結束為止的物理頁mark_bootmem(start, end, 0, 0);//把釋放的物理頁對應的位清零}
6.memblock結構解析memblock結構體位于include/linux/memblock.h文件:
struct memblock {bool bottom_up;//表示內存分配方式,真:從低地址向上分配,假:從高地址向下分配phys_addr_t current_limit;//可分配內存的最大物理地址struct memblock_type memory;//可用物理內存區域(包括已分配和未分配的)struct memblock_type reserved;//預留物理內存區域(預留起來不可用,例子:設備樹)#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAPstruct memblock_type physmem;//所有的物理內存區域#endif};struct memblock_type {unsigned long cnt;//區域數量unsigned long max;//分配區域的大小phys_addr_t total_size;//所有區域的大小struct memblock_region *regions;//區域數組指向區域數組char *name;//內存類型符號名};struct memblock_region {phys_addr_t base;//起始物理地址phys_addr_t size;//長度enum memblock_flags flags;//內存區域標志屬性#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAPint nid;//節點編號#endif};//內存區域標志屬性定義enum memblock_flags {MEMBLOCK_NONE = 0x0,//表示沒有特殊要求區域MEMBLOCK_HOTPLUG = 0x1,//表示可以熱插拔的區域MEMBLOCK_MIRROR = 0x2,//表示鏡像的區域,將內存數據做兩份復制,分配放在主內存和鏡像內存中MEMBLOCK_NOMAP = 0x4,//表示不添加到內核直接映射區域,即線性映射區};
memblock體系的結構:
7.memblock接口函數解析1)memblock添加內存區域函數:
int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size){phys_addr_t end = base + size - 1;memblock_dbg("memblock_add: [%pa-%pa] %pF\",&base, &end, (void *)_RET_IP_);//直接調用memblock_add_range將內存區塊添加到memblock.memory進行管理return memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);}
我們繼續追memblock_add_range:
int __init_memblock memblock_add_range(struct memblock_type *type,phys_addr_t base, phys_addr_t size,int nid, enum memblock_flags flags){bool insert = false;phys_addr_t obase = base;phys_addr_t end = base + memblock_cap_size(base, &size);int idx, nr_new;struct memblock_region *rgn;if (!size)return 0;if (type->regions[0].size == 0) {WARN_ON(type->cnt != 1 || type->total_size);type->regions[0].base = base;type->regions[0].size = size;type->regions[0].flags = flags;memblock_set_region_node(&type->regions[0], nid);type->total_size = size;return 0;}repeat:/** The following is executed twice. Once with ?lse @insert and* then with %true. The first counts the number of regions needed* to accommodate the new area. The second actually inserts them.*/base = obase;nr_new = 0;//遍歷所有內存塊,與新的內存塊比較for_each_memblock_type(idx, type, rgn) {phys_addr_t rbase = rgn->base;phys_addr_t rend = rbase + rgn->size;if (rbase >= end)//新加入的內存塊的結束地址已經到了則遍歷結束break;if (rend <= base)//即加入的內存塊的起始地址還沒到則遍歷下一塊continue;/** @rgn overlaps. If it separates the lower part of new* area, insert that portion.*///如果新加入的內存起始地址已經到了,但是還沒到遍歷的內存則插入if (rbase > base) {#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAPWARN_ON(nid != memblock_get_region_node(rgn));#endifWARN_ON(flags != rgn->flags);nr_new++;if (insert)//添加內存區域,也就是填充struct memblock_region而已memblock_insert_region(type, idx++, base,rbase - base, nid,flags);}/* area below @rend is dealt with, forget about it */base = min(rend, end);}/* insert the remaining portion */if (base < end) {nr_new++;if (insert)memblock_insert_region(type, idx, base, end - base,nid, flags);}//如果需要加入的內存塊個數為0則返回,不需要第二次遍歷執行加入操作if (!nr_new)return 0;/** If this was the first round, resize array and repeat for actual* insertions; otherwise, merge and return.*///第一次會進入,判斷內存區域塊是否達到上限,是則退出,否則回到repeat//因為insert參數原因,第一次沒有真正插入,第二次才會真正的插入if (!insert) {while (type->cnt + nr_new > type->max)if (memblock_double_array(type, obase, size) < 0)return -ENOMEM;insert = true;goto repeat;} else {memblock_merge_regions(type);//合并相鄰且沒有縫隙的內存區域return 0;}}
2)memblock刪除內存區域函數:memblock_remove
int __init_memblock memblock_remove(phys_addr_t base, phys_addr_t size){phys_addr_t end = base + size - 1;memblock_dbg("memblock_remove: [%pa-%pa] %pS\",&base, &end, (void *)_RET_IP_);return memblock_remove_range(&memblock.memory, base, size);}
memblock_remove_range:
static int __init_memblock memblock_remove_range(struct memblock_type *type,phys_addr_t base, phys_addr_t size){int start_rgn, end_rgn;int i, ret;//要刪除的內存區域內存區內的內存塊存在重疊部分,把這部分需要獨立出來ret = memblock_isolate_range(type, base, size, &start_rgn, &end_rgn);if (ret)return ret;//根據要刪除內存區的索引號,刪除內存區塊for (i = end_rgn - 1; i >= start_rgn; i--)memblock_remove_region(type, i);return 0;}
3)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_base(phys_addr_t size, phys_addr_t align, phys_addr_t max_addr){phys_addr_t alloc;alloc = __memblock_alloc_base(size, align, max_addr);if (alloc == 0)panic("ERROR: Failed to allocate %pa bytes below %pa.\",&size, &max_addr);return alloc;}phys_addr_t __init __memblock_alloc_base(phys_addr_t size, phys_addr_t align, phys_addr_t max_addr){return memblock_alloc_base_nid(size, align, max_addr, NUMA_NO_NODE,MEMBLOCK_NONE);}phys_addr_t __init memblock_alloc_base_nid(phys_addr_t size,phys_addr_t align, phys_addr_t max_addr,int nid, enum memblock_flags flags){return memblock_alloc_range_nid(size, align, 0, max_addr, nid, flags);}static phys_addr_t __init memblock_alloc_range_nid(phys_addr_t size,phys_addr_t align, phys_addr_t start,phys_addr_t end, int nid,enum memblock_flags flags){phys_addr_t found;if (!align)align = SMP_CACHE_BYTES;//在給定范圍和節點內找一塊空區域found = memblock_find_in_range_node(size, align, start, end, nid,flags);//memblock_reserve是把找到的空區域添加到memblock.reserved中,表示已經用了if (found && !memblock_reserve(found, size)) {/** The min_count is set to 0 so that memblock allocations are* never reported as leaks.*///一個內存塊分配物理內存的通知kmemleak_alloc_phys(found, size, 0, 0);return found;}return 0;}
4)memblock釋放內存函數:memblock_free
int __init_memblock memblock_free(phys_addr_t base, phys_addr_t size){phys_addr_t end = base + size - 1;memblock_dbg(" memblock_free: [%pa-%pa] %pF\",&base, &end, (void *)_RET_IP_);//通知釋放部分內存塊kmemleak_free_part_phys(base, size);return memblock_remove_range(&memblock.reserved, base, size);}static int __init_memblock memblock_remove_range(struct memblock_type *type,phys_addr_t base, phys_addr_t size){int start_rgn, end_rgn;int i, ret;//要刪除的內存區域內存區內的內存塊存在重疊部分,把這部分需要獨立出來ret = memblock_isolate_range(type, base, size, &start_rgn, &end_rgn);if (ret)return ret;//根據要刪除內存區的索引號,刪除內存區塊for (i = end_rgn - 1; i >= start_rgn; i--)memblock_remove_region(type, i);return 0;}
7.memblock啟動流程 1)解析設備樹中的/memory,把所有物理內存添加到memblock 2)在memblock_init中初始化memblock linux啟動從init/main.c文件的start_kernel函數開始,然后從文件setup_arch(arch/arm64/kernel/setup.c文件中)函數檢測處理器類型,初始化處理器和內存,其中的arm64_memblock_init(arch/arm64/mm/init.c文件中)函數就是arm64架構的memblock初始化流程。
void __init arm64_memblock_init(void){const s64 linear_region_size = -(s64)PAGE_OFFSET;/* Handle linux,usable-memory-range property *///解析設備樹文件的內存節點fdt_enforce_memory_region;/* Remove memory above our supported physical address size *///刪除超出我們支持的物理地址大小的內存memblock_remove(1ULL << PHYS_MASK_SHIFT, ULLONG_MAX);/** Ensure that the linear region takes up exactly half of the kernel* virtual address space. This way, we can distinguish a linear address* from a kernel/module/vmalloc address by testing a single bit.*/BUILD_BUG_ON(linear_region_size != BIT(VA_BITS - 1));/** Select a suitable value for the base of physical memory.*///全局變量memstart_addr記錄了內存的起始物理地址memstart_addr = round_down(memblock_start_of_DRAM,ARM64_MEMSTART_ALIGN);/** Remove the memory that we will not be able to cover with the* linear mapping. Take care not to clip the kernel which may be* high in memory.*///把線性映射區無法覆蓋的物理內存范圍從memblock中刪除memblock_remove(max_t(u64, memstart_addr + linear_region_size,__pa_symbol(_end)), ULLONG_MAX);if (memstart_addr + linear_region_size < memblock_end_of_DRAM) {/* ensure that memstart_addr remains sufficiently aligned */memstart_addr = round_up(memblock_end_of_DRAM - linear_region_size,ARM64_MEMSTART_ALIGN);memblock_remove(0, memstart_addr);}/** Apply the memory limit if it was set. Since the kernel may be loaded* high up in memory, add back the kernel region that must be accessible* via the linear mapping.*///如果設置了內存限制,要根據限制使用內存if (memory_limit != PHYS_ADDR_MAX) {memblock_mem_limit_remove_map(memory_limit);//把超出限制的內存移除memblock_add(__pa_symbol(_text), (u64)(_end - _text));//添加可以使用的內存}if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && initrd_start) {/** Add back the memory we just removed if it results in the* initrd to become inaccessible via the linear mapping.* Otherwise, this is a no-op*/u64 base = initrd_start & PAGE_MASK;u64 size = PAGE_ALIGN(initrd_end) - base;/** We can only add back the initrd memory if we don\'t end up* with more memory than we can address via the linear mapping.* It is up to the bootloader to position the kernel and the* initrd reasonably close to each other (i.e., within 32 GB of* each other) so that all granule/#levels combinations can* always access both.*/if (WARN(base < memblock_start_of_DRAM ||base + size > memblock_start_of_DRAM +linear_region_size,"initrd not fully accessible via the linear mapping -- please check your bootloader ...\")) {initrd_start = 0;} else {memblock_remove(base, size); /* clear MEMBLOCK_ flags */memblock_add(base, size);memblock_reserve(base, size);}}if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {extern u16 memstart_offset_seed;u64 range = linear_region_size -(memblock_end_of_DRAM - memblock_start_of_DRAM);/** If the size of the linear region exceeds, by a sufficient* margin, the size of the region that the available physical* memory spans, randomize the linear region as well.*/if (memstart_offset_seed > 0 && range >= ARM64_MEMSTART_ALIGN) {range /= ARM64_MEMSTART_ALIGN;memstart_addr -= ARM64_MEMSTART_ALIGN *((range * memstart_offset_seed) >> 16);}}/** Register the kernel text, kernel data, initrd, and initial* pagetables with memblock.*///把內核鏡像占用的內存添加到memblock的預留區中,表示預留了不再分配出去memblock_reserve(__pa_symbol(_text), _end - _text);#ifdef CONFIG_BLK_DEV_INITRDif (initrd_start) {memblock_reserve(initrd_start, initrd_end - initrd_start);/* the generic initrd code expects virtual addresses */initrd_start = __phys_to_virt(initrd_start);initrd_end = __phys_to_virt(initrd_end);}#endif//掃描設備樹中的保留內存區域并添加到memblock的預留區域中early_init_fdt_scan_reserved_mem;/* 4GB maximum for 32-bit only capable devices */if (IS_ENABLED(CONFIG_ZONE_DMA32))arm64_dma_phys_limit = max_zone_dma_phys;elsearm64_dma_phys_limit = PHYS_MASK + 1;reserve_crashkernel;reserve_elfcorehdr;high_memory = __va(memblock_end_of_DRAM - 1) + 1;dma_contiguous_reserve(arm64_dma_phys_limit);memblock_allow_resize;}
最后,引導內存分配器退休,會將物理內存填充到伙伴分配器中,移交給伙伴分配器進行管理。
end
人人極客社區
關注,回復【peter】海量Linux資料贈送
文章推薦
?【專輯】
?【專輯】
?【專輯】
?【專輯】
?【專輯】
?【專輯】
?【專輯】
?【專輯】
?
?【專輯】
以上就是關于pos機引導者,引導內存分配器的知識,后面我們會繼續為大家整理關于pos機引導者的知識,希望能夠幫助到大家!









