Magenta-Userboot
在kernel初始化完毕后,需要跳转至user space并初始化user的init进程(devmgr),此也是user的第一个进程。为了可以顺利的启动user进程,magenta在 编译,初始化和启动 阶段分别做了特殊处理。
userboot就是专为此而实现的模块。包括:
- kernel userboot:运行在kernel空间,为进入user空间做准备
- user userboot:运行在user空间,加载init进程。
编译阶段
user userboot的编译
以目录 /system/core/userboot为基础,生成libuserboot.so,这是user userboot的主体部分。libuserboot.so需要通过sys-call功能访问magenta。所以基于目录/system/ulib/magenta 生成了动态库libmagenta.so。其内容形如其下:
...
00000000000064cc <_mx_handle_close>:
64cc: 41 52 push %r10
64ce: 41 53 push %r11
64d0: 49 89 ca mov %rcx,%r10
64d3: b8 02 00 00 00 mov $0x2,%eax
64d8: 0f 05 syscall
00000000000064da <CODE_SYSRET_mx_handle_close_VIA_mx_handle_close>:
64da: 41 5b pop %r11
64dc: 41 5a pop %r10
64de: c3 retq
...
可见实现了系统调用接口。user code可以加载此.so以访问kernel所提供功能。
但是user userboot比较特殊,它运行在kernel创建的进程中,且是第一个user process。在它进入user space时,没有现成的user environment,需要自己创建并配置。由于libuserboot.so在初期就需要访问kernel,为了减少加载.so时所需的重定位等等步骤,magenta对libuserboot.so的编译做了特殊处理,即在编译libuserboot.so阶段就固定libuserboot.so和libmagenta.so的相对地址。这样libuserboot.so可以以PC-relative的方式访问libmagenta.so提供的函数。为了达到此目的,需要解析libuserboot.so和libmagenta.so中的代码和数据的layout结构。
编译libuserboot.so后,根据链接脚本rodso.ld,以及其中定义的 CODE_START/CODE_END,利用脚本生成文件userboot-code.h,其内容形如:
#define USERBOOT_FILENAME "./build-magenta-pc-x86-64/system/core/userboot/libuserboot.so"
#define USERBOOT_DATA_START_dynsym 0x0000000000000298
#define USERBOOT_DATA_END_dynsym 0x00000000000002b0
#define USERBOOT_CODE_START 0x0000000000003000
#define USERBOOT_ENTRY 0x0000000000003a40
#define USERBOOT_ENTRY_SIZE 0x00000000000009df
#define USERBOOT_CODE_END 0x000000000000d000
可以得到libuserboot.so的文件偏移layout。这是供kernel userboot加载libuserboot.so时所用。
编译libmagenta.so后,根据链接脚本rodso.ld,以及其中定义的 CODE_START/CODE_END,利用脚本生成文件vdso-code.h,其内容形如:
#define VDSO_FILENAME "./build-magenta-pc-x86-64/system/ulib/magenta/libmagenta.so"
#define VDSO_DATA_START_dynsym 0x0000000000000c38
#define VDSO_DATA_END_dynsym 0x00000000000027b0
#define VDSO_DATA_CONSTANTS 0x0000000000003d20
#define VDSO_DATA_CONSTANTS_SIZE 0x0000000000000018
#define VDSO_CODE_START 0x0000000000006000
#define VDSO_CODE_soft_ticks_get 0x00000000000062a0
#define VDSO_CODE_soft_ticks_get_SIZE 0x0000000000000007
....
#define VDSO_CODE_END 0x0000000000007000
...
可以得到libmagenta.so的文件偏移layout。这是供kernel userboot加载libmagenta.so时所用。
为了让libuserboot.so可以直接访问到libmagenta.so,基于libmagenta.so,利用脚本生成2个文件vdso-syms.h、vdso-syms.ld,供libuserboot.so编译和链接使用。此2个文件形如其下:
vdso-syms.h:
...
FUNCTION(_mx_handle_close, 0x00000000000064cc, 0x0000000000000013)
WEAK_FUNCTION(mx_handle_close, 0x00000000000064cc, 0x0000000000000013)
...
vdso-syms.ld:
...
PROVIDE_HIDDEN(_mx_handle_close = CODE_END + 0x00000000000064cc);
...
宏的定义如下:
#define FUNCTION(name, address, size) \
PROVIDE_HIDDEN(name = CODE_END + address);
#define WEAK_FUNCTION(name, address, size) FUNCTION(name, address, size)
注:CODE_END指libuserboot.so的代码结束地址,即 USERBOOT_CODE_END。定义了属性PROVIDE_HIDDEN,可以生成简单的PC-relative 代码。
经过上述处理后,如libuserboot.so需要访问系统调用mx_handle_close,则会直接调转至CODE_END + 0x00000000000064cc处。所以需要kernel userboot将libmagenta.so加载至libuserboot.so其后。下图是libmagenta.so和libuserboot.so两者之间的布局示意图。
最后,需要利用宏
RODSO_IMAGE
将libmagenta.so和libuserboot.so作为数据内嵌到magenta.bin文件中,不依赖文件系统就可以访问此2个.so文件。
初始化阶段
此阶段指的是kernel userboot为进入user space所做的各种准备,所需资源包括如下:
enum bootstrap_handle_index {
BOOTSTRAP_VDSO,
BOOTSTRAP_VDSO_LAST_VARIANT = BOOTSTRAP_VDSO + VDso::variants() - 1,
BOOTSTRAP_RAMDISK,
BOOTSTRAP_RESOURCE_ROOT,
BOOTSTRAP_STACK,
BOOTSTRAP_PROC,
BOOTSTRAP_THREAD,
BOOTSTRAP_JOB,
BOOTSTRAP_VMAR_ROOT,
BOOTSTRAP_HANDLES
};
kernel userboot会创建各种object和其对应的handle,并将handle转移并映射到新进程中,从而新进程启动后可以访问此些资源。
- 创建Job Object: 在root job下创建一个新的job obj,并同步创建对应的handle,此即是BOOTSTRAP_JOB。
- 创建Process Object :在root job下创建name是 “userboot”的process obj,并创建一个handle 指向此obj,此handle即BOOTSTRAP_PROC;同步也创建的进程的vmaro(virtual memory address region object),用于管理进程的的虚拟空间,并同步创建对应的handle,此即是BOOTSTRAP_VMAR_ROOT。
- 创建Thread Object : 在”userboot”的process中创建name是 “userboot”的thread obj,并创建一个handle指向此obj,此handle即BOOTSTRAP_THREAD;
- 创建Ramdisk Object: 根据Ramdisk在ram中的基地址和长度,创建vmo(virtual memory object)。vmo可以理解为RAM中的数据块,可以通过handle对此数据块读写。然后创建一个handle指向此obj,此handle就是BOOTSTRAP_RAMDISK。user process可以根据此obj读取Ramdisk并解析,然后将文件释放到文件系统中。
- 创建VDSO Object: 根据vdso在ram中的基地址和长度,创建vmo。并同步创建对应的handle,此即是BOOTSTRAP_VDSO;
- 创建Stack Object: 创建一个大小为stack size的vmo。并同步创建对应的handle,此即是BOOTSTRAP_STACK。此vmo在此阶段并没有实际分配物理page,待后期stack缺页中断时再分配物理page。
- 创建Resource Object: 创建root resource,并同步创建对应的handle,此即是BOOTSTRAP_VMAR_ROOT。
创建完所需资源后,开始对user userboot的加载:
- 根据libuserboot.so在ram中基地址和长度,创建 “userboot” vmo;
- 结合”userboot” vmo和vdso vmo的总长度,即libmagenta.so和libuserboot.so的总长度,在新进程的虚拟空间中分配对应长度的虚拟空间vmar,然后将这2个vmo映射至此vmar。
- 将stack obj映射进新进程的虚拟空间,从而新线程有课自己的栈空间;
- 创建channel obj,一端定义为kernel_channel obj,另一端定义为user_channel_handle obj。kernel利用kernel_channel将boot trap msg写入此channel; 创建一个handle指向user_channel_handle obj,并将此handle映射进新进程的handle空间,从而在新进程的user space可以通过此handle读取到boot trap msg。
- 根据映射时得到的libmagenta.so和libuserboot.so虚拟地址,启动新线程,并将user_channel_handle 和vdso基地址作为参数传给user code。
以上加载时的log表现如下:
[00000.064] 00000.00000> userboot: ramdisk 0x358000 @ 0xffffff8000252000
[00000.069] 00000.00000> userboot: userboot rodata 0 @ [0xe3d00fc6000,0xe3d00fc9000)
[00000.069] 00000.00000> userboot: userboot code 0x3000 @ [0xe3d00fc9000,0xe3d00fd3000)
[00000.069] 00000.00000> userboot: vdso/full rodata 0 @ [0xe3d00fd3000,0xe3d00fd9000)
[00000.070] 00000.00000> userboot: vdso/full code 0x6000 @ [0xe3d00fd9000,0xe3d00fda000)
[00000.071] 00000.00000> userboot: entry point @ 0xe3d00fc9a40
新线程经如下调用
UserThread::Start
--》 UserThread::StartRoutine
--》arch_enter_uspace
切换至user space。不同的arch,arch_enter_uspace的实现不一样。
启动阶段
此阶段是在user space,新线程的entry地址是_start,实现在文件start.c中。有如下流程:
- 从bootstrap_message中读取环境变量和参数,以及handles;
- 在channel read时,kernel会将handles映射于当前process中,且将得到的
handle返回给user;
- 在channel read时,kernel会将handles映射于当前process中,且将得到的
- 创建process “bin/devmgr”
- load文件”bin/devmgr”至process “bin/devmgr”;
- 由于devmgr中有section interp,所以其实是load此interp “lib/ld.so.1”
- 将相关msg写入msgpipe,以便ld.so.1读取并解析;参数包括需要加载的文件名,即”bin/devmgr”;
- 创建并映射stack至process “bin/devmgr”;
- 创建新thread,
- 将启动参数和handles写于msgpipe;
- start process, 在新进程中执行lib/ld.so.1的entry函数。
- 启动loader_service,以响应ld.so.1的加载module的请求。如ld.so.1依赖其他lib,则当ld.so.1初始化时,需要加载依赖库。如”bin/devmgr”有依赖libs,也需此service帮忙load libs。当从ld.so.1跳转至“bin/devmgr”后,会关闭此service。
- 如果loader service退出了,则”userboot”进程退出。
注: ld.so.1 其实本身就是libc.so(musl)
从上可见,分了2个步骤来加载文件”bin/devmgr”:
- libuserboot.so在用户进程中加载ld.so.1,并跳转至其entry函数;
- ld.so.1完成其自身初始化后,加载文件”bin/devmgr”;
libc.so的entry函数是_start,不同arch有不同的定义,但流程都一样。
- _start
- _dl_start :重定位;
- __dls2 :映射”libc.so”和””,这2个模块由libuserboot.so之前已经加载完毕
- __dls3 :读取并解析bootstrap msg,包含handles以及argc/env,配置了当前进程的运行环境;得到application vmo,即文件”bin/devmgr”的vmo。
- dls3 : load “bin/devmgr” vmo至当前process;在此过程中,以请求loader service加载依赖libs;
- jump to application entry(即devmgr entry)
相关的log如下:
[00000.119] 01029.01036> userboot: searching bootfs for program "bin/devmgr"
[00000.120] 01029.01036> userboot: bin/devmgr has PT_INTERP "lib/ld.so.1"
[00000.120] 01029.01036> userboot: searching bootfs for dynamic linker "lib/ld.so.1"
[00000.126] 01029.01036> userboot: process bin/devmgr started.
[00000.126] 01029.01036> userboot: waiting for loader-service requests...
以上是 libuserboot.so运行时的log。
[00000.128] 01029.01036> userboot: searching bootfs for shared library "lib/libfs-management.so"
[00000.129] 01029.01036> userboot: searching bootfs for shared library "lib/liblaunchpad.so"
[00000.130] 01029.01036> userboot: searching bootfs for shared library "lib/libmxio.so"
[00000.133] 01043.01046> Loaded at [0x51ab193c6000,0x51ab193e4000): <application>
[00000.133] 01043.01046> Loaded at [0x6904190c000,0x69041910000): libfs-management.so
[00000.133] 01043.01046> Loaded at [0x229a645d5000,0x229a645de000): liblaunchpad.so
[00000.133] 01043.01046> Loaded at [0x3f0516fc9000,0x3f0516fe2000): libmxio.so
[00000.133] 01043.01046> Loaded at [0x6212fec84000,0x6212fec8b000): <vDSO> (libmagenta.so)
[00000.133] 01043.01046> Loaded at [0x65db29b63000,0x65db29c43000): libc.so
以上是libc.so运行时的log。可见加载了3个依赖libs。
[00000.141] 01043.01046> devmgr: main()
以上是libc.so初始化完后,跳转至devmgr。
...
[00000.147] 01029.01036> userboot: loader-service channel peer closed
[00000.147] 01029.01036> userboot: finished!
以上是loader service退出后,userboot进程也自动退出。