Zircon内核到用户空间启动流程(userboot)

Zircon是一种微内核的设计风格。微内核设计的一种复杂性在于如何引导初始的用户空间进程。通常达到这一功能是通过让内核实现以引导为目的的最小版本的文件系统读取和程序加载,这些内核功能可能在引导之后再也不会使用。Zircon采用了不同的方法。

Boot loader 与内核启动

引导加载程序boot loader将内核加载到内存中,并将控制权转交到内核的启动代码。引导加载程序协议的详细信息不在此处描述。Zircon使用的boot loader以Zircon Boot Image的格式加载内核镜像与数据块(data blob)。ZBI格式是嵌入了boot loader传递的参数项的容器格式,包括硬件特定信息、提供引导选项的[kernel “command line”](kernel_cmdline.md)和RAM Disk镜像(通常是压缩格式的)。内核在启动的早期阶段提取了一些供它自身使用的基本信息。

BOOTFS

ZBI中嵌入的项目之一是初始RAM Disk文件系统镜像。其通常使用LZ4压缩格式。一旦解压缩,镜像将采用BOOTFS格式。这是一个小型的只读文件系统格式,简单的列出了文件名,以及每个文件在BOOTFS镜像中的偏移量和大小(两个值都必须以页面对齐,限制为32位)。

主BOOTFS镜像包含用户空间系统需要运行的所有:可执行文件、共享库和数据文件。这些当中包括设备驱动程序和更高级的文件系统的实现,使从存储设备或网络中读取更多代码和数据成为可能。

系统自启动后,主BOOTFS镜像中的文件作为只读文件系统挂载到/boot目录下(并由bootsvc管理)。

内核加载userboot

内核不包含任何用于解压缩LZ4格式的代码,也不包含能够解释BOOTFS格式的任何代码。相反,所有这些工作由第一个用户空间进程userboot完成。

userboot是一个常规的用户空间进程。它只能像其它进程一样,通过vDSO请求标准的系统调用,以及完全受到vDSO enforcement的约束。userboot的特殊之处在于它的加载方式。

userboot程序以ELF动态共享对象格式生成,使用与vDSO相同的RODSO layout布局。类似于vDSO,userboot ELF镜像在编译时嵌入到的内核中。它的简单布局意味着要加载它不需要内核在引导时解释ELF头部信息。内核只需要知道三件事:只读段的大小、可执行段的大小和userboot执行入口点。在编译时,由userboot程序的ELF镜像中提取这些值,在内核代码中当中常量使用。

像其它进程一样,userboot必须在执行前确保VDSO已经映射到其地址空间,以便进行系统调用。内核将userboot和vDSO映射到第一个用户进程中,然后在userboot入口点启动它。

内核发送processargs消息

在常规 program loading,过程中,发送一个 bootstrap message 到新进程中。进程的首个线程接收一个位于寄存器中的channel句柄。接下来可以读取进程创建者发送的数据和句柄。

内核使用与以上完全相同的协议来启动userboot。内核命令行被拆分为单独的词,这些词将成为引导消息中的环境变量字符串。userboot本身需要的所有句柄,以及系统的其余部分访问内核设施所需的句柄,都包含在此消息中。按照正常格式,handle info enties描述每个句柄的用途。其中包括PA_VMO_VDSO 句柄

userboot在vDSO中查找system calls

用于通知新进程VDSO映射的standard convention标准,要求进程来解释vDSO的ELF头部信息和符号表,用于定位系统调用入口点。为了避免这种复杂性,userboot以一种不同的方式在vDSO中查找系统调用入口点。

当内核将userboot映射到第一个用户进程时,它选择内存中的一个随机位置,就像正常程序加载时一样。但是,当它映射vDSO时,它不会像在通常情况下选择另一个随机的位置。相反,它会将vDSO镜像正好放置在内存中的userboot镜像之后。这样,vdDSO代码总是位于userboot代码的固定偏移处。

在编译时,所有系统调用入口点的符号表条目都将由vDSO的ELF镜像中提取出来。然后,发送给链接器脚本符号定义,由于每个符号在vDSO镜像中的偏移是固定的,使用该偏移来定义位于链接器提供的_end符号的固定偏移符号。这样,userboot代码可以直接调用每个vDSO入口点,因为这些入口将在userboot镜像本身之后的正确内存位置中。

userboot解压BOOTFS

userboot做的第一件事是读取内核发送的启动引导消息。所有它从内核获得的句柄中,有一个handle info entry PA_HND(PA_VMO_BOOTDATA, 0)。这是一个VMO包含来自boot loader的ZBI镜像。userboot从这个VMO中读取ZBI头部信息,查找第一个类型为ZBI_TYPE_STORAGE_BOOTFS的项。其包含BOOTFS镜像。此项的ZBI头部指示它是否被压缩,通常是这样。userboot映射VMO的这一部分。userboot包含LZ4格式支持代码,用于将此项解压缩到新的VMO中。

userboot由BOOTFS加载第一个"real"用户进程

接下来,userboot检查它从内核接收到的环境变量,其表示内核命令行。如果存在字符串userboot=file,随后file将作为第一个实际用户进程加载。如果不存在这样的项,则默认的filebin/bootsvc。其包含在BOOTFS镜像中。

要加载文件,userboot实现了一个功能齐全的ELF程序加载器。通常加载的文件是一个动态链接的可执行文件,带有PT_INTERP程序头部。在本例中,userboot查找以PT_INTERP命名的文件并加载。

然后,userboot加载vDSO到一个随机的地址。使用标准约定启动一个新的进程,传递一个通道channel句柄和vDSO基址。在通道句柄上,userboot发送标准的processargs信息。它传递从内核接收到的所有重要句柄(需要替换特定的句柄,如进程本身和线程本身句柄,替换为新进程本身的,而不是userboot进程的)。

userboot加载器服务

遵循标准程序加载协议,当userboot通过PT_INTERP加载程序,在主消息之前发送额外的processargs消息,旨在使用动态链接器。此消息包括一个PA_LDSVC_LOADER通道句柄,在此通道上,userboot提供一个最小实现的标准loader service.

userboot只有一个线程,它处于循环中,处理加载器的服务请求,直到通道channel关闭。当它收到LOADER_SVC_OP_LOAD_OBJECT请求,它在BOOTFS中查找前缀为lib/的对象名文件,并返回其内容的VMO。因此,第一个"real"的用户进程可以(通常是)一个动态链接的可执行文件,其运行需要各种共享库。动态链接器、可执行文件和共享库都是从相同的BOOTFS页面加载,其后以文件的形式显示在/boot目录中。

将由userboot加载的可执行文件(i.e. bootsvc)通常应在启动完成之后关闭它的加载器服务通道。这让userboot知道它不再需要了。

userboot进程结束

当加载器服务通道关闭的时候(或者如果可执行文件没有PT_INTERP,因此不再需要加载器服务,进程已经启动完成),userboot不再有任何操作。

如果userboot.shutdown 选项指定在内核命令行中userboot将等待它开启的进程退出,然后关闭系统(就像通过dm shutdown命令)。这对于运行单个测试程序,然后关闭计算机(或模拟器)很有用。例如,命令行userboot=bin/core-tests userboot.shutdown运行Zircon的核心测试程序,然后关闭。

否则,userboot不等待其启动的进程退出。userboot立即退出,留下第一个"真正的"用户进程负责处理系统其它部分的启动和关闭。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值