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.