Linux应用运行条件
Linux应用假定自己运行于Linux环境,它本身及依赖库及工具链都基于这一假定。需要明确的是,目前仅考虑riscv64架构。
此处说支持Linux应用,其实可以认为是,之前能够在Linux平台上运行的应用,现在能够不改变源码就在ArceOS操作系统内核之上正常运行。
针对于C语言来说,我们平常所编写的应用程序,都会通过include来包含一些C标准库的接口。C标准库接口可以认为只是一些.h文件,其具体实现根据库的不同会有所差异。目前C标准库的实现主要就是GLibc和Musl_libc,前者更通用,后者更轻量,适合嵌入式等场景。同时GLibc和Musl_libc也是工具链,且行为会随目标系统和架构变化,例如riscv64-linux-musl-gcc
编译工具,编译出的结果就是基于musl_libc的,目标操作系统为Linux,目标架构为riscv64,这样的应用,就是一种Linux应用。
那么针对C语言,层次关系为:用户C语言程序层—Libc层-OS层。如果要保持之前的C语言程序层不变,不改变用户源码就能运行,我们起码有两个选择:
- 在用户C语言程序层和Libc层之间改动,那么我们应当找到这两层之间的接口,即C标准库接口,然后基于现有内核(与Linux内核不完全相同),实现一个满足C标准库接口的库,这样用户源码不需要修改,但是仍需要重新基于这个库进行一次编译;
- 在Libc层和OS层之间改动,比如直接将基于riscv64-linux-musl-gcc编译的应用运行在该OS上,无需重新编译。Libc层和OS层的交互,分为硬件管理的trap和主动触发的系统调用,此处我们处理的主要是后者,这样编译的应用默认底层操作系统是Linux,架构为riscv,在系统调用时会遵循一系列规则(由musl_libc保证),如使用ecall指令、把系统调用号放在a7寄存器、参数放在a0等寄存器等,且各个系统调用号对应的功能也遵循riscv64_linux的规定。所以,如果要在这一层支持Linux应用,只需要底层内核也遵循这一套规则就行了,至于底层内核的实现究竟是什么样,其实也是无所谓的。
但是个人觉得,第二种更接近支持Linux应用(与高等程序代码无关)原本的意思,第一种更像是支持C语言应用(大部分与底层系统及架构无关),所以下面的解析也基于第二种方式。(仓库中ulibc文件夹下的axstd和axlibc,其实就像是第一种方式,前者支持rust应用,后者支持C应用)
支持Linux应用过程
加载Linux应用的ELF文件
ELF文件格式是最广泛使用的应用链接和执行的格式。riscv64-linux-musl-gcc工具链支持生成ELF文件,如下命令可以解析生成的ELF文件,具体字段含义可问GPT。
按照ELF文件信息,将LOAD段放到对应的虚拟地址,由于不同的应用是各自独立编译的,单地址空间必然很容易load冲突,还需要多地址空间来防止应用间的干扰。