-了解linux物理内存初始化
1.系统是怎么知道物理内存的?
memory节点,以arch/arm64/boot/dts/freescale/fsl-ls208xa.dtsi为例:
memory@80000000 {
device_type = "memory";
reg = <0x00000000 0x80000000 0 0x80000000>;
/* DRAM space - 1, size : 2 GB DRAM */
};
这个节点描述了内存的起始地址及大小,内核在解析dtb文件时会去读取该memory节点的内容,从而将检测到的内存注册进系统。
Uboot会将kernel image和dtb拷贝到内存中,并且将dtb物理地址告知kernel,kernel需要从该物理地址上读取到dtb文件并解析,才能得到最终的内存信息,dtb的物理地址需要映射到虚拟地址上才能访问,但是这个时候paging_init还没有调用,也就是说物理地址的映射还没有完成,那该怎么办呢?Fixed map机制出现了参考:fixmap addresses原理。
1.1.DTB映射
在执行setup_arch中,会最先进行early_fixmap_init(),这个函数用来map dtb,但是它只会建立dtb对应的这段物理地址中间level的页表entry,而最后一个level的页表映射,则通过setup_machine_fdt函数里的fixmap_remap_fdt来创建。fixmap_remap_fdt主要是为fdt建立地址映射,在该函数的最后,调用memblock_reserve保留了该段内存。
可以看出dtb的映射采用的是fixmap,所谓fixmap就是固定映射,它需要我们明确的知道想要映射的物理地址,并把这段地址映射到想要映射的虚拟地址上。当然这里固定映射还有些片面,因为在fixmap机制实现上,也有支持动态分配虚拟地址的功能,这个功能主要用于临时fixmap映射(这个临时映射就是用来执行early ioremap使用的。),而dtb的映射属于永久映射。
- early_fixmap_init
Fixed map指的是虚拟地址中的一段区域,在该区域中所有的线性地址是在编译阶段就确定好的,这些虚拟地址需要在boot阶段去映射到物理地址上。
图中fixed: 0xffffffbefe7fd000 - 0xffffffbefec00000,描述的就是Fixed map的区域。详细布局如下所示:(arch/arm64/include/asm/fixmap.h中的enum fixed_addresses)
从图中可以看出,如果要访问DTB所在的物理地址,那么需要将该物理地址映射到Fixed map中的区域,然后访问该区域中的虚拟地址即可。访问IO空间也是一样的道理。
1.2.early_fixmap_init函数关键代码:
void __init early_fixmap_init(void)
{
pgd_t *pgd;
pud_t *pud;
pmd_t *pmd;
unsigned long addr = FIXADDR_START; /* (1) */
pgd = pgd_offset_k(addr); /* (2) */
if (CONFIG_PGTABLE_LEVELS > 3 &&
!(pgd_none(*pgd) || pgd_page_paddr(*pgd) == __pa_symbol(bm_pud))) {
/*
* We only end up here if the kernel mapping and the fixmap
* share the top level pgd entry, which should only happen on
* 16k/4 levels configurations.
*/
BUG_ON(!IS_ENABLED(CONFIG_ARM64_16K_PAGES));
pud = pud_offset_kimg(pgd, addr);
} else {
if (pgd_none(*pgd))
__pgd_populate(pgd, __pa_symbol(bm_pud), PUD_TYPE_TABLE); /* (3) */
pud = fixmap_pud(addr);
}
if (pud_none(*pud))
__pud_populate(pud, __pa_symbol(bm_pmd), PMD_TYPE_TABLE); /* (4) */
pmd = fixmap_pmd(addr);
__pmd_populate(pmd, __pa_symbol(bm_pte), PMD_TYPE_TABLE); /* (5) */
......
}
- FIXADDR_START,定义了Fixed map区域的起始地址,位于arch/arm64/include/asm/fixmap.h中;
- pgd_offset_k(addr),获取addr地址对应pgd全局页表中的entry,而这个pgd全局页表正是swapper_pg_dir全局页表;
- 将bm_pud的物理地址写到pgd全局页目录表中;
- 将bm_pmd的物理地址写到pud页目录表中;
- 将bm_pte的物理地址写到pmd页表目录表中;
bm_pud/bm_pmd/bm_pte是三个全局数组,相当于是中间的页表,存放各级页表的entry,定义如下:
static pte_t bm_pte[PTRS_PER_PTE] __page_aligned_bss;
static pmd_t bm_pmd[PTRS_PER_PMD] __page_aligned_bss __maybe_unused;
static pud_t bm_pud[PTRS_PER_PUD] __page_aligned_bss __maybe_unused;
early_fixmap_init只是建立了一个映射的框架,具体的物理地址和虚拟地址的映射没有去填充,这个是由使用者具体在使用时再去填充对应的pte entry。比如像fixmap_remap_fdt()函数,就是典型的填充pte entry的过程,完成最后的一步映射,然后才能读取dtb文件。
1.3.early_ioremap_init
如果在boot早期需要操作IO设备的话,那么ioremap就用上场了。对于一些硬件需要在内存管理系统起来之前就要工作的,我们就可以使用这种机制来映射内存给这些硬件driver使用。各个模块在使用完early ioremap的地址后,需要尽快把这段映射的虚拟地址释放掉,这样才能反复被其他模块继续申请使用。
注意:如果想要在伙伴系统初始化之前进行设备寄存器的访问,那么可以考虑early IO remap机制。
dtb和early ioremap都是在fixmap区的,如下图:
1.4.DTB访问
完成dtb的map之后,内核可以访问这一段的内存了,通过解析dtb中的内容,内核可以勾勒出整个内存布局的情况,为后续内存管理初始化奠定基础。这一步主要在setup_machine_fdt中完成。
2.在内存管理真正初始化之前,内核的代码执行需要分配内存该怎么处理?
当所有物理内存添加进系统后,在mm_init之前,系统会使用memblock模块来对内存进行管理。
2.1.结构体
由三个数据结构来描述:
- struct memblock定义了一个全局变量,用来维护所有的物理内存;
- struct memblock_type代表系统中的内存类型,包括实际使用的内存和保留的内存;
- struct memblock_region用来描述具体的内存区域,包含在struct memblock_type中的regions数组中,最多可以存放128个。
2.2.arm64_memblock_init
当物理内存都添加进系统之后,arm64_memblock_init会对整个物理内存进行整理,主要的工作就是将一些特殊的区域添加进reserved内存中。函数执行完后,如下图所示:
refer to
- https://www.lagou.com/lgeduarticle/23340.html