北科大操作系统Lab5
git checkout master
解释一下lab5的代码原理
实验五的代码其实相比于实验四好一点,依旧先找要写的文件路径/kernel/src/task/process.rs.
sys_waitid
要写的函数在文档里面写的很清楚流程,这就是根据伪代码来改编一下。
A. 判定是否有匹配子进程
无 -> return -1
B. 判定匹配子进程是否有 Zombie
无 -> return -2
C. 回收 Zombie 并写回退出码
remove(child) -> *translated_refmut(...) = exit_code -> return child_pid
判断是否有子进程
首先有一个叫current_task()的函数,能返回当前正在运行的任务进程,看一眼他的定义,是调用了进程PORCESSOR的各种操作
pub fn current_task() -> Option<Arc<TaskControlBlock>> {
PROCESSOR.exclusive_access().current()
}
再往上翻会发现processor的定义
pub struct Processor {
///The task currently executing on the current processor
current: Option<Arc<TaskControlBlock>>,
///The basic control flow of each core, helping to select and switch process
idle_task_cx: TaskContext,
}
- current:存放当前进程的TCB
- idle_task_cx: 存放进程上下文
这里看一眼上下文定义,即使一堆寄存器和栈指针。(TCB在后面)
pub struct TaskContext {
/// Ret position after task switching
ra: usize,
/// Stack pointer
sp: usize,
/// s0-11 register, callee saved
s: [usize; 12],
}
看了一圈发现就那样,current_task()就会返回当前进程,也不在意里面怎么实现了。
let current = current_task().unwrap();
接下来呢就得找内部子进程了,这东西可让我一顿好找啊。首先我们得到是一个TCB,里面和前面实验一样,都会有Inner的一个小类封装一手。这里的代码很多,我删掉了一部分,把用到的留下
pub struct TaskControlBlock {
inner: UPSafeCell<TaskControlBlockInner>,
}
impl TaskControlBlock {
//进入内部信息
pub fn inner_exclusive_access(&self) -> RefMut<'_, TaskControlBlockInner> {
self.inner.exclusive_access()
}
}
pub struct TaskControlBlockInner {
//子进程
pub children: Vec<Arc<TaskControlBlock>>,
// 退出码
pub exit_code: i32,
// 进程优先级
pub priority: usize,
/// stride of the process
pub stride: Stride,
}
impl TaskControlBlockInner {
//判断是否是僵尸进程
pub fn is_zombie(&self) -> bool {
self.get_status() == TaskStatus::Zombie
}
我们会发现进入inner调用inner_exclusive_access()即可,可以访问TCB内部的一些信息,TCB内部呢,就会有一个children的数组,存着自己的子进程。那接下来一顿操作,从头到尾遍历会吧,当pid == -1(任意进程都能等待也就是任意进程都是子进程)或者pid相同的时候(子进程也是一个tcb有getpid操作)
let mut inner = current.inner_exclusive_access();
let mut flag = false;
for child in inner.children.iter() {
if pid == -1 || child.getpid() == pid as usize {
flag = true;
break;
}
}
if !flag {
return -1;
}
寻找僵尸进程
看到这里,依旧遍历大法,从头到尾遍历,然后用那个is_zombie()函数
let mut idx = 0;
let mut found = false;
for (i, child) in inner.children.iter().enumerate() {
if (pid == -1 || child.getpid() == pid as usize)
&& child.inner_exclusive_access().is_zombie()
{
idx = i;
found = true;
break;
}
}
如果找到了呢?就返回僵尸进程的pid,也就是我记录的那个idx的位置的进程,然后退出码要返回。
if found {
let tmp = inner.children.remove(idx);
let code = tmp.inner_exclusive_access().exit_code;
*translated_refmut(token, exit_code_ptr) = code;
return tmp.getpid() as isize;
}
这里唯一要解释的就是
*translated_refmut(token, exit_code_ptr) = code;
其实就是根据父进程的token找到页表,然后就能看到退出码的物理地址进行修改。
ai给的解释:
根据当前进程的页表 token,把一个“用户态虚拟地址” exit_code_ptr 翻译成内核可以访问的可变引用。
完整代码:
pub fn sys_waitpid(pid: isize, exit_code_ptr: *mut i32) -> isize {
trace!("kernel::pid[{}] sys_waitpid [{}]", current_task().unwrap().pid.0, pid);
let current = current_task().unwrap();
let mut inner = current.inner_exclusive_access();
let token = inner.get_user_token();
let mut flag = false;
for child in inner.children.iter() {
if pid == -1 || child.getpid() == pid as usize {
flag = true;
break;
}
}
if !flag {
return -1;
}
let mut idx = 0;
let mut found = false;
for (i, child) in inner.children.iter().enumerate() {
if (pid == -1 || child.getpid() == pid as usize)
&& child.inner_exclusive_access().is_zombie()
{
idx = i;
found = true;
break;
}
}
if found {
let tmp = inner.children.remove(idx);
let code = tmp.inner_exclusive_access().exit_code;
*translated_refmut(token, exit_code_ptr) = code;
return tmp.getpid() as isize;
}
-2
}
stride 调度算法
这个调度算法的本质就是要从ready_queue寻找有最小stride的进程,然后需要根据这个进程的优先级去提升其stride.
我觉得如果有C++编程思想的都能写出来这段遍历的代码,只是可能不知道怎么来获取各种信息。
// 防止就绪队列为空
if self.ready_queue.is_empty() {
return None ;
}
// 记录答案(最小stride)的位置
let mut ans = 0 ;
// 记录最小stride是多少
let mut minstride = self.ready_queue[0].inner_exclusive_access().stride ;
for (idx , tcb) in self.ready_queue.iter().enumerate() {
let cur = tcb.inner_exclusive_access().stride ;
// 比较一下stride
if cur < minstride {
ans = idx ;
minstride = cur ;
}
}
return self.ready_queue.remove(ans) ;
但是由于stride不是基本类型,你需要去学习一下如何重载运算符,这里可以去学习一下rust的重载语法
impl PartialOrd for Stride {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.0.cmp(&other.0))
}
}
impl PartialEq for Stride {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
最后就是完善一下,根据调度算法我们知道:不仅要移除最小的stride对应的进程,还需要将其step,所以在return前需要补上一个step,根据优先级提升stride的步骤.
let mut inner = self.ready_queue[ans].inner_exclusive_access();
let pr = inner.priority;
inner.stride.step(pr);
Stride的类型其实也很好解读,这里传入的参数可以自行理解。
pub struct Stride(StrideInner);
impl Stride {
const BIG_STRIDE: StrideInner = StrideInner::MAX / 10000;
pub fn step(&mut self, prio: usize) {
let pass = Stride::BIG_STRIDE / prio;
self.0 += pass;
}
}
验收问题:
- *translated_refmut(token, exit_code_ptr) = code;的作用是什么?
- 代码中CPU是怎么进行进程管理的?
- 进程的切换是怎么进行的?
- 进程处理的这个trap机制在哪个文件中管理?
这几个问题的强度还是很高的,和Lab4相比感觉这种属于通读代码的。

浙公网安备 33010602011771号