Yanyan's Wiki 软件工程研究入门 (2022)

AbstractMachine: 抽象计算机

AbstractMachine 是裸机上的 C 语言运行环境,提供 5 组 (15 个) 主要 API,可以实现各类系统软件 (如操作系统):

  • (TRM) putch/halt - 最基础的计算、显示和停机
  • (IOE) ioe_read/ioe_write - I/O 设备管理
  • (CTE) ienabled/iset/yield/kcontext - 中断和异常
  • (VME) protect/unprotect/map/ucontext - 虚存管理
  • (MPE) cpu_count/cpu_current/atomic_xchg - 多处理器

## 1. 简介 我们在 C 程序中调用 AbstractMachine API 就能实现操作系统,从而不需要知道底层硬件的中断、虚拟存储等是如何在指令级实现的——这是计算机系统设计与实现中的一种抽象;一个类似的例子是我们在应用程序里调用 libc 库就能完成绝大部分任务,而不需要知道底层的操作系统 API。这样的设计方法在复杂计算机系统的构造中十分常见:底层系统提供**机制** (mechanism),上层系统利用这些机制实现**策略** (policy)。典型的机制/策略分离的抽象层有指令集体系结构、系统调用、库函数等;AbstractMachine 属于一种硬件抽象层 (hardware abstraction layer, HAL)。 ## 2. 安装与配置 ### 2.1. 配置 AbstractMachine 对于操作系统实验的同学,`os-workbench` 中已经包含了 AbstractMachine 代码,并且操作系统实验的框架代码已经完成了配置。如果你使用 Debian/Ubuntu 系统,你在编译代码时可能会遇到一些命令执行失败的情况,通常需要以下软件包 (也可能需要其他软件包,请 STFW): * `gcc-multilib` (交叉编译用) * `libsdl2-dev` (图形库) * `qemu-system` (全系统模拟器) 如果你希望建立自己的 AbstractMachine 项目 (例如之后的 Hello World 例子) 或编译运行其他示例代码 (microbench, fceux, litenes, ...) 等,则需要先配置好 `AM_HOME` 环境变量 (请 STFW 什么是环境变量。可以用 `env` 命令查看当前的环境变量),把它设置为 AbstractMachine 目录的**绝对路径**:
export AM_HOME=/abs/path/.../os-workbench/abstract-machine
你可以把这一行写在 `.bashrc`/`.zshrc` 中,使得不必每次打开终端都重新配置。如果你第一次使用 Linux,这会是一个比较痛苦的过程——你还需要学习 Shell 有关的基础知识。一份不错的入门材料是 MIT 的 “[The Missing Semester of Your CS Education](https://missing.csail.mit.edu/)”。 ## 2.2. Hello, AbstractMachine 完成必要软件的安装和正确的 `AM_HOME` 配置后,我们就可以在 AbstractMachine 上编程了。在文件系统的任意目录中创建一个 `.c` 文件和 `Makefile` (参考 `man 3 stdarg`):
// hello.c
#include <am.h>
#include <stdarg.h>

void print(const char *s, ...) {
  va_list ap;
  va_start(ap, s);
  while (s) {
    for (; *s; s ++) putch(*s);
    s = va_arg(ap, const char *);
  }
  va_end(ap);
}

int main(const char *args) {
  print("\"", args, "\"", " from " __ISA__ " program!\n", NULL);
  return 0;
}
# Makefile
NAME := hello
SRCS := hello.c
include $(AM_HOME)/Makefile.app
我们就完成了一个**可移植**到多个 “bare-metal” 平台的 Hello World 程序,它们可以直接在模拟器里运行,甚至直接在开发板上运行!当然了,现在编译、链接、运行都会交给 AbstractMachine 中的代码帮忙搞定。 为了编译运行,AbstractMachine 需要知道目标的平台/体系结构,通过 `ARCH` 环境变量指定。例如,我们希望编译出能在 x86-64 (QEMU) 下运行的镜像:
$ make ARCH=x86_64-qemu
# Building hello-image [x86_64-qemu]
+ CC hello.c
...
+ CREATE -> build/hello-x86_64-qemu
会自动完成编译,得到 `build/` 目录下的若干文件:
build
├── hello-x86_64-qemu    // 可运行、包含 bootloader 等的磁盘镜像
├── hello-x86_64-qemu.o  // hello 项目的二进制文件
└── x86_64-qemu
    ├── hello.d          // hello.c 依赖的头文件 (gcc -MMD 生成)
    └── hello.o          // 编译 hello.c 得到的目标文件
不妨用 `objdump -d` 命令查看 `hello-x86_64-qemu.o` 反汇编后的代码,是直接运行在裸机上的 C 程序代码,程序的入口是 `_start`。Makefile 也自带了运行功能,传入 `mainargs` 环境变量,即可将参数传递给 `main` 函数:
$ make run ARCH=x86_64-qemu mainargs="Hello World"
...
"Hello World" from x86_64 program!
CPU #0 Halt (00).
你可以尝试其他的 `ARCH` 环境变量:`x86-qemu`, `native`,在不同平台下运行。你也可以 export 默认的 `ARCH=x86_64-qemu`,避免每次键入。 > #### 思考题:`__ISA__` > > 我们的 Hello World 程序引用了这个奇怪的宏。这个宏不是 C 标准里定义的,那么是谁定义的?当我们设置不同的 `ARCH`,打印的 `__ISA__` 也各不相同。这一定是 Makefile 做的。怎么知道呢?UNIX 世界早就给我们提供了足够的工具来搞定这些日常琐事: > > :::text > find . -type f | xargs grep -ne __ISA__ > > (请 RTFM 理解这个命令的含义)。我们能定位到 `Makefile` 中的一行代码,将 `__ISA__` 的定义使用 gcc 的 `-D` 选项加入了 `CFLAGS`。
Creative Commons License    苏 ICP 备 2020049101 号