0%

2023开源操作系统训练营一二阶段总结-zhang722

2023开源操作系统训练营一二阶段总结

Lab1

Lab1要实现获取任务的信息,包括任务使用的系统调用及调用次数、系统调用时刻距离任务第一次被调度时刻的时长,实验内容比较简单,但是本章的知识点却是后几章的基础,需要深刻理解。因此关于Lab1的总结主要分为两部分:

  1. 操作系统特权级切换及任务切换
  2. 实验完成思路

    特权级切换及任务切换

    特权切换

    对于rCore,特权切换发生在系统调用和中断前后,如下图所示:

要实现特权级的切换,需要软硬件协同工作,主要包括两部分:

  1. Trap上下文的保存与恢复
  2. CSR寄存器的设置
    对于Trap上下文的保存与恢复,主要有以下几步:
  3. 系统初始化的时候,修改 stvec 寄存器来指向正确的 Trap 入口
  4. 发生trap时,进入入口函数__alltraps,它做了以下几件事:
    1. 将栈指针指向内核栈
    2. 在内核栈保存通用寄存器和CSR寄存器
    3. 调用trap_handler
  5. 调用完成后,调用__restore,它做以下几件事:
    1. 内核栈顶的 Trap 上下文恢复通用寄存器和 CSR
    2. 将栈指针指向用户栈
    3. sret返回用户态

而任务切换不涉及特权级切换,是在进入到Trap之后,进一步进行的,主要有以下几步:

  1. A 在 A 任务上下文空间在里面保存 CPU 当前的寄存器快照
  2. B 任务上下文,根据 B 任务上下文保存的内容来恢复 ra 寄存器、s0~s11 寄存器以及 sp 寄存器

实验思路

我们需要实现系统调用:

1
fn sys_task_info(ti: *mut TaskInfo) -> isize

其中:

1
2
3
4
5
struct TaskInfo {
status: TaskStatus,
syscall_times: [u32; MAX_SYSCALL_NUM],
time: usize
}

我们需要获得syscall_times和time。

  1. syscall_times: 需要在任务控制块中加入用以记录系统调用及其次数的字段,并且暴露出一个可以更新这个字段的方法,然后在syscall函数里面,通过调用更新方法,维护调用次数。
  2. time:需要在任务控制块中加入用以记录时间的字段,每次调用某任务前,首先查看是不是第一次调用该任务,若是,则记录当前时间。

Lab2

目前的操纵系统存在两个问题:

  1. 用户程序可以通过物理内存修改其他用户程序的代码数据甚至内核的代码数据,这是非常不安全的。
  2. 此外,由于用户程序的加载强依赖于物理地址,因此我们在编写用户程序的时候就需要考虑程序在物理内存中的布局以防止和其他用户程序和内核程序之前的重叠,这给用户程序编写者带来了极大的不便。
    为了解决这两个问题,引入虚拟内存。对于虚拟内存如何翻译到物理内存,这部分比较简单,略过。对于OS代码中如何建立地址空间,着重讲一下,因此关于Lab2的总结主要分为两部分:
  3. 地址空间
  4. 实验完成思路

    地址空间

    如图为应用地址空间

每一段我们抽象成一个MapArea,整个地址空间我们抽象成一个MapArea的数组。因此,建立地址空间就是建立一个个MapArea,然后push到数组里面。在这一章里,有一个很重要的细节点,就是RAII的思想,可以说是贯穿了操作系统内存管理的始终。

实验完成思路

我们需要实现系统调用:

1
fn sys_mmap(start: usize, len: usize, port: usize) -> isize
  1. 首先需要检查错误
  2. 因为memory_set是暴露出来的,我们可以直接访问memory_set里面的相关方法进行一段连续内存的映射

对于取消申请的系统调用,思想同上。

1
fn sys_munmap(start: usize, len: usize) -> isize

Lab3

这一章的重点是如何新建一个进程

  1. 进程生成机制:这主要是指 fork/exec 两个系统调用。
  2. 进程资源回收机制:当一个进程主动退出或出错退出的时候,在 exit_current_and_run_next 中会立即回收一部分资源并在进程控制块中保存退出码;而需要等到它的父进程通过 waitpid 系统调用(与 fork/exec 两个系统调用放在相同位置)捕获到它的退出码之后,它的进程控制块才会被回收,从而该进程的所有资源都被回收。
    关于Lab3的总结主要分为两部分:
  3. 进程生成
  4. 实验完成思路

进程生成

在内核中手动生成的进程只有初始进程 initproc ,余下所有的进程都是它直接或间接 fork 出来的。当一个子进程被 fork 出来之后,它可以调用 exec 系统调用来加载并执行另一个可执行文件。因此, fork/exec 两个系统调用提供了进程的生成机制。

fork

fork主要做了以下几件事:

  1. 复制父进程地址空间
  2. 得到trap上下文物理地址页号
  3. 分配pid及内核栈
  4. 创建进程控制块
  5. 添加子进程至父进程进行管理
  6. 修改trap上下文中的kernel_sp使其指向新建的内核栈

exec

exec主要做了以下几件事:

  1. 从elf新建一个地址空间
  2. 得到trap上下文物理地址页号
  3. 更新地址空间
  4. 更新trap上下文物理地址页号
  5. 更新entry_point, user_sp

实验思路

实现系统调用

1
fn sys_spawn(path: *const u8) -> isize

结合exec和fork方法,糅合即可。

实现系统调用

1
2
3
4
5
// syscall ID:140
// 设置当前进程优先级为 prio
// 参数:prio 进程优先级,要求 prio >= 2
// 返回值:如果输入合法则返回 prio,否则返回 -1
fn sys_set_priority(prio: isize) -> isize;

只需在任务控制块里添加prio、pass、stride字段,将TaskManager里的VecQueue换成BinaryHeap,然后自定义任务控制块的比较方法,使其按照stride进行比较。然后自定义stride的比较方法,以处理溢出问题即可。

总结

学习rCore使我认识到,不论是rust还是操纵系统,我都还有很长的学习之路要走;写总结报告使我认识到,将一个论点清晰地展示给别人,是一件难度不亚于习得该论点的事情。如何高效学习,再如何简洁明了地教给别人,值得我长时间的思考。