北科大操作系统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;
    }
}

验收问题:

  1. *translated_refmut(token, exit_code_ptr) = code;的作用是什么?
  2. 代码中CPU是怎么进行进程管理的?
  3. 进程的切换是怎么进行的?
  4. 进程处理的这个trap机制在哪个文件中管理?
    这几个问题的强度还是很高的,和Lab4相比感觉这种属于通读代码的。
posted @ 2026-04-28 16:08  莫枭  阅读(59)  评论(0)    收藏  举报