首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >linux内核启动流程分析 - efistub的入口函数

linux内核启动流程分析 - efistub的入口函数

作者头像
KINGYT
发布2020-06-16 17:31:53
3.2K0
发布2020-06-16 17:31:53
举报

最近打算写一个系列文章,主要讲linux内核的启动流程。

网上类似标题的文章很多,但大都是从start_kernel讲起,我觉得这是远远不够的。

在start_kernel之前还有很多很多的内容,而且是更复杂,更难以理解的部分,需要阅读大量的代码,以及有非常多的知识储备才能串联起来,我觉得这部分反而应该是要重点讲的。

linux内核的启动流程涉及的东西非常多,而且偏硬件,比较难理解,写这个系列其实还是挺有难度的,我会尽量讲的透彻一点,尽量不敷衍每个细节。

好,那今天我们就从如何找到efi stub的入口函数讲起。

linux内核的启动方式有非常多种,大方向来看分为bios和uefi,在此基础上又有各种各样的boot loader,比如我们常见的grub,它们会根据配置信息,加载linux内核到内存,并通过一定的协议来启动linux内核。

我们今天要讲的是efi stub的方式,你可以把它理解成另一种boot loader,只是它是内置在linux内核里的。

通过efi stub,linux内核可以在不使用grub等传统boot loader的情况下,直接在uefi硬件上,以uefi application的方式启动,可以说是非常简单。

该方式为我们研究内核启动降低了不少难度,我们不用再去看类似于grub等boot loader的代码了,从开机到启动完毕的所有流程代码,在内核里都可以找到,完美。

不过这也需要我们阅读大量的相关材料,比如 uefi 的各种specification(https://uefi.org/specifications),以及 uefi application 的具体文件格式(linux内核是以 uefi application 方式启动的,uefi 指定了 application 的格式为pecoff,该格式文档可参考其官方说明 https://docs.microsoft.com/en-us/windows/win32/debug/pe-format)。

在上一篇文章 聊聊各种操作系统都在使用什么样的格式来存储可执行文件及目标文件 中也有提到pecoff格式,该格式为windows平台可执行文件及目标文件等的默认存储格式。

好,我们再回到原主题,来继续说efi stub。

efi stub是linux的一个feature,它可以通过配置 CONFIG_EFI_STUB 来开启和关闭。

它的实现原理是,按照 uefi 指定的 pecoff 格式,将内核伪装成一个 uefi application,这样在支持 uefi 的各种硬件上,就可以按照 uefi 协议,直接启动linux内核了。

所以下面的代码介绍和 pecoff 格式定义息息相关,建议先通读上面链接中 pecoff 格式文档,这样理解下面的内容就非常容易了。

linux内核efi stub有关pecoff 格式定义的部分都在 arch/x86/boot/header.S 这个文件里,具体如下:

其中 AddressOfEntryPoint字段填充的就是 efi stub 的入口函数地址,或者说是 uefi application 的入口函数地址,这个可以从 pecoff 文档

以及uefi specification中得到确认。

好,既然这个就是我们要找的 efi stub 的入口函数,那我们来看下它具体的值是什么。

由上面可见,它的初始值是0,然后注释中说它真正的值会在build.c中设置。

build.c其实是内核的一个小工具,在构建linux内核时,make最终会调用该工具把内核编译后的各个部分,组装成最终的bzImage。

bzImage的文件结构大致为:

setup部分 - 对应到 arch/x86/boot/ 中的代码

compressed部分 - 对应到 arch/x86/boot/compressed/ 中的代码

这两个部分都可以认为是内核启动流程的部分,并不是真正的内核逻辑,真正内核逻辑被压缩到了compressed部分里的piggy.S文件里。

有关编译后的bzImage的layout情况,我们会在另一篇文章中详细讲,这里只需要知道,build.c这个工具把这些部分按照一定的顺序结合在一起,生成了最终的bzImage。

我们再来回头看下build.c中是如何设置efi stub的AddressOfEntryPoint的:

上面选中行就是设置AddressOfEntryPoint的部分,其中text_start你可以认为是compressed部分的起始地址,而efi_pe_entry就是我们最终要找的 efi stub 入口函数。

选择行中将 text_start + efi_pe_entry (efi_pe_entry运行时的地址) 的结果,赋值到 pe_header + 0x28 指向的内存里,结合 pecoff 文档以及上面的header.S文件内容,我们可知这个地址就是 header.S 文件中的 AddressOfEntryPoint 变量的地址。

这也说明了该选中行确实是在设置 AddressOfEntryPoint。

如果看过build.c中的代码,你会发现 efi_pe_entry 也是一个变量,那该变量具体指向的是哪个函数呢?

还是在build.c里,由上可见,efi_pe_entry是从zoffset.h中解析出来的,而zoffset.h是在make的过程中生成的:

zoffset.h的生成方式是用nm命令查询compressed中vmlinux的各种指定symbol的地址,zoffset最终文件内容如下:

也就是说,build.c中解析的 efi_pe_entry 其实指向的就是 compressed 部分中的某个函数,我们搜索后会发现这个:

这个就是我们最终要找的函数了。

这时,有些同学可能会有疑问,不是说是compressed部分里的代码吗,这怎么是driver里的代码了?

看compressed部分的makefile:

看上面选中行,compressed部分在编译时,也把libstub目录中的代码包含进来了。

现在,我们就找到了efi stub的入口函数。

这样,当linux内核以 uefi application 的形式,被 uefi 直接启动时,被执行的第一行代码就是这个方法。

或者说,在 uefi 平台上,以 efi stub形式启动内核时,开机后内核执行的第一个方法就是该方法。

以这里为起点,我们就可以开始探索linux内核的完整启动流程了。

该系列后面的文章会从这里继续介绍。


该文章虽然写的比较多了,但是还是忽略了很多细节没讲,如果有问题,可以留言讨论,重要的细节后面还会另写文章单独介绍。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-06-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Linux内核及JVM底层相关技术研究 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档