知识点:
bootmem分配器
memblock分配器
一、bootmem分配器--临时引导内存分配器
在内核初始化的过程中需要分配内存,内核提供临时的引导内存分配器,在页分配器和块分配器初始化完成之后,把空闲的物理页交给页分配器管理,丢弃引导内存分配器bootmem分配器定义的数据结构,内核源码如下:
D:\linux-4.1.2\Linux-4.12\include\linux\bootmem.h
用来描述节点的结构体 typedef struct pglist_data { //该节点所在管理区为ZONE_HIGHMEM ZONE_NORMAL ZONE_DMA 包含了结点中各内存域的数据结构 struct zone node_zones[MAX_NR_ZONES]; /*备用结点及其内存域的列表,以便在当前结点没有可用空间时,在备用结点分配内存*/ struct zonelist node_zonelists[MAX_ZONELISTS]; /*表示该节点管理区的数目,1-3之间,并不是所有的节点都有三个管理区*/ int nr_zones; #ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */ struct page *node_mem_map; #ifdef CONFIG_PAGE_EXTENSION struct page_ext *node_page_ext; #endif #endif #ifndef CONFIG_NO_BOOTMEM struct bootmem_data *bdata;//引导内存bootmem分配器, 每个内存节点都应该有一个该分配器
bootmem分配器提供分配内存/释放内存(alloc_bootmem/free_bootmem)。ARM64架构的内核已不使用bootmem分配器,但其它处理器架构还在使用 bootmem分配器。
二、memblock分配器--代替bootmem
1、memblock数据结构
MEMBLOCK 内存分配器作为 arm32 早期的内存管理器,用于维护系统可用的物理内存。 系统启动过程中,可以使用 MEMBLOCK 内存分配器获得所需的物理内存,也可以将特定物理内存预留给特殊功能使用。MEMBLOCK 内存分配器逻辑框架简单易用,框架中将物理内存和已分配的物理内存维护在不同的数据结构中,以此实现 MEMBLOCK 分配器对物理 内存的分配和回收等操作。
MEMBLOCK 分配器使用一个 struct memblock 结构维护着两种内存, 其中成员 memory 维护着可用物理内存区域;成员 reserved 维护着操作系统预留的内存区域。 每个区域使用数据结构 struct memblock_type 进行管理,其成员 regions 负责维护该类型内存的所有内存区,每个内存区使用数据结构 struct memblock_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; char *name; };
struct memblock { bool bottom_up; /* 表示分配内存的方式 is bottom up direction? */ phys_addr_t current_limit; struct memblock_type memory; memory 维护着可用物理内存区域 struct memblock_type reserved; reserved 维护着操作系统预留的内存区域 #ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP struct memblock_type physmem; #endif };
/* Definition of memblock flags. */ enum { //表示没有特殊要求的区域 MEMBLOCK_NONE = 0x0, /* No special request */ //表示可以热插拔的区域,即在系统运行过程中可以拔出或者插入物理内存 MEMBLOCK_HOTPLUG = 0x1, /* hotpluggable region */ //表示镜像的区域,将内存数据做两个复制,分配放在在内存和镜像内存中 MEMBLOCK_MIRROR = 0x2, /* mirrored region */ //表示不添加到内存直接映射区域,即线性映射区域 MEMBLOCK_NOMAP = 0x4, /* don't add to kernel direct mapping */ }; 内存块区域的数据结构 struct memblock_region { phys_addr_t base; phys_addr_t size; unsigned long flags; #ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP int nid; #endif };
2、arm64内核初始化memblock分配器流程
1、memblock数据结构
2、arm64内核初始化memblock分配器
memblock_add:添加新的内存块区域到memblock.memory中;
memblock_remove:删除内存块区域;
memblock_alloc:分配内存;
memblock_free:释放内存。
初始化过程:
void __init setup_arch(char **cmdline_p) { pr_info("Boot CPU: AArch64 Processor [%08x]\n", read_cpuid_id()); sprintf(init_utsname()->machine, UTS_MACHINE); init_mm.start_code = (unsigned long) _text; init_mm.end_code = (unsigned long) _etext; init_mm.end_data = (unsigned long) _edata; init_mm.brk = (unsigned long) _end; *cmdline_p = boot_command_line; early_fixmap_init(); early_ioremap_init(); setup_machine_fdt(__fdt_pointer); parse_early_param(); /* * Unmask asynchronous aborts after bringing up possible earlycon. * (Report possible System Errors once we can report this occurred) */ local_async_enable(); /* * TTBR0 is only used for the identity mapping at this stage. Make it * point to zero page to avoid speculatively fetching new entries. */ cpu_uninstall_idmap(); xen_early_init(); efi_init(); arm64_memblock_init();
.......
void __init arm64_memblock_init(void) { const s64 linear_region_size = -(s64)PAGE_OFFSET; /* 解析设备数二进制文件 Handle linux,usable-memory-range property */ fdt_enforce_memory_region(); /* * 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)); /* * 全局变量memstart_addr 记录为内存的起始物理地址Select a suitable value for the base of physical memory. */ 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_t)ULLONG_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 ...\n")) { 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 = range / ARM64_MEMSTART_ALIGN + 1; memstart_addr -= ARM64_MEMSTART_ALIGN * ((range * memstart_offset_seed) >> 16); } } /* * Register the kernel text, kernel data, initrd, and initial * pagetables with memblock. *把内核镜像占用物理内存范围添加到menblock中*/ memblock_reserve(__pa_symbol(_text), _end - _text); #ifdef CONFIG_BLK_DEV_INITRD if (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 //从设备树二进制文件中的内存保留区域,对应设备源文件字段和机电 early_init_fdt_scan_reserved_mem(); /* 4GB maximum for 32-bit only capable devices */ if (IS_ENABLED(CONFIG_ZONE_DMA)) arm64_dma_phys_limit = max_zone_dma_phys(); else arm64_dma_phys_limit = PHYS_MASK + 1; reserve_crashkernel(); reserve_elfcorehdr(); dma_contiguous_reserve(arm64_dma_phys_limit); memblock_allow_resize(); }
3、memblock编程入口