- 本章展现了操作系统的一个基本目标:让应用与硬件隔离,这种形式 简化了应用访问硬件的难度和复杂性。这也是远古操作系统雏形和现代的一些简单嵌入式操作系统的主要功能。具有这样功能的操作系统 形态就是一个函数库,可以被应用访问,并通过函数库的函数来访问硬件。
- 大多数程序员的第一行代码都从 Hello, world! 开始,当我们满怀着好奇心在编辑器内键入仅仅数个字节,再经过几行命令编译(靠的是编译器)、运行(靠的是操作系统),终于在黑洞洞的终端窗口中看到期望中的结果的时候,一扇通往编程世界的大门已经打开。生成应用程序二进制执行代码所依赖的是以 编译器 为主的开发环境 ;运行应用程序执行码所依赖的是以 操作系统 为主的执行环境 。
- 系统调用
系统调用(syscall) 是操作系统提供给软件的一系列接口,使得软件能够使用系统的功能。
syscall 本质上属于一种 异常/中断。 在 riscv 的汇编指令中 系统调用以 ecall 的形式出现 。
syscall 的种类有很多,操作系统通过区分 syscall 的 id 来判断是哪一个syscall 。
- 本章任务
本章我们将从操作系统最简单但也是最重要的println入手,要求大家实现一个裸机上的println以及带色彩的LOG,如info和warn,error等功能。本章的 println 所需要的在 console 中打印字符,也需要调用到 syscall系统调用。
【 1. 代码框架简述 】
.
├── bootloader
│ └── rustsbi-qemu.bin
├── LICENSE
├── Makefile
├── os
│ ├── console.c
│ ├── console.h
│ ├── defs.h
│ ├── entry.S
│ ├── kernel.ld
│ ├── log.h
│ ├── main.c
│ ├── printf.c
│ ├── printf.h
│ ├── riscv.h
│ ├── sbi.c
│ ├── sbi.h
│ └── types.h
├── README.md
1.1 OS 是怎么跑起来的?
1.1.1 qemu 的作用
- 我们的 OS 的运行,是要依赖著名的模拟器软件 qemu的。
比较形象的比喻是,OS 就是一个内核软件,qemu就类似一个主板,它模拟了许多硬件,比如CPU,I/O串口等等,OS 会和 qemu 模拟出来的这些硬件打交道,而 qemu 则把得到的指令分配给实际存在的硬件完成。
1.1.2 rustsbi.bin 的作用
- OS 启动的时候,就像一个真正的操作系统启动一样。qemu 使用我们提供的 rustsbi 的bin文件作为引导程序 来启动OS。
- SBI 是 RISC-V 的一种底层规范,RustSBI 是SBI的一种实现。 操作系统内核与 RustSBI 的关系有点像应用与操作系统内核的关系,后者(即RustSBI)向前者(即操作系统内核)提供一定的服务,只是SBI提供的服务很少, 比如关机,显示字符串,读入字符串等。
- SBI 的一个直接作用:提供输入输出。
我们的内核作为运行在qemu中的虚拟机,是无法直接和我们的外部 host系统通信的。因此我们OS自己实现的printf函数,想要真正地输出到我们外部运行的shell上被我们看到,是要经过qemu的。实际上,在 启动时sbi已经初始化好了,经过 qemu模拟出来的串口,最终打印到我们外部的shell上 的,从我们的shell之中读取输入,也是同样的道理。
SBI 为我们内核提供的功能不止于输入输出,在sbi.c文件的可以看到其他支持的功能,比如关机。
1.2 qemu 是怎么跑起来的?
- qemu 拓展阅读: qemu参数
- makefile 之中提供了具体运行qemu所需要的参数:

- make run这条指令首先执行上面 kernel 所需要的链接以及编译操作,得到一个二进制的 kernel,之后执行按照 QEMUOPTS 变量指定的参数启动qemu。
QEMUOPTS意义如下:
- nographic: 无图形界面。
- smp 1: 单核 (默认值,可以省略)。
- machine virt: 模拟硬件 RISC-V VirtIO Board。
- bios $(bios): 使用指定 bios,这里指向的是我们提供的 rustsbi 的bin文件。
- kernel: 使用 elf 格式的 kernel。这里就是我们需要写的OS内核了。
- makerun 指令完成了内核代码的编译生成kernel,并按照QEMUOPTS变量指定的参数加载我们的kernel,“加电”启动qemu。 此时,CPU 的其它通用寄存器清零,而 PC 会指向 0x1000 的位置,这里有固化在硬件中的一小段引导代码,它会很快跳转到 0x80000000 的 RustSBI 处。 RustSBI完成硬件初始化后,会跳转到 $(KERNEL_BIN) 所在内存位置 0x80200000 处, 执行我们操作系统的第一条指令。

- 那么,知道了这些步骤之后,关键就是怎么去写我们的OS了,这也是我们接下来各个实验的内容。
1.3 OS 文件夹
- os文件夹下存放了所有我们构建操作系统的源代码,是本次实验中最最重要的一部分,也是整个实验过程中唯一需要修改的部分。在开始实验之前,大家一定要清楚我们这是 自己设计的 OS,是无法使用C提供的官方标准库的 ,也就是说,就算是最简单的 printf 之类的函数都无法使用。还好,作为一个轻量级的 OS,我们也用不到那么多函数。
- 我们的os是一个由 makefile 来构建的C项目。下面介绍框架之中一些重要文件的作用,以及整个项目是如何链接及编译的。
1.3.1 kernel.ld