[TOC]

0. 问题的出现

因为在ArceOS开发组实习中,我经手的项目是令ArceOS支持原生Linux应用,所以我对这些问题比较熟悉,在此写一篇文章来介绍这个过程。

1. ArceOS的不同

ArceOS是一个Unikernel的操作系统,具体什么是Unikernel我不做过多阐释,仅仅列举与本问题相关的、与常规的Monokernel(Monolithic kernel)的几个不同:

  • Unikernel是没有内核态与用户态的区分,所有代码都运行在内核态中。
  • 用户的代码和内核代码会被打包成一个可执行文件。
  • Monokernel中的“系统调用”在Unikernel中是以“函数调用”的形态体现的,这个往往也是其运行高效的一个原因。
  • Unikernel是没有Process(进程)的概念的,或者说它有且仅有一个Process。
  • Unikernel是可以有多个Thread的。

笔者写下这个博客时,ArceOS的进度是希望直接加载动态链接的Linux应用,完成对Linux应用的支持,可以在不修改、不重新编译的情况下直接执行原生Linux应用。因此,石磊老师提出了两种思路。

2. 两种思路

49f3055a02621dea4523bb6eec597afa

第一种思路是通过在ArceOS内部创建中间层进行适配,第二种就是通过对动态库进行改造,对底层函数进行替换完成开发。

第一种shi x na

  • 第一种方式:通过在ArceOS内部创建中间层进行适配
    • 这意味着绝大多数的代码将在ArceOS内部进行开发,通常意味着需要在这个中间层中实现所有的musl兼容库的代码开发。
    • 这个不仅意味着ArceOS本身将会变得很大,不能实现库代码与系统代码的分离,同时也意味着我们在拓展更多版本的支持的时候需要直接修改代码本身。
    • 优势就是我们不需要从PLASH中同时加载Lib和App,其加载的速度要快一些。
  • 第二种思路:通过对musl库的考察,最小化替换其中仅能用于Linux而不是ArceOS的代码
    • 这个能够加速开发过程,并且简化测试过程,因为绝大多数代码都是使用的musl库的代码,仅仅xu yao dui

3. 思路二的支持现状和加载过程详解

3.1. 完整支持的头文件

  • [x] endian.h
  • [x] stdbool.h
  • [x] stdint.h
  • [ ] features.h(融合了src/include/features.h)
  • [x] ctype.h
  • [x] float.h
  • [x] limits.h
  • [x] stdarg.h
  • [x] stdbool.h
  • [ ] stdlib.h
  • [ ] time.h
  • [x] math.h
  • [ ] string.h
  • [x] values.h
  • [x] stdc_predef.h

3.2. 注意的问题

目前在某些函数部分使用了特殊的一个调用,在使用到不支持函数的时候会输出Not implement yet.并退出。这部分均是使用到float 128类型的。使用这个类型的时候,musl会去调用一个compile_rt库,并链接这里的函数。但是由于我使用-nostdlib参数去编译库,所以这部分的库不会被链接进去,我自己将这些函数实现了,具体的实现就是在内部调用一个NOIMPL并退出,作为占位符。

3.3. 加载过程详解

3.3.1. 加载通用前奏

在完成abi函数的注册和其他系统的初始化后,程序进入loader.rs中执行。在这里,将代码或代码与库函数一同从PLASH中移动到可执行区间。然后在对静态加载和动态加载分类处理后,将abi_table 设定为abi函数的映射表的地址,并作为a7参数传递,之后跳转到entry进入对应区域,离开Kernel区域。

3.3.2. 静态加载

静态加载的时候,musl标准下的__libc_start_main函数被我自己实现的同名函数替换。这个函数用于从a7寄存器中取出abi_table的地址,并完成在libc库中的设定。这样可以在mocklibc中调用这些函数。