linux内核启动流程分析 - efi_main

2020-10-10 10:04:10 浏览数 (1)

上一篇文章 linux内核启动流程分析 - efi_stub_entry 中,为了叙述方便,我们只是粗略的讲了下efi_main函数,这里我们再具体看下。

通过该函数的注释可知,其主要目的是返回startup_32的运行时地址,这个我们在上篇文章中也说过。

673行保存startup_32的运行时地址到bzimage_addr中。

675行保存boot_params->hdr的地址到hdr中。

有关boot_params的创建及其hdr的初始化,我们在 linux内核启动流程分析 - efi_pe_entry 中有讲到,这里就不再赘述。

继续看该函数的其余部分。

该部分主要讲在某些情况下,加载到内存的kernel需要被移动到合适的位置。

710行通过将bzimage_addr和image_offset相减,计算出kernel被加载到内存时的起始地址。

bzimage_addr由上可知,是startup_32的运行时地址,而startup_32又是compressed部分的起始地址,这个可以通过以下方式验证。

看上图中的选中行,startup_32的编译时地址是0,也就是说,它是compressed部分的起始地址。

而710行中的image_offset是kernel在内存的起始地址到compressed部分起始地址(startup_32的地址)的偏移量,这个在 linux内核启动流程分析 - efi_pe_entry 中有讲过。

所以,710行中的buffer_start就是kernel被加载到内存时的起始地址。

712行在buffer_start的基础上,加上hdr->init_size,得出kernel在内存中的结束地址。

hdr->init_size值的声明和初始化是在header.S里

它表示的是kernel在启动过程中需要的内存大小。

linux内核构建结束后,最终生成的文件是 arch/x86/boot/bzImage,这其实是个压缩过的内核,在kernel启动过程中,还要在内存中对内核进行解压,进而得到真正的内核。

而解压及后续过程中需要的最大内存大小,就是通过init_size指定的。

或者,我们可以将init_size认为是uefi在加载bzImage这个uefi application时,需要分配的内存大小。

综上可知,712行中的buffer_end表示的是kernel在启动过程中,需要使用的内存的结束地址。

714到717行是一些条件判断,在这些条件下,需要拷贝内存中的内核到合适的位置上。

718行的efi_relocate_kernel方法就是具体的拷贝操作,大致逻辑是,在合适的位置重新分配hdr->init_size大小的内存,然后将当前内存中的kernel,从bzimage_addr位置开始,拷贝init_size长度到新内存中,并将新内存的起始地址赋值到bzimage_addr变量里。

这里有一个疑问,通过上文我们知道,bzimage_addr中存放的是startup_32的地址,也就是bzImage中compressed部分的起始地址,而在compressed部分之前,还有setup部分。

在efi_relocate_kernel方法中,拷贝是从bzimage_addr开始,而不是从setup开始,且拷贝的大小是init_size,即kernel在内存中的总大小,那岂不是该拷贝操作会拷贝非法内存?

这个问题我想了好久,最终在build.c中找到了一些答案。

build.c是一个小工具,用于将setup部分和compressed部分拼接起来,组成最终的bzImage。

在该工具执行过程中,其有如下操作:

由上图选中行可见,uefi在以uefi application形式启动bzImage时,我们要求要为其分配的内存大小确实是init_size,但是,这个init_size是加上了CONFIG_PHYSICAL_ALIGN之后的,而CONFIG_PHYSICAL_ALIGN的默认大小是2MiB。

我们再看setup部分的大小:

远小于2MiB,所以efi_relocate_kernel中的拷贝操作虽然不是从setup开始,但也是安全的。

继续看efi_main函数的后续部分:

该部分主要是解析efi相关的一些参数及加载initrd,在此不详细讲,后面如果用到再一一展开。

继续看efi_main的剩余部分:

这里面主要看下799行,其余行后面如果用到,再回头来详细讲。

该行主要目的是调用uefi的ExitBootServices服务,告知uefi其工作已经完成,可以安全退出了,然后内核会接管uefi管理的资源,比如内存分配等。

在调用ExitBootServices之前,exit_boot方法内还会通过一定的方式,获取uefi boot service 资源管理情况,比如内存分配情况等,记录在boot_params里,供后续使用。

最后805行,返回bzimage_addr的值,即startup_32的运行地址给efi_stub_entry,函数结束。

0 人点赞