66

Linux的内存初始化

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzI3NzA5MzUxNA%3D%3D&%3Bmid=2664606453&%3Bidx=1&%3Bsn=4c9e5dab05319186b84fe986b54a70fe&%3Butm_source=tuicool&%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

看了很多关于linux内存管理的文章还是云里雾里,听了很多关于linux内存管理的课程还是一头雾水。其实很多时候造成不懂的原因不是资料太少,恰恰是资料太多,而且各个内核版本的差异,32位64位的不同,文章的胡编乱造等都给读者带来疑惑。本着对内存深度剖析的态度,希望以版本kernel-4.14,架构AARCH64为专题做个内存管理的架构性整理。

这篇文章我们先来看下linux在启动过程中的初始化。

创建启动页表:

在汇编代码阶段的head.S文件中,负责创建映射关系的函数是 create_page_tables create_page_tables函数负责identity mapping和kernel image mapping。

  • identity map:是指把idmap_text区域的物理地址映射到相等的虚拟地址上,这种映射完成后,其虚拟地址等于物理地址。idmap_text区域都是一些打开MMU相关的代码。

  • kernel image map: 将kernel运行需要的地址(kernel txt、rodata、data、bss等等)进行映射。

arch/arm64/kernel/head.S:

ENTRY(stext)

bl preserve_boot_args

bl el2_setup // Drop to EL1, w0=cpu_boot_mode

adrp x23, __PHYS_OFFSET

and x23, x23, MIN_KIMG_ALIGN - 1 // KASLR offset, defaults to 0

bl set_cpu_boot_mode_flag

bl __create_page_tables

/*

* The following calls CPU setup code, see arch/arm64/mm/proc.S for

* details.

* On return, the CPU will be ready for the MMU to be turned on and

* the TCR will have been set.

*/

bl __cpu_setup // initialise processor

b __primary_switch

ENDPROC(stext)

__create_page_tables主要执行的就是identity map和kernel image map:

__create_page_tables:

......

create_pgd_entry x0, x3, x5, x6

mov x5, x3 // __pa(__idmap_text_start)

adr_l x6, __idmap_text_end // __pa(__idmap_text_end)

create_block_map x0, x7, x3, x5, x6


/*

* Map the kernel image (starting with PHYS_OFFSET).

*/

adrp x0, swapper_pg_dir

mov_q x5, KIMAGE_VADDR + TEXT_OFFSET // compile time __va(_text)

add x5, x5, x23 // add KASLR displacement

create_pgd_entry x0, x5, x3, x6

adrp x6, _end // runtime __pa(_end)

adrp x3, _text // runtime __pa(_text)

sub x6, x6, x3 // _end - _text

add x6, x6, x5 // runtime __va(_end)

create_block_map x0, x7, x3, x5, x6

......

其中调用 create_pgd_entry 进行 PGD及所有中间level(PUD, PMD) 页表的创建,调用 create_block_map 进行 PTE页表 的映射。关于四级页表的关系如下图所示,这里就不进一步解释了。

RVraMzq.jpg!web

汇编结束后的内存映射关系如下图所示:

fUrU7r7.jpg!web

当执行完上面的map之后,MMU就已经打开了并且开始进入C代码运行阶段,那么下一步就要对dtb进行映射了。

fixmap区之dtb map:

F3EfmuY.jpg!web

在执行setup_arch中,会最先进行early_fixmap_init(),这个函数就是用来map dtb的,但是它只会建立dtb对应的这段物理地址中间level的页表entry,而最后一个level的页表映射则通过setup_machine_fdt函数里的 fixmap_remap_fdt 来创建。

void *__init fixmap_remap_fdt(phys_addr_t dt_phys)

{

void *dt_virt;

int size;


dt_virt = __fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL_RO);

if (!dt_virt)

return NULL;


memblock_reserve(dt_phys, size);

return dt_virt;

}

fixmap_remap_fdt主要是为fdt建立地址映射,在该函数的最后,顺便就调用 memblock_reserve 保留了该段内存。

可以看出dtb的映射采用的是fixmap,所谓fixmap就是固定映射,它需要我们明确的知道想要映射的物理地址,并把这段地址映射到想要映射的虚拟地址上。当然这里固定映射还有些片面,因为在fixmap机制实现上,也有支持动态分配虚拟地址的功能,这个功能主要用于临时fixmap映射( 这个临时映射就是用来执行early ioremap使用的。 ),而dtb的映射属于永久映射。

fixmap区之early ioremap:

对于一些硬件需要在内存管理系统起来之前就要工作的,我们就可以使用这种机制来映射内存给这些硬件driver使用。各个模块在使用完early ioremap的地址后,需要尽快把这段映射的虚拟地址释放掉,这样才能反复被其他模块继续申请使用。

early_ioremap_init会调用early_ioremap_setup:

rQVziiM.png!web

可见它的实现是依赖fixmap的,所以它必须要在early_fixmap_init之后才能运行。

注意: 如果想要在伙伴系统初始化之前进行设备寄存器的访问,那么可以考虑early IO remap机制。

至此我们已经知道dtb和early ioremap都是在fixmap区的,如下图:

MNbMZre.jpg!web

系统内存的布局:

完成dtb的map之后,内核可以访问这一段的内存了,通过解析dtb中的内容,内核可以勾勒出整个内存布局的情况,为后续内存管理初始化奠定基础。 这一步主要在setup_machine_fdt中完成。这里就不看代码了,其调用流程是:setup_machine_fdt->early_init_dt_scan->early_init_dt_scan_nodes

6fYn6vr.png!web

就像注释中所示内核根据dtb的不同node勾勒出 choosen node,root node,memory node相应内存区域。

除了这3个node,还有一个reserved-memory node,它是在上面讲到dtb map的时候 fixmap_remap_fdt函数做的。下面我们看下这4个node的具体实现。

  • choosen node

eArmMzV.png!web

该节点有一个bootargs属性,该属性定义了内核的启动参数,比如mem= xx,此外,还处理initrd相关的property,并保存在initrd_start和initrd_end这两个全局变量中。

  • root node

与内存无关,暂时不详述,以后有机会讲到device tree系列再详述。

  • memory node

faUb6jM.png!web

通过 memblock_add 加入到memblock.memory对应的memblock_type链表中进行管理。

接下来到arm64_memblock_init函数:

void __init arm64_memblock_init(void)

{

......

memblock_reserve(__pa_symbol(_text), _end - _text); 1.kernel image保留区

#ifdef CONFIG_BLK_DEV_INITRD

if (initrd_start) {

memblock_reserve(initrd_start, initrd_end - initrd_start); 2.initrd保留区

/* 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(); 3.dts中配置为保留的区域

......

}

  1. reserve内核代码、数据区等(_text到_end那一段,具体的内容可以参考内核链接脚本)

  2. 保留initital ramdisk image区域(从initrd_start到initrd_end区域)

  3. reserved-memory node 如下所示:

yqeYzqE.jpg!web

完成:

通过上面的一系列操作,需要动态管理的内存已经被放到了memory type和reserved type这两个region中了,现在内存已经被memblock模块所管理了,这只是启动后的第一步,后续内存才会加入到伙伴系统去管理。

yqu2Yfi.jpg!web

添加极客助手微信,加入 技术交流群

qMZve2R.jpg!web

长按,扫码,关注 公众号

麻烦给个好看!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK