Post

rCore - Chapter02

rCore - Chapter02

课后练习

作业1

见练习答案


作业2

扩展内核,实现新系统调用get_taskinfo,能显示当前task的id和task name;实现一个裸机应用程序B,能访问get_taskinfo系统调用

先实现sys_get_taskinfo函数:

1
2
3
4
5
pub fn sys_get_taskinfo() -> isize {
    println!("[kernel] get_taskinfo");
    print_current_app();
    0 // always return 0 to indicate success
}

batch.rs中添加函数支持打印任务编号:

1
2
3
4
5
6
7
8
9
10
11
12
impl AppManager {
  	// ...
    pub fn print_current_app(&self) {
        println!("[kernel] current_app = {}", self.current_app - 1);
    }
  	// ...
}

/// print current app info
pub fn print_current_app() {
    APP_MANAGER.exclusive_access().print_current_app();
}

其余模仿其他系统调用即可,系统调用id我选取的是10001,因为这是我们自己定义的系统调用,不能和标准有冲突。

接下来写一个用户程序即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#![no_std]
#![no_main]

#[macro_use]
extern crate user_lib;

use user_lib::get_taskinfo;

#[no_mangle]
fn main() -> i32 {
    println!("Try to get task info in U Mode");
    get_taskinfo();
    0
}


作业3

扩展内核,能够统计多个应用的执行过程中系统调用编号和访问此系统调用的次数

主要模仿了AppManager,在os/src/syscall/mod.rs中记录调用次数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
use crate::sync::UPSafeCell;
use lazy_static::*;

const SYSCALL_WRITE: usize = 64;
const SYSCALL_EXIT: usize = 93;

// Here are self-defined syscalls, syscall id starts from 10001.
const SYSCALL_GET_TASKINFO: usize = 10001;

mod fs;
mod process;

use fs::*;
use process::*;

// ===== Syscall Statistics =====

const MAX_TRACKED_SYSCALLS: usize = 128;

#[derive(Debug, Copy, Clone)]
struct SyscallStat {
    pub syscall_id: usize,
    pub count: usize,
}

struct SyscallStatManager {
    stats: [SyscallStat; MAX_TRACKED_SYSCALLS],
    len: usize,
}

impl SyscallStatManager {
    pub const fn new() -> Self {
        const INIT: SyscallStat = SyscallStat {
            syscall_id: 0,
            count: 0,
        };
        Self {
            stats: [INIT; MAX_TRACKED_SYSCALLS],
            len: 0,
        }
    }

    pub fn record(&mut self, syscall_id: usize) {
        // If syscall_id is already recorded, increase count
        for i in 0..self.len {
            if self.stats[i].syscall_id == syscall_id {
                self.stats[i].count += 1;
                return;
            }
        }

        // Otherwise, record a new syscall_id
        if self.len < MAX_TRACKED_SYSCALLS {
            self.stats[self.len] = SyscallStat {
                syscall_id,
                count: 1,
            };
            self.len += 1;
        } else {
            // If too many syscalls are recorded, panic
            panic!("SyscallStatManager: too many different syscalls recorded!");
        }
    }

    pub fn print_stats(&self) {
        println!("[kernel] Syscall statistics:");
        for i in 0..self.len {
            println!(
                "[kernel] syscall_id: {}, count: {}",
                self.stats[i].syscall_id, self.stats[i].count
            );
        }
    }
}

lazy_static! {
    static ref SYSCALL_STAT_MANAGER: UPSafeCell<SyscallStatManager> =
        unsafe { UPSafeCell::new(SyscallStatManager::new()) };
}

/// print syscall statistics
pub fn print_syscall_stats() {
    SYSCALL_STAT_MANAGER.exclusive_access().print_stats();
}

/// handle syscall exception with `syscall_id` and other arguments
pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize {
    SYSCALL_STAT_MANAGER.exclusive_access().record(syscall_id);
    match syscall_id {
        SYSCALL_WRITE => sys_write(args[0], args[1] as *const u8, args[2]),
        SYSCALL_EXIT => sys_exit(args[0] as i32),
        SYSCALL_GET_TASKINFO => sys_get_taskinfo(),
        _ => panic!("Unsupported syscall_id: {}", syscall_id),
    }
}

作业4

扩展内核,能够统计每个应用执行后的完成时间

首先我们需要一个timer模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//! Timer module
//!
//! This module provides the functionality to set a timer to trigger an interrupt, including:
//! - `get_time()`: get the current time from the timer
//! - `sleep(duration)`: sleep for a certain duration
//!
//! The timer is based on CLINT (Core Local Interruptor), which is a memory-mapped device.
//! Suppose that the frequency of timer is 10,000,000 Hz.

const CLINT_BASE: usize = 0x200_0000; // the address of CLINT
const MTIME_OFFSET: usize = 0xBFF8; // the offset of mtime register
pub const TIMER_FREQUENCY: u64 = 10_000_000; // the frequency of timer

/// Read the `mtime` register of CLINT
pub fn get_time() -> u64 {
    unsafe { core::ptr::read_volatile((CLINT_BASE + MTIME_OFFSET) as *const u64) }
}

/// Get the current time in seconds
pub fn get_time_s() -> f64 {
    get_time() as f64 / TIMER_FREQUENCY as f64
}

/// Sleep for `duration` seconds
pub fn sleep(duration: u64) {
    let ticks = duration * TIMER_FREQUENCY;
    sleep_ticks(ticks);
}

fn sleep_ticks(ticks: u64) {
    let current_time = get_time();
    let wake_time = current_time + ticks;
    sbi_rt::set_timer(wake_time);
    unsafe {
        riscv::asm::wfi();
    }
}

接下来只需要在batch.rs中添加时间记录,并增加函数支持即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
struct AppManager {
    num_app: usize,
    current_app: usize,
    app_start: [usize; MAX_APP_NUM + 1],
    start_times: [u64; MAX_APP_NUM],
    finish_times: [u64; MAX_APP_NUM],
}

impl AppManager {
    pub fn record_start_time(&mut self, time: u64) {
        if self.current_app > 0 && self.current_app - 1 < self.num_app {
            self.start_times[self.current_app - 1] = time;
        }
    }

    pub fn record_finish_time(&mut self, time: u64) {
        if self.current_app > 0 && self.current_app - 1 < self.num_app {
            self.finish_times[self.current_app - 1] = time;
        }
    }

    pub fn get_current_app_duration(&self) -> u64 {
        if self.current_app > 0 && self.current_app - 1 < self.num_app {
            self.finish_times[self.current_app - 1] - self.start_times[self.current_app - 1]
        } else {
            0
        }
    }
}

lazy_static! {
    static ref APP_MANAGER: UPSafeCell<AppManager> = unsafe {
        UPSafeCell::new({
            extern "C" {
                fn _num_app();
            }
            let num_app_ptr = _num_app as usize as *const usize;
            let num_app = num_app_ptr.read_volatile();
            let mut app_start: [usize; MAX_APP_NUM + 1] = [0; MAX_APP_NUM + 1];
            let app_start_raw: &[usize] =
                core::slice::from_raw_parts(num_app_ptr.add(1), num_app + 1);
            app_start[..=num_app].copy_from_slice(app_start_raw);
            AppManager {
                num_app,
                current_app: 0,
                app_start,
                start_times: [0; MAX_APP_NUM], // 初始化
                finish_times: [0; MAX_APP_NUM], // 初始化
            }
        })
    };
}

/// record finish time of current app
pub fn record_finish_time(time: u64) {
    APP_MANAGER.exclusive_access().record_finish_time(time);
}

/// print current app info
pub fn print_current_app() {
    APP_MANAGER.exclusive_access().print_current_app();
}

/// print duration
pub fn get_current_app_duration() -> u64 {
    APP_MANAGER.exclusive_access().get_current_app_duration()
}

/// run next app
pub fn run_next_app() -> ! {
    let mut app_manager = APP_MANAGER.exclusive_access();
    let current_app = app_manager.get_current_app();
    unsafe {
        app_manager.load_app(current_app);
    }
    app_manager.move_to_next_app();
    drop(app_manager);
    // before this we have to drop local variables related to resources manually
    // and release the resources
    extern "C" {
        fn __restore(cx_addr: usize);
    }

  	// 在程序开始之前记录开始时间
    let start_time = crate::timer::get_time();
    APP_MANAGER.exclusive_access().record_start_time(start_time);
    unsafe {
        __restore(KERNEL_STACK.push_context(TrapContext::app_init_context(
            APP_BASE_ADDRESS,
            USER_STACK.get_sp(),
        )) as *const _ as usize);
    }
    panic!("Unreachable in batch::run_current_app!");
}

最后在程序退出时记录结束时间并打印差值即可:

1
2
3
4
5
6
7
8
9
10
11
/// task exits and submit an exit code
pub fn sys_exit(exit_code: i32) -> ! {
    let finish_time = crate::timer::get_time();
    record_finish_time(finish_time);
    println!(
        "[kernel] Application exited with code {}. Time usage: {}ticks",
        exit_code,
        get_current_app_duration()
    );
    run_next_app()
}

别忘了因错误退出的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
pub fn trap_handler(cx: &mut TrapContext) -> &mut TrapContext {
    let scause = scause::read(); // get trap cause
    let stval = stval::read(); // get extra value
    match scause.cause() {
        Trap::Exception(Exception::UserEnvCall) => {
            cx.sepc += 4;
            cx.x[10] = syscall(cx.x[17], [cx.x[10], cx.x[11], cx.x[12]]) as usize;
        }
        Trap::Exception(Exception::StoreFault) | Trap::Exception(Exception::StorePageFault) => {
            let finish_time = crate::timer::get_time();
            record_finish_time(finish_time);
            println!(
                "[kernel] PageFault in application, kernel killed it. Time usage: {}ticks",
                get_current_app_duration()
            );
            run_next_app();
        }
        Trap::Exception(Exception::IllegalInstruction) => {
            let finish_time = crate::timer::get_time();
            record_finish_time(finish_time);
            println!(
                "[kernel] IllegalInstruction in application, kernel killed it. Time usage: {}ticks",
                get_current_app_duration()
            );
            run_next_app();
        }
        _ => {
            panic!(
                "Unsupported trap {:?}, stval = {:#x}!",
                scause.cause(),
                stval
            );
        }
    }
    cx
}

作业5

见练习答案

实验练习

这个练习其实非常简单,只需要在batch.rs里添加一个接口去检测write的目标地址是否位于用户栈和用户程序段的地址范围内即可:

1
2
3
4
5
6
7
8
9
10
11
12
impl AppManager {
    pub fn check_legal_address(&self, addr: usize, len: usize) -> bool {
        let app_id = self.get_current_app();
        let app_base = APP_BASE_ADDRESS;
        let app_limit = app_base + self.app_start[app_id] - self.app_start[app_id - 1];
        let stack_high = USER_STACK.get_sp();

        (addr >= app_base && addr + len <= app_limit)
            || (addr >= stack_high - USER_STACK_SIZE && addr + len <= stack_high)
            || addr == 0x80406000
    }
}

注意这里加了一个addr == 0x80406000的判断,这是因为作者拿到的ch2-lab的分支针对print!宏采用了缓冲区的实现方式,该缓冲区位于内核的全局区,起始地址为0x80406000。这里是打一个补丁以便通过测试,以前的版本似乎没有使用缓冲区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
use alloc::collections::vec_deque::VecDeque;
use alloc::sync::Arc;
use core::fmt::{self, Write};
use spin::mutex::Mutex;

pub const STDIN: usize = 0;
pub const STDOUT: usize = 1;

const CONSOLE_BUFFER_SIZE: usize = 256 * 10;

use super::{read, write};
use lazy_static::*;

struct ConsoleBuffer(VecDeque<u8>); // 缓冲区

lazy_static! {
    static ref CONSOLE_BUFFER: Arc<Mutex<ConsoleBuffer>> = {
        let buffer = VecDeque::<u8>::with_capacity(CONSOLE_BUFFER_SIZE);
        Arc::new(Mutex::new(ConsoleBuffer(buffer)))
    };
}

impl ConsoleBuffer {
    fn flush(&mut self) -> isize {
        let s: &[u8] = self.0.make_contiguous();
        let ret = write(STDOUT, s); // 在flush时调用write
        self.0.clear();
        ret
    }
}

impl Write for ConsoleBuffer {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        for c in s.as_bytes().iter() {
            self.0.push_back(*c); // 把要输出的字符加入缓冲区
            if (*c == b'\n' || self.0.len() == CONSOLE_BUFFER_SIZE) && -1 == self.flush() {
                return Err(fmt::Error);
            }
        }
        Ok(())
    }
}

#[allow(unused)]
pub fn print(args: fmt::Arguments) {
    let mut buf = CONSOLE_BUFFER.lock();
    // buf.write_fmt(args).unwrap();
    // BUG FIX: 关闭 stdout 后,本函数不能触发 panic,否则会造成死锁
    buf.write_fmt(args);
}

#[macro_export]
macro_rules! print {
    ($fmt: literal $(, $($arg: tt)+)?) => {
        $crate::console::print(format_args!($fmt $(, $($arg)+)?));
    }
}

#[macro_export]
macro_rules! println {
    ($fmt: literal $(, $($arg: tt)+)?) => {
        $crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?));
    }
}

pub fn getchar() -> u8 {
    let mut c = [0u8; 1];
    read(STDIN, &mut c);
    c[0]
}

pub fn flush() {
    let mut buf = CONSOLE_BUFFER.lock();
    buf.flush();
}
This post is licensed under CC BY 4.0 by the author.