几个核心问题
- 目前 ArceOS 已经实现了“线程”组件 axtask,如何利用它转变为进程?
- 独立的地址空间
- 如何保持 unikernel 的特性?
- 仅保留单一特权级
ArceOS 作为动态链接库
目前成果
在保持 ArceOS 单特权级的情况下,支持一些简单的动态链接程序的二进制兼容:
- 编写加载器 loader 支持加载动态链接的 ELF
- 为每个应用创建虚拟地址空间(页表),将其绑定在任务控制块上
- 每个进程通过任务控制块参与调度,在
switch_to()
时切换地址空间
需要实现的功能
- 动态链接的原理:
-
plt
和got
如何配合工作:.rela.plt
:存储了加载器在动态链接时需要填写的.got
表项,以及动态链接函数的符号信息,用于索引.dynsym
中的一项:1
2
ELF64_R_SYM(Elf64_Rela.r_info).dynsym
:可以查找到和动态链接符号的相关属性,以及符号名称(位于.dynstr
)
- ABI 是如何保持的:函数签名保持一致即可
-
- ArceOS 需要能加载 ELF
- 加载时如何填写 .got
- ArceOS 的编译脚本需要修改,
- 编译 axlibc,导出符号表
- 自身能够读取 libc 的符号表
- 最好能直接将 libc 映射到一个地址段,所有进程共享
实现路径
mmap()
:难度较大,因为目前没有进程,也就没有其对应的地址空间- ELF 解析
- 动态链接
日志记录
11 月 23 日
ArceOS 的 axstd 和 axlibc 不能同时链接
- 如果强制链接的话会出现重复的符号
解决方法:将 rust_libc 和 app 链接在一起,c_libc 再和 rust 代码链接
- rust loader 可以调用 libc 的函数
- ArceOS 需要实现
mmap()
11 月 27 日
KISS 原则实现了加载时的动态链接:先不实现 mmap 和 ELF 解析,采用硬编码的方式加载 hello
ELF 解析加载过程:
- 读取 Program Headers(在 host 上用 readelf),将其中类型为
LOAD
的 segment 从文件中加载到内存。
11 月 28 日
实现了根据 ELF 头获取需要进行动态链接函数的填充地址和填充值,并进行动态链接。
ELF 解析加载过程:
- 获取 ELF 头,找到 section header table
- 根据 .shstrtab 找到:
- .rela.plt:找到外部链接符号
- .rela.dyn:链接内部符号
- .dynsym:动态链接符号表
- .dynstr:动态链接符号名的字符串表
- 外部动态链接
- 用
rela.r_sym()
索引.dynsym
,获取符号项dynsym
- 根据
dynsym
判断符号属性,符合动态链接的用dynsym.st_name
索引dynstr
获取符号名func_name
- 根据符号名查找 loader 初始化时填写的链接表,获取链接虚地址
link_vaddr
- 将虚地址
rela.r_offset
处的 8 字节填写为link_vaddr
- 用
- 内部动态链接
rela.r_addend
的值即为链接虚地址link_vaddr
- 将虚地址
rela.r_offset
处的 8 字节填写为link_vaddr
11 月 29 日
根据 ELF 头读取 segment 信息,将其中属性为 LOAD 的段加载到内存。此时对 kernel 而言,hello app 不具有单独的进程(或者线程)属性,而是与 loader 为一体。
在 loader 的 Cargo.toml 开启 axstd 的 multitask 属性,可以正常编译,但内核 panic 退出,信息为 current task is uninitialized
,定位到 axtask::task::CurrentTask::get()
函数。显然,在初始化时,已经设置了 current task
,这里为何报错?
经过进一步阅读源码,发现 current task
的指针通过 axhal::cpu::current_task_ptr()
获取,而 axtask/multitask
开启了 percpu
功能,也就是说此时的 current task
指针是一个 percpu
变量。目前,ArceOS 中通过 gp
实现 percpu
,这与 riscv 的 ABI 是不符的。具体体现为 hello app 一开始就重新加载了 gp
,把内核里维护 percpu
的值冲掉了。
因此,为了保持 ABI,我将 percpu
改回了使用 tp
维护(前一个修改的 commit 将 tp
改为 gp
)。此时在打开 axstd/multitask
后可以正常退出。
11 月 30 日
- 实现
axtask::spawn_from_ptr()
,专门用于创建从 ELF 加载任务的 TCB - 在 riscv 的任务上下文中加入
satp
,context_switch()
时同时切换页表 - 目前实现了动态分配的页表,但未实现进程退出后页的释放
12 月 1 日
通过复用 axhal::paging::PageTable
的接口,实现了完整的页表分配、回收功能,并且修改 TaskInner
结构体,将页表绑定到上面。这样就实现了类似于 rCore 中的 RAII 风格页表。
总结
目前的实现充分利用了 ArceOS 中已经存在的 axlibc 和页表,不足之处:
- loader 对于 ArceOS 而言也是一个应用程序,但是其中跨层级调用了很多底层接口,不符合 ArceOS 的设计原则
- 不支持静态链接的 app,即程序中的系统调用指令 目前 ArceOS 无法处理
- 修改了一些 ArceOS 中的现有模块,使得原有模块的实现发生改变,如利用
gp
保存percpu
变量,为了验证思路的可行性,将其改为了tp