Assignment 3 的主要目标,是使自己的操作系统能够运行用户态程序。这个 Assignment 中,需要实现特权级别切换、上下文切换、调度器、系统调用处理、虚拟内存等代码。并将之前实现的 shell 移动至用户态,做为一个进程来运行。
文章目录:
- Stanford CS140e 学习笔记 (1):Rust 基础、LED 闪烁
- Stanford CS140e 学习笔记 (2):驱动、bootloader、shell
- Stanford CS140e 学习笔记 (3):文件系统
- Stanford CS140e 学习笔记 (4):用户态程序的运行
ARM 架构基础知识学习
Assignment 3 所实现的不少功能,都需要操作系统软件和 CPU 的配合。所以,在正式实验之前,需要了解 ARM 架构的基础知识。主要参考如下文档:
为了实现用户态和内核态之间的切换,首先需要关注异常和特权级别。原网页已经有较为详细的介绍。同时我又找了这样几篇文章用于加深理解。
同时也进行了指令集、寻址方式的简单学习。并阅读了 init.S
初始化程序。
异常与中断处理
切换到 EL1
AArch64 CPU 启动时,EL 级别位于 EL2. 正常情况下,OS 内核工作于 EL1,用户态程序工作于 EL0。所以需要在内核初始化时,将 EL 级别由 EL2 切换到 EL1.
异常向量
为了使内核能够处理终端和异常,需要设置异常向量。ARM 的向量表中有 16 个异常向量,每条向量可存放最多 16 条指令。为了更方便地处理异常,每条异常向量,都将调用 handle_exception
函数,并设置不同的参数,最终由 handle_exception
函数统一处理。
通过汇编代码调用 handle_exception
时,需要考虑传入参数,获取返回值。对于 AArch64,前 8 个参数依次传入到 r0
..r7
,函数调用完毕后,前 8 个返回值也从 r0
..r7
中获取。另外,在调用函数前后,还需要保存和恢复各个寄存器中的值,这些寄存器分为 caller-saved 和 callee-saved,对于前者,如果 caller 需要在函数执行调用后继续使用寄存器中的内容,则需要主动将寄存器中的值保存在栈上,否则不需要保存;对于后者,由 callee 保证寄存器的值在函数调用后不发生变化。
在异常处理中,实现了一个 debug shell,方便后续进行调试。
上下文切换
进入异常后,寄存器的状态可能会被异常处理程序改变,导致从异常返回后,程序无法继续执行。所以进入异常、以及从异常返回时,需要分别保存、恢复各个寄存器的状态。这一过程称为「上下文切换」。
第一个进程
对于内核,需要为每个进程维护如下信息:
- 栈
- 堆
- 可执行代码
- 虚拟地址空间
- 调度器状态(进程是否需要被得到调度)
- 运行状态(寄存器等信息,方便上下文切换后进行恢复)
所以在代码中,通过定义一个名为 Process
的结构体,保存进程运行所需的信息。
然后将之前实现的 shell,修改为一个用户态 (EL0
) 进程。由于未实现虚拟内存,这个进程除了运行在 EL0
之外,更像是一个内核线程。对于虚拟内存的部分,留在后面的课程中实现。
调度器
定时器中断
在 AArch64 上,中断其实和异常基本类似,知识中断往往由外部事件产生。我们需要做的,是修改之前完成的定时器驱动代码,设置 BCM2837 上的定时器,同时实现中断控制器驱动,使定时器能够正常产生中断。
定时器驱动代码的修改,和之前玩单片机时使用的方式类似。通过设置一个时间比较寄存器,当定时器中的时间与该寄存器的时间相等,则产生一个中断。通过这种方式,周期性地产生中断,做为内核的时钟使用。
中断控制器也需要经过设置,才能将定时器产生的中断送至 CPU。
round-robin 抢占式调度器
之前在单片机上实现过一个简单的非抢占式的调度器,这门课程中,需要实现另外一个。
调度器通过前面实现的定时器中断,来确定接下来要运行哪个进程。用于调度的算法有很多种,本课程中,主要实现简单的 round-robin 调度算法,为每个进程分配相同的时间片。
在这个操作系统中,调度器为进程有三种状态:Ready, Running, Waiting. 所有进程放在一个队列中,当前进程的时间片用尽,则将进程放入队列末尾,并取出一个新的进程。如果新的进程状态为 Ready, 则继续执行,如果为 Waiting,则直接放入队列末尾,并再取一个新的进程执行。
系统调用
接下来要实现第一个系统调用 sleep
,告诉调度器制定时间内不对本进程进行调度。同时通过在 shell 中实现 sleep
命令,验证该系统调用是否能正常工作。
系统调用是一种特殊的异常,是用户态进程请求内核服务的一种方式。在 AArch64 中,通过 svc
指令来执行系统调用。
类似函数调用,在系统调用中,也需要传入参数,获取返回值。本课程中实现的系统调用中,将寄存器 x0
..x6
用做传入参数与返回值,将 x7
用做错误码。
更多
课程的剩余内容,CS140e 的网站上已经几个月没有更新了。这门课程的学习暂时告一段落,等课程更新后再继续。
留言