0%

2023开源操作系统训练营第三阶段项目一最终任务总结报告-TheSayOL

2023秋冬季开源操作系统训练营第三阶段总结报告

前言

此为训练营第三阶段Unikernel项目最终任务的报告

  • 描述了我对arceos支持Linux原始应用以及支持多应用的实现思路以及验证过程

思路

支持 Linux 原始应用

在思路一的基础上支持思路二

思路一

  • 需要获得应用源码
  • 将应用和arceos一起静态编译

思路二

  • 应用不需要重新编译, 但必须动态编译
  • 实现一个和libc接口一致的动态库
  • 运行时, 使用一个动态链接器将应用重定位到该动态库
  • 该动态库必须知道arceos系统调用函数的地址
  • 动态库不使用中断, 而是用事先获得的地址, 直接跳转过去进行处理
  • 细节参考第二周的练习4

本思路

  • 应用不需要重新编译, 但必须动态编译
  • 实现一个和libc接口一致的库, 和一个动态链接器, 两者和arceos一起静态编译
  • 如此一来, 库的内部实现可以直接调用arceos函数
  • 应用装入内存时, 用动态链接器重定位到库函数

支持多应用

每个应用都有一个arceosTCB, 在其中保存一些关键信息

  • 应用有自己的内存空间, 所以需要保存自己的页表
  • 应用可以创建线程, 所以需要保存子线程的TCB
  • 多应用情况下, 必须进行内存的管理, 所以还需要保存应用申请的物理页

原理和实现

支持 Linux 原始应用

实现接口一致的库

这部分很简单

  • 对外接口和libc一致即可, 目的是让应用感知不到动态库被替换
  • 内部实现随意, 只要达到对应功能即可
  • 因为和arceos静态链接, 调用系统调用十分方便
  • 具体实现可以参考或者直接使用axlibc
  • 也可以”翻译”glibcmusl-libc

实现的库函数

参考 musl-libc 1.2.4 源码

对于一个C程序, 其启动流程如下

  • 内核将一些信息放入栈中, 随后跳转到程序入口_start
  • _start, 将当前sp作为参数, 跳转到_start_c
  • _start_csp里解析出argcargv, 跳转到__libc_start_main
  • __libc_start_main进行初始化工作, 完成后跳转到应用的main
  • main执行完毕, 回到__libc_start_main
  • __libc_start_main完成收尾工作

对于动态编译的程序

  • 上述提高的函数里, 仅有_start, _start_c, main
  • 当然可能还有其他函数比如_init, _fini等, 这些往往作为参数交给__libc_start_main进行处理
  • __libc_start_main本身是不包括在程序里的, 也就是说, 需要由我们实现

__libc_start_main的实现

  • 对于一个hello world程序而言, __libc_start_main的实现十分简单
  • 不需要进行初始化工作, 直接跳转到main即可
  • 也不需要进行收尾工作, 直接exit即可

动态链接器

动态链接器自举

  • 动态链接器装入内存的地址不确定, 需要自举, 即对自己重定位, 这是动态链接器最复杂的部分
  • 自举时无法使用标准库功能, 甚至不能进行函数调用
  • 但因为该动态链接器和arceos静态编译, 所以不需要自举

动态重定位

对于一个动态链接的应用

  • 函数调用的地址在编译期无法确定, 需要在运行时查询GOT来确定
  • GOT可以确定一个函数名到函数地址的映射
  • 装入时重定位: 应用在装入时, 动态链接器修改GOT, 将函数地址改成正确对应地址
  • 运行时重定位: 动态链接器将函数地址改成dl_runtime_resolve()的地址, 仅当应用调用函数的时候才修改GOT为正确地址

本实现里, 使用装入时重定位

  • 应用装入内存时, 修改GOT, 改成上文的”实现接口一致的库”的对应函数的地址
  • 如此一来, 应用调用库函数便会跳转到期望的地址执行

支持多应用

内存管理

arceos的设计

  • 只支持单应用, 内核以库的形式存在, 即libos
  • 应用拥有所有内存空间, 随意使用

为了支持多应用, 需要对应用进行内存管理

  • TCB里使用一个数组保存应用使用的物理内存
  • 当应用退出时, 回收所申请的内存

应用独立地址空间

arceos的设计

  • 只支持单应用, 因此不需要进行页表切换
  • 内核初始化时初始一张页表, 映射内核的数据
  • 将该页表作为根页表写入页表寄存器, 之后不再改动

为了支持多应用

  • 每个应用在建立的时候, 都为之新建一张页表, 保存在TCB
  • 页表初始化时, 也需要映射内核的数据
    • 对于sv39, 地址空间分为高256G和低256G
    • arceos在完成初始化工作之后, 将自己的运行地址设置在了256G
    • 因此应用运行在低256G即可, 高256G留给内核运行
  • 设计应用二进制数据的起始地址为0x10000, 重定位完毕之后, 将数据写入该地址
    • 申请应用和数据大小一致的物理内存
    • 将应用数据写入该物理内存
    • 在页表上将0x10000映射到该内存地址

支持页表切换

  • 调度时, 需要将切换页表, 将地址空间从上一个应用切换到下一个
  • 系统初始化创建main进程运行, 但是此时没有发生调度, 因此需要手动将页表切换成main的页表

将来的改进

完善库函数

  • 完善__libc_start_main, 初始化和收尾工作必须符合应用期望
  • 可以直接用成熟的__libc_start_main实现
  • 也可以直接”翻译”一个libc中的相关实现

支持运行时重定位

  • 目前支持装入时重定位, 在面对复杂应用时, 重定位工作很耗时间
  • 可以进一步改为运行时重定位, 减少应用启动时间

支持应用创建子线程

  • 可以在tcb中保存一个pid
  • 创建子线程时, 将pid设置为一致, 表示属于同一个进程

验证

过程

  • 下载riscv版本的Ubuntu, 使用qemu安装并运行
  • 在其中下载musl-libc源码, 编译
  • 创建两个hello world应用, 使用musl-gcc编译
  • 复制到arceos项目文件中, 运行arceos, 成功