Linux内存从0到1学习笔记(六,物理内存初始化) --- 更新中

Posted 高桐@BILL

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内存从0到1学习笔记(六,物理内存初始化) --- 更新中相关的知识,希望对你有一定的参考价值。

写在前面

kernel启动阶段的物理内存初始化流程如下:

 

  1. 获取线性地址大小,记录物理内存的起始地址,以及通过memblock分配器来初始化系统预留内存。
  2. 初始化内存基本数据结构包括内存节点,内存域;
  3. 初始化系统分页机制;
  4. 初始化zonelist;
  5. 初始化内存分配器;

一、物理内存数据结构

二、物理内存初始化流程

2.1 获取线性地址大小,记录物理内存的起始地址,以及通过memblock分配器来初始化系统预留内存

代码链接:https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/arm64/mm/init.c?h=v5.10.105

Linux内核使用伙伴系统管理内存,那么在伙伴系统之前,内核使通过memblock来管理。在系统启动阶段,使用memblock记录物理内存的使用情况。memblock就是将以上内存按功能划分为若干内存区,使用不同的类型存放在memory和reserved的两个集合中,memory即为动态内存,而resvered包括静态内存等。对于系统预留内存,包括静态内存(内核image,ramdisk,fdt等占用空间),以及camera,display等作系统预留的大量连续内存。这部分都是永久分配出去,不会再被伙伴系统管理。

这个函数的主要作用是初始化内存块。获取memory的区间范围,,remove一些超出实际物理内存地址空间的区域,并将kernel text, kernel data, initrd,initial agetables,crash kernel,elf core header到预留内存中。kernel 5.x的为CMA预留连续的物理内存初始化被移到了bootmem_init。
root/arch/arm64/mm/init.c

void __init arm64_memblock_init(void)

    //获取线性地址的大小
	const s64 linear_region_size = BIT(vabits_actual - 1);
	//处理linux可用内存范围的属性
	fdt_enforce_memory_region();
    //从memblock.memory上移除超出实际物理地址的地址空间;
	memblock_remove(1ULL << PHYS_MASK_SHIFT, ULLONG_MAX);
	//选择一个合适的值作为内核加载地址对应的物理地址值,保存在memstart_addr;也就是说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_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);
	
	/*
	 * If we are running with a 52-bit kernel VA config on a system that
	 * does not support it, we have to place the available physical
	 * memory in the 48-bit addressable part of the linear region, i.e.,
	 * we have to move it upward. Since memstart_addr represents the
	 * physical address of PAGE_OFFSET, we have to *subtract* from it.
	 */
	if (IS_ENABLED(CONFIG_ARM64_VA_BITS_52) && (vabits_actual != 52))
		memstart_addr -= _PAGE_OFFSET(48) - _PAGE_OFFSET(52);
	/*
	 * 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) && phys_initrd_size) 
		/*
		 * 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 = phys_initrd_start & PAGE_MASK;
		u64 size = PAGE_ALIGN(phys_initrd_start + phys_initrd_size) - 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")) 
			phys_initrd_size = 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);
		
	
    //将一块物理内存区块加入到预留物理内存内,通过memblock注册kernel text, kernel data, initrd和initial agetables到预留内存中。
	memblock_reserve(__pa_symbol(_text), _end - _text);
	if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && phys_initrd_size) 
		/* the generic initrd code expects virtual addresses */
		initrd_start = __phys_to_virt(phys_initrd_start);
		initrd_end = initrd_start + phys_initrd_size;
	
    //为crash kernel预留内存
	early_init_fdt_scan_reserved_mem();
    //为elf core header预留内存
	reserve_elfcorehdr();

	high_memory = __va(memblock_end_of_DRAM() - 1) + 1;

  1. 初始化内存基本数据结构包括内存节点,内存域;
  2. 初始化系统分页机制;
  3. 初始化zonelist;
  4. 初始化内存分配器;

以上是关于Linux内存从0到1学习笔记(六,物理内存初始化) --- 更新中的主要内容,如果未能解决你的问题,请参考以下文章

Linux内存从0到1学习笔记(六,物理内存初始化之三 --- 物理内存管理数据结构)

Linux内存从0到1学习笔记(六,物理内存初始化之二 --- 内存模型)

Linux内存从0到1学习笔记(6.4,物理内存初始化之预留内存)

Linux内存从0到1学习笔记(6.8,物理内存初始化之buddy伙伴系统)

Linux内存从0到1学习笔记(6.7,物理内存初始化之CMA初始化)

Linux内存从0到1学习笔记(6.10 物理内存初始化之vmalloc分配器)