专栏首页Linux内核及编程语言底层相关技术研究linux内核启动流程分析 - efistub的入口函数

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

最近打算写一个系列文章,主要讲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内核的完整启动流程了。

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


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

本文分享自微信公众号 - Linux内核及JVM底层相关技术研究(ytcode),作者:wangyuntao

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-06-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • linux内核启动流程分析 - efi_pe_entry

    接上一篇文章 linux内核启动流程分析 - efistub的入口函数,我们继续看efi_pe_entry这个函数。

    KINGYT
  • linux内核启动流程分析 - efi_stub_entry

    接上一篇文章 linux内核启动流程分析 - efi_pe_entry,我们继续看efi_stub_entry函数。

    KINGYT
  • linux内核启动流程分析 - efi_main

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

    KINGYT
  • linux内核启动流程分析 - startup_64

    在前面的第三篇文章中我们说道,efi_stub_entry最终会调用startup_64,那这篇文章我们就来看下startup_64的具体逻辑。

    KINGYT
  • 第3阶段——内核启动分析之创建si工程和分析stext启动内核函数(4)

    目标: (1)创建Source Insight 工程,方便后面分析如何启动内核的 (2)分析uboot传递参数,链接脚本如何进入stext的  (3) 分析st...

    张诺谦
  • 第3阶段——内核启动分析之创建si工程和分析stext启动内核函数(4)

    目标: (1)创建Source Insight 工程,方便后面分析如何启动内核的 (2)分析uboot传递参数,链接脚本如何进入stext的  (3) 分析st...

    张诺谦
  • Spark内核详解 (3) | Spark集群启动流程的简单分析

    本片博文主要分析的是Standalone 模式下 Spark 集群(Master, work)启动流程

    不温卜火
  • 超详细分析Bootloader(Uboot)到内核的启动流程(万字长文!)

      Bootloader的启动过程可以分为单阶段、多阶段两种。通常多阶段的 Bootloader能提供更为复杂的功能以及更好的可移植性。从固态存储设备上启动的 ...

    嵌入式与Linux那些事
  • Linux-分析ifconfig到内核的调用过程,实现内核启机自动设MAC地址(原)

    因为ifconfig是命令,代码位于busybox,不过我们在内核的documentation目录下找到了ifconfig介绍,代码介绍文件位于:

    张诺谦
  • 《Linux内核分析》之分析fork函数对应的系统调用处理过程

    xref: /linux-3.18.6/include/linux/sched.h

    WindCoder
  • 从创建进程到进入main函数,发生了什么?

    前几天,读者群里有小伙伴提问:从进程创建后,到底是怎么进入我写的main函数的?

    CSDN技术头条
  • Android启动流程——1序言、bootloader引导与Linux启动

    前面讲解的很多内容都很抽象,所以本次系列决定"接点地气",准备开始讲解大家熟悉的Activity了,为了让我以及大家更好的理解Activity,我决定本系列...

    隔壁老李头
  • linux内核启动过程分析

    start_kernel是内核启动阶段的入口,通过单步调试,可以发现它是linux内核执行的第一个init,我们单步进入看看它做了哪些操作:

    De4dCr0w
  • Linux内核调试技术——kprobe使用与实现(四)

    Linux内核调试技术——kprobe使用与实现(四)--kprobe内核注册过程

    用户5807183
  • 干货 | Linux系统行为新型实时监控技术

           万物互联和大数据技术的发展,让我们的生活更加活色生香,其背后离不开安全、稳定可靠的服务器系统。

    C4rpeDime
  • Linux 的启动流程

    本篇的重点是讲解设备和驱动的启动流程,设备和驱动的流程是整个内核启动的核心,也是工作中最常面对的问题。出于知识点的系统性考虑,在进入主题之前我们先看下整个 Li...

    刘盼
  • Linux架构

    我以下图为基础,说明Linux的架构(architecture)。(该图参考《Advanced Programming in Unix Environment》...

    Vamei
  • Linux X86-ACPI PNP Hardware ID的识别框架

    基于X86架构的Linux内核,在移植驱动的过程中,发现GPIO和I2C的device ID添加到pnp驱动框架后无法进入probe函数,后面找了下原因,因为...

    morixinguan
  • Android系统启动流程(一)解析init进程

    前言 作为“Android框架层”这个大系列中的第一个系列,我们首先要了解的是Android系统启动流程,在这个流程中会涉及到很多重要的知识点,这个系列我们就来...

    用户1269200

扫码关注云+社区

领取腾讯云代金券