rCore-lab1

chapter3 编程作业

任务

获取任务信息

ch3 中,我们的系统已经能够支持多个任务分时轮流运行,我们希望引入一个新的系统调用 sys_task_info 以获取当前任务的信息,定义如下:

fn sys_task_info(ti: *mut TaskInfo) -> isize
  • syscall ID: 410
  • 查询当前正在执行的任务信息,任务信息包括任务控制块相关信息(任务状态)、任务使用的系统调用及调用次数、系统调用时刻距离任务第一次被调度时刻的时长(单位ms)。
struct TaskInfo {
status: TaskStatus,
syscall_times: [u32; MAX_SYSCALL_NUM],
time: usize
}
  • 参数:

    • ti: 待查询任务信息
  • 返回值:执行成功返回0,错误返回-1

  • 说明:

    • 相关结构已在框架中给出,只需添加逻辑实现功能需求即可。
    • 在我们的实验中,系统调用号一定小于 500,所以直接使用一个长为 MAX_SYSCALL_NUM=500 的数组做桶计数。
    • 运行时间 time 返回系统调用时刻距离任务第一次被调度时刻的时长,也就是说这个时长可能包含该任务被其他任务抢占后的等待重新调度的时间。
    • 由于查询的是当前任务的状态,因此 TaskStatus 一定是 Running。(助教起初想设计根据任务 id 查询,但是既不好定义任务 id 也不好写测例,遂放弃 QAQ)
    • 调用 sys_task_info 也会对本次调用计数。
  • 提示:

    • 大胆修改已有框架!除了配置文件,你几乎可以随意修改已有框架的内容。
    • 程序运行时间可以通过调用 get_time() 获取,注意任务运行总时长的单位是 ms。
    • 系统调用次数可以考虑在进入内核态系统调用异常处理函数之后,进入具体系统调用函数之前维护。
    • 阅读 TaskManager 的实现,思考如何维护内核控制块信息(可以在控制块可变部分加入需要的信息)。
    • 虽然系统调用接口采用桶计数,但是内核采用相同的方法进行维护会遇到什么问题?是不是可以用其他结构计数?

解决思路

tutorial中给出的提示非常重要,仔细阅读提示,对完成实验的帮助非常大!

流程分析

首先对sys_task_info这个系统调用的应用场景和触发条件进行分析。

在测试文件中可以看到以下代码

// user/src/bin/ch3_taskinfo.rs

let t1 = get_time() as usize;
let mut info = TaskInfo::new();
get_time();
sleep(500);
let t2 = get_time() as usize;
// 注意本次 task info 调用也计入
assert_eq!(0, task_info(&mut info));
let t3 = get_time() as usize;

对这里的get_timetask_info两个函数进行源码追踪

// user/src/lib.rs

pub fn get_time() -> isize {
let mut time = TimeVal::new();
match sys_get_time(&mut time, 0) {
0 => ((time.sec & 0xffff) * 1000 + time.usec / 1000) as isize,
_ => -1,
}
}

pub fn task_info(info: &mut TaskInfo) -> isize {
sys_task_info(info)
}

// user/src/syscall.rs
pub fn sys_task_info(info: &mut TaskInfo) -> isize {
syscall(SYSCALL_TASK_INFO, [info as *const _ as usize, 0, 0])
}

pub fn sys_get_time(time: &mut TimeVal, tz: usize) -> isize {
syscall(SYSCALL_GETTIMEOFDAY, [time as *const _ as usize, tz, 0])
}

pub fn syscall(id: usize, args: [usize; 3]) -> isize {
let mut ret: isize;
unsafe {
core::arch::asm!(
"ecall",
inlateout("x10") args[0] => ret,
in("x11") args[1],
in("x12") args[2],
in("x17") id
);
}
ret
}

可以看到,这两个函数都调用了syscall.rs里的函数,利用系统调用来完成任务。

在第二章的文档中有如下的一段话

在子模块 syscall 中我们来通过 ecall 调用批处理系统提供的接口, 由于应用程序运行在用户态(即 U 模式), ecall 指令会触发名为 Environment call from U-mode 的异常, 并 Trap 进入 S 模式执行批处理系统针对这个异常特别提供的服务程序。 这个接口被称为 ABI 或者系统调用。

进入Trap后会被trap_handler捕获,然后调用内核中的syscall根据syscall_id调用相应的函数。

总结

user级的应用调用task_info -> user级的sys_task_info -> 调用系统调用syscall -> ecall指令触发中断异常,Trap进入S模式,trap被trap_handler捕获 -> 根据syscall_id调用处理函数

代码分析

系统调用次数考虑在进入内核态系统调用异常处理函数之后,进入具体系统调用函数之前维护。

一开始的想法是在os/src/trap/mod.rstrap_handler函数中,利用一个静态全局变量来维护。实操了一下,笔者初学Rust, 大概不到二十小时,包的引用和静态全局变量这方面的维护也不太会处理。遂放弃,后来发现其实在TaskManager中已经有了很好的处理方式,也有一个静态全局变量,那就利用已有代码,在结构体中添加一些成员变量,对应sys_taskinfo中所需的信息。

syscall_times

在syscall函数里,进入具体系统维护函数之前统计对应syscall_id的调用次数。在TaskControlBlock中维护即可

// os/src/syscall/process.rs
/// YOUR JOB: Finish sys_task_info to pass testcases
pub fn sys_task_info(_ti: *mut TaskInfo) -> isize {
trace!("kernel: sys_task_info");
if _ti.is_null() {
return -1;
}
unsafe {
*_ti = TaskInfo {
status: get_task_status(),
syscall_times: get_syscall_times(),
time: get_run_time()
}
}
0
}
// os/src/syscall/mod.rs
use crate::task::count_syscall;
/// handle syscall exception with `syscall_id` and other arguments
pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize {
count_syscall(syscall_id);
match syscall_id {
...
}
}
// os/src/task/mod.rs
impl TaskManager {
/// When a syscall is called, we need to increase the syscall_times
fn count_syscall(&self, syscall_id: usize) {
if syscall_id < MAX_SYSCALL_NUM {
let mut inner = self.inner.exclusive_access();
let current = inner.current_task;
inner.tasks[current].syscall_times[syscall_id] += 1;
}
}
}
// os/src/task/task.rs
/// The task control block (TCB) of a task.
#[derive(Copy, Clone)]
pub struct TaskControlBlock {
/// The task status in it's lifecycle
pub task_status: TaskStatus,
/// The task context
pub task_cx: TaskContext,
/// The task syscall times
pub syscall_times: [u32; MAX_SYSCALL_NUM],
}

task_status

虽然当前task的状态一定是Running,但是要借助代码获取,而不是用硬编码固定状态。思路就和统计syscall的调用次数一样,只需要在TaskManager中维护一个get_task_status函数,获取当前任务的状态即可

run_time

在系统中只有获取当前时间的方法,要计算程序运行时间,应该用当前时间减去程序开始时间,那么在TaskControlBlock里面维护一个start_time就可以了。

/// The task control block (TCB) of a task.
#[derive(Copy, Clone)]
pub struct TaskControlBlock {
/// The task status in it's lifecycle
pub task_status: TaskStatus,
/// The task context
pub task_cx: TaskContext,
/// The task syscall times
pub syscall_times: [u32; MAX_SYSCALL_NUM],
/// The start time of task
pub start_time: Option<usize>
}

start_time在每个程序运行之前记录一下

impl TaskManager {
fn run_first_task(&self) -> ! {
...
let next_task_cx_ptr = &task0.task_cx as *const TaskContext;
task0.start_time = Some(get_time_ms());
drop(inner);
...
}

/// Switch current `Running` task to the task we have found,
/// or there is no `Ready` task and we can exit with all applications completed
fn run_next_task(&self) {
if let Some(next) = self.find_next_task() {
...
let next_task_cx_ptr = &inner.tasks[next].task_cx as *const TaskContext;
if inner.tasks[next].start_time.is_none() {
inner.tasks[next].start_time = Some(get_time_ms());
}
drop(inner);
...
}
}

/// Get the task run time
fn get_run_time(&self) -> usize {
let inner = self.inner.exclusive_access();
let current = inner.current_task;
let current_time = get_time_ms();
if let Some(start_time) = inner.tasks[current].start_time {
return current_time - start_time;
} else {
return 0;
}
}
}