跳转至

实验八:扩展实验

海阔凭鱼跃,天高任鸟飞。
 ——《古今贤文》

实验任务与要求

首先,祝贺你已经顺利完成了前面的实验,你已经拥有了一个基本的操作系统内核,它可以在 QEMU 中运行,并且可以通过串口输出进行交互。

它有自己的进程管理机制,可以从磁盘中加载用户程序进行执行;它有自己的同步互斥机制,可以让多个线程安全地访问共享资源;它有自己的内存管理机制,可以在内核态和用户态之间进行内存的分配和释放。

在这个实验中,你需要在这个基础上,自主实现一些更加有趣的功能,例如图形输出、文件系统、内存分配、块设备缓存层等。

请从下列任务中,选择一到两个目标进行实现


VGA 显示输出

串口输出只是最基本的交互方式,尝试在 bootloader 中,利用 get_handle_for_protocol::<GraphicsOutput> 获取到 VGA 显示的模式、分辨率、缓冲区地址等信息。

之后通过利用 embedded-graphics 等库,实现一个简单的图形输出驱动,赋予操作系统绘制图形的能力,之后实现一个简单的 Shell 显示,并激活键盘输入的驱动,通过和串口启动共享缓冲区的方式,响应键盘输入。

实现目标

  1. 在 bootloader 中,获取图形输出的相关信息,并传递给内核。
  2. 实现一个简单的图形输出驱动,支持基本的 VGA 图形绘制操作。
  3. 利用 embedded-graphics 实现字符渲染。
  4. 为 VGA 显示实现一个 Shell 输出,使其能够输出字符、清屏等,并将其对接到合适的日志输出。

加分项

  1. 利用键盘输入中断,为 QEMU 的 GUI 界面实现输入输出。
  2. 尝试将 Shell 部分限制在屏幕的下半部分,并在上半部分实现一些图像的绘制。作为一个例子,你可以利用 sleep 等方式,实现一个不断绘制转动的钟表的进程,并让它作为一个后台进程运行。

可读写的临时文件系统

根据现有的其他 OS 实验中的文件系统设计、或参考现有的文件系统设计,结合你学过的算法、组织数据的方式,实现一个简单的文件系统的驱动。

可以是目录结构文件系统(例如参考 FAT32),可以是日志文件系统(例如参考 ext4、NTFS)。实现之后利用帧分配器在操作系统初始化分配一段内存空间,将其作为块设备,使用你的文件系统对其进行格式化、挂载、读写等操作。为了更好的实现你可能需要适当增大启动 OS 的内存量。

实现目标

  1. 将启动目录挂载至 /boot 目录下。
  2. 将临时文件系统挂载至 /tmp 目录下。
  3. 创建 /tmp/mydir 目录。
  4. 创建 /tmp/mydir/hello.txt 文件。
  5. /tmp/mydir/hello.txt 文件写入一段字符序列,包含你的学号。
  6. 读取 /tmp/mydir/hello.txt 文件,将其中的字符序列打印到屏幕上。

加分项

  1. 尝试实现文件系统的硬链接,并测试读写操作。
  2. 修改 QEMU 参数,挂载一块虚拟磁盘,尝试实现文件的持久化。

内存管理算法

在上次实验实现了堆内存的分配和释放后,用户态有了自己的能力去管理自己的页面,但是其实际的动态内存分配还是依赖于内核提供的服务。

在这个实验目标中,你可以尝试实现一些内存管理算法,例如 Buddy 算法、Slab 算法等,将其作为内存分配器,为用户态提供更加灵活的内存分配服务。

不过,在这一目标中,你大概率需要一个链表,你可以找一些现成的实现,或者选择完全的自己实现。

实现目标

  1. pkg 下添加一个独立的新 package,用于实现能够测试的内存分配器。
  2. 参考 storage 包进行测试配置,并配置好 no_std 等属性。
  3. 在其中实现你的内存管理算法,参考 LockedHeap 的内存初始化方式,管理一段内存。
  4. lib 中为你的内存分配器声明一个 feature,并将它声明为 #[global_allocator]
  5. 编写测试程序,并测试你的内存分配器能否正确分配内存。

加分项

  1. 成功利用 brk 和你自己实现的内存分配器,实现用户态的内存管理。
  2. 尝试让你的内存分配器支持调用 brk 进行扩容。

将你的内存分配器作为 static 变量在线程间共享使用有助于避免线程导致的内存错误。


块设备的缓存层

storage 包中,基于目前已有的实现,实现一个块设备的缓存层。

这里给出一个缓存设备结构体的设计参考:

1
2
3
4
5
6
7
8
pub struct CachedDevice<B, C>
where
    B: BlockTrait,
    C: CacheManager<B>,
{
    cache: C,
    device: Arc<dyn BlockDevice<B>>,
}

其中,CacheManager trait 定义缓存层的缓存管理器,在预期的实现中,你并不需要在这里实现具体的缓存算法和数据结构,而将其留在 kernel 中实现。

实现目标

  1. 对设备缓存层、缓存管理器、缓存块进行抽象,并进行泛型设计。
  2. 缓存管理器提供缓存块的获取、存储能力。
  3. 缓存块实现脏数据标记、缓存块的读写,并为自身实现 Drop 时的自动写回。
  4. 为设备缓存层实现 BlockDevice<B> trait。
  5. 在内核中定义实际的缓存结构体,为它实现缓存管理器的相关功能。
  6. 在最后的实现中,你应当能够将缓存层作为 Fat16inner

加分项

  1. 向操作系统暴露缓存的使用情况,并在系统状态中进行展示。
  2. 验证缓存是否能带来一定的性能提升,设计相关测试并记录输出。

多核进程调度

在之前的实现中,进程只会在同一个核心上被调度执行,而在实际的多核系统中,多个核心可以并行地执行多个进程。

在这个目标中,你需要实现一个简单的多核调度器,使得内核能够在多个核心上调度多个进程。

实现不要求性能,结合能够找到的资料和相关理论知识,能够验证多核运行就是成功。

实现目标

  1. 修改 apic 初始化部分,利用 cpuid 使得它能够在多核系统中正确初始化多个 LAPIC。
  2. 初始化部分建议查阅文档,并参考其他现有的实现。
  3. 尝试实现操作系统的多核调度,并进行适当的日志显示。
  4. 尝试在用户态验证,并输出运行的核心序号、进程 ID 等。

加分项

  1. 尝试实现进程亲和性,使得进程能够在特定的核心上运行。
  2. 实现多核多队列模型,或尝试实现任一负载均衡算法(二选一)。