rCore Notes: My Answers to the Programming Questions in Chapter 2

Introduction

In this article, I will answer the programming questions in Chapter 2 of rCore tutorial v3. The questions are written in Chinese, and I will translate them into English and answer them.

Questions

Question 1

  • 问题: 实现一个裸机应用程序A,能打印调用栈。
  • Question: Implement a bare-metal application A that can print the call stack.

At first, we need get fp register to get the frame pointer of the current function. Then, we can get the saved return address and saved frame pointer from the stack. Finally, we can print the saved return address and saved frame pointer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// os/src/stack_trace.rs

use core::{arch::asm, ptr};

pub unsafe fn print_stack_trace() -> () {
    let mut fp: *const usize;
    asm!("mv {}, fp", out(reg) fp);

    println!("== Begin stack trace ==");
    while fp != ptr::null() {
        let saved_ra = *fp.sub(1);
        let saved_fp = *fp.sub(2);

        println!("0x{:016x}, fp = 0x{:016x}", saved_ra, saved_fp);

        fp = saved_fp as *const usize;
    }
    println!("== End stack trace ==");
}

After that, we need to add the stack_trace module to the main.rs.

1
2
3
// os/src/main.rs

mod stack_trace;

Then, we can call the print_stack_trace function in the panic_handler for printing the call stack when a panic occurs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// os/src/lang_items.rs

use crate::sbi::shutdown;
use crate::stack_trace::print_stack_trace;
use core::panic::PanicInfo;

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    if let Some(location) = info.location() {
        println!(
            "Panicked at {}:{} {}",
            location.file(),
            location.line(),
            info.message()
        );
    } else {
        println!("Panicked: {}", info.message());
    }
    unsafe { print_stack_trace() };
    shutdown(true)
}

Question 2

  • 问题: 扩展内核,实现新系统调用get_taskinfo,能显示当前task的id和task name;实现一个裸机应用程序B,能访问get_taskinfo系统调用。
  • Question: Extend the kernel to implement a new system call get_taskinfo that can display the ID and name of the current task; implement a bare-metal application B that can access the get_taskinfo system call.

I didn’t find get_taskinfo in Linux and other kernels, so I will implement it by myself. I use 114514 as the syscall number of get_taskinfo. (你是一个一个一个系统调用啊啊啊)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// os/src/syscall/mod.rs

const SYSCALL_GET_TASKINFO: usize = 114514;

pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize {
    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),
    }
}

Then we need to implement the sys_get_taskinfo system call, it will print the current task’s information and return the current task’s ID.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// os/src/syscall/process.rs


use crate::batch::{get_current_app, print_app_info};

pub fn sys_get_taskinfo() -> isize {
    print_app_info();

    get_current_app() as isize
}
1
2
3
4
5
// os/src/batch.rs

pub fn get_current_app() -> usize {
    APP_MANAGER.exclusive_access().get_current_app()
}

After that, we can test the get_taskinfo system call in the user application.

1
2
3
4
5
6
7
// user/src/syscall.rs

const SYSCALL_GET_TASKINFO: usize = 114514;

pub fn sys_get_taskinfo() -> isize {
    syscall(SYSCALL_GET_TASKINFO, [0, 0, 0])
}
1
2
3
4
5
// user/src/lib.rs

pub fn get_taskinfo() -> isize {
    sys_get_taskinfo()
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// user/src/bin/05get_taskinfo.rs

#![no_std]
#![no_main]

use user_lib::get_taskinfo;

#[macro_use]
extern crate user_lib;

#[no_mangle]
fn main() -> i32 {
    let current_app = get_taskinfo();
    println!("current_app = {}", current_app);
    0
}

Finally, run it and check the output.

Question 3

  • 问题: 扩展内核,能够统计多个应用的执行过程中系统调用编号和访问此系统调用的次数。
  • Question: Extend the kernel to be able to count the number of times each system call number is accessed during the execution of multiple applications.

We add a SyscallCounter struct to count the number of times each system call number is accessed. Similar to AppManager, we use UPSafeCell for make SyscallCounter safe on single-core systems. In addition. we add a public function get_syscall_count to get the number of times a system call number is accessed.

 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
// os/src/syscall/mod.rs

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

const MAX_SYSCALL_ID: usize = 93; // SYSCALL_EXIT is max currently

mod fs;
mod process;

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

use lazy_static::lazy_static;

use crate::sync::UPSafeCell;

struct SyscallCounter {
    count: [usize; MAX_SYSCALL_ID + 1],
}

impl SyscallCounter {
    fn add_count(&mut self, syscall_id: usize) {
        self.count[syscall_id] += 1;
    }

    fn get_count(&self, syscall_id: usize) -> usize {
        return self.count[syscall_id];
    }
}

lazy_static! {
    static ref SYSCALL_COUNTER: UPSafeCell<SyscallCounter> = unsafe {
        UPSafeCell::new(SyscallCounter {
            count: [0; MAX_SYSCALL_ID + 1],
        })
    };
}

pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize {
    let ret = match syscall_id {
        SYSCALL_WRITE => sys_write(args[0], args[1] as *const u8, args[2]),
        SYSCALL_EXIT => sys_exit(args[0] as i32),
        _ => panic!("Unsupported syscall_id: {}", syscall_id),
    };

    SYSCALL_COUNTER.exclusive_access().add_count(syscall_id);

    ret
}

pub fn get_syscall_count(syscall_id: usize) -> usize {
    SYSCALL_COUNTER.exclusive_access().get_count(syscall_id)
}

After that, we can test the get_syscall_count and print the number of times SYSCALL_WRITE is called after all applications are completed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// os/src/batch.rs
impl AppManager {
    // ... existing code

    unsafe fn load_app(&self, app_id: usize) {
        if app_id >= self.num_app {
            println!("All applications completed");

            // Show the number of times SYSCALL_WRITE is called
            println!("{}", crate::syscall::get_syscall_count(64));

            shutdown(false);
        }
      // ... existing code
    }

    // ... existing code
}

Question 4

  • 问题: 扩展内核,能够统计每个应用执行后的完成时间。
  • Question: Extend the kernel to be able to count the completion time of each application after execution.

We have introduced how to get time on the RISC-V platform in the previous articles. We can use the same method to get the start and end time of each application. We add start_time and end_time to the AppManager struct to record the start and end time of each application. We modified move_to_next_app to and record the end time of the current application. We add get_app_exec_time to get the execution time of an application. We add print_app_exec_time to print the execution time of each application.

 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
// os/src/batch.rs

use riscv::register::time;

struct AppManager {
    num_app: usize,
    current_app: usize,
    app_start: [usize; MAX_APP_NUM + 1],
    start_time: [usize; MAX_APP_NUM + 1],
    end_time: [usize; MAX_APP_NUM + 1],
}

impl AppManager {
    // ... existing code

    pub fn move_to_next_app(&mut self) {
        self.end_time[self.current_app] = time::read(); // last app end time
        self.current_app += 1;
        self.start_time[self.current_app] = time::read(); // next app start time
    }

    pub fn get_app_exec_time(&self, app_id: usize) -> usize {
        if app_id >= self.num_app {
            panic!("app_{} don't exist", app_id);
        }

        self.end_time[app_id] - self.start_time[app_id]
    }

    pub fn print_app_exec_time(&self) {
        const TIMEBASE_FREQ: usize = 0x989680; // 10_000_000 is QEMU's timebase frequency.

        for i in 0..self.num_app {
            println!(
                "[kernel] app_{} used {}us",
                i,
                self.get_app_exec_time(i) / (TIMEBASE_FREQ / 1_000_000),
            );
        }
    }

    // ... existing code
}

After that, we can test the print_app_exec_time and print the execution time of each application after all applications are completed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// os/src/batch.rs

impl AppManager {
    // ... existing code

    unsafe fn load_app(&self, app_id: usize) {
        if app_id >= self.num_app {
            println!("All applications completed");

            // Show the execution time of each application
            self.print_app_exec_time();

            shutdown(false);
        }
      // ... existing code
    }

    // ... existing code
}

Question 5

  • 问题: 扩展内核,统计执行异常的程序的异常情况(主要是各种特权级涉及的异常),能够打印异常程序的出错的地址和指令等信息。
  • Question: Extend the kernel to count the exception information of the program that executes the exception (mainly the exceptions related to various privilege levels), and print the error address and instruction information of the exception program.

We can read sstatus for checking the privilege level of the exception program. And we can use scause, stval and spec to get the cause, bad address and bad instruction of the exception.

 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
// os/src/trap/mod.rs

#[no_mangle]
/// handle an interrupt, exception, or system call from user space
pub fn trap_handler(cx: &mut TrapContext) -> &mut TrapContext {
    let scause = scause::read();
    let stval = stval::read();

    if sstatus::read().spp() !=  sstatus::SPP::User {
        panic!("Trap not from user mode");
    }

    match scause.cause() {
        Trap::Exception(Exception::UserEnvCall) => {
            cx.spec += 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) => {
            println!("[kernel] {:?} in application, bad address = {:#x}, bad instruction = {:#x}, kernel killed it.",
                scause.cause(), stval, cx.spec);
            run_next_app();
        }
        Trap::Exception(Exception::IllegalInstruction) => {
            println!("[kernel] IllegalInstruction in application, kernel killed it.");
            run_next_app();
        }
        _ => {
            panic!("Unsupported trap {:?}, stval = {:#x}!", scause.cause(), stval);
        }
    }

    cx
}

Conclusion

In this article, I answered the programming questions in Chapter 2 of the rCore tutorial. I add some new features to the Batch OS kernel, it’s interesting to implement these features. I hope you can learn something from my answers. If you have any questions, please contact me. Thank you for reading.

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy