rCore Notes: Implement the sys_get_taskinfo System Call

Introduction

TIPS: This post had been rewritten for rCore Tutorial Guide 2024 Autumn

In the lab of Chapter 3, we have been asked to implement the sys_get_taskinfo system call. This syscall is used to get the information of a task. In this post, I will share my implementation of this syscall.

The syscall is defined as follows:

1
fn sys_task_info(ti: *mut TaskInfo) -> isize

The parameter ti is a pointer to a TaskInfo structure. The TaskInfo structure is defined as follows:

1
2
3
4
5
6
struct TaskInfo {
    id: usize,
    status: TaskStatus,
    syscall_times: [u32; MAX_SYSCALL_NUM],
    time: usize
}

When the syscall executes successfully, it should return 0. Otherwise, it should return -1.

Analysis

By review the test case of this lab, we can known we must record the system call times before it be called. And review the sys_get_time syscall, we can known TaskInfo.time should be the running milliseconds of the task.

 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
#![no_std]
#![no_main]

extern crate user_lib;

use user_lib::{
    get_time, println, sleep, task_info, TaskInfo, TaskStatus, SYSCALL_EXIT, SYSCALL_GETTIMEOFDAY,
    SYSCALL_TASK_INFO, SYSCALL_WRITE, SYSCALL_YIELD,
};

#[no_mangle]
pub fn main() -> usize {
    let t1 = get_time() as usize;
    let info = TaskInfo::new();
    get_time();
    sleep(500);
    let t2: usize = get_time() as usize;
    // 注意本次 task info 调用也计入
    assert_eq!(0, task_info(&info));
    let t3 = get_time() as usize;
    assert!(3 <= info.syscall_times[SYSCALL_GETTIMEOFDAY]);
    assert_eq!(1, info.syscall_times[SYSCALL_TASK_INFO]);
    assert_eq!(0, info.syscall_times[SYSCALL_WRITE]);
    assert!(0 < info.syscall_times[SYSCALL_YIELD]);
    assert_eq!(0, info.syscall_times[SYSCALL_EXIT]);
    assert!(t2 - t1 <= info.time + 1);
    assert!(info.time < t3 - t1 + 100);
    assert!(info.status == TaskStatus::Running);

    // 想想为什么 write 调用是两次
    println!("string from task info test\n");
    let t4 = get_time() as usize;
    assert_eq!(0, task_info(&info));
    let t5 = get_time() as usize;
    assert!(5 <= info.syscall_times[SYSCALL_GETTIMEOFDAY]);
    assert_eq!(2, info.syscall_times[SYSCALL_TASK_INFO]);
    assert_eq!(2, info.syscall_times[SYSCALL_WRITE]);
    assert!(0 < info.syscall_times[SYSCALL_YIELD]);
    assert_eq!(0, info.syscall_times[SYSCALL_EXIT]);
    assert!(t4 - t1 <= info.time + 1);
    assert!(info.time < t5 - t1 + 100);
    assert!(info.status == TaskStatus::Running);

    println!("Test task info OK!");
    0
}

Implementation

We need to add the SYSCALL_GET_TASKINFO constant to define the system call number of sys_get_taskinfo and the MAX_SYSCALL_NUM constant to define the maximum number of system calls.

1
2
3
4
5
// os/src/syscall/mod.rs

const SYSCALL_GET_TASKINFO: usize = 410;

pub const MAX_SYSCALL_NUM: usize = 411;

Implement System Calls Counter

It’s easy to do this by adding a method to the TaskManager structure and expose it to the outside.

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

impl TaskManager {
    fn record_syscall_times(&self, syscall_id: usize) {
        let mut inner = self.inner.exclusive_access();
        let current = inner.current_task;
        inner.tasks[current].syscall_times[syscall_id] += 1;
    }
}

pub fn record_syscall_times(syscall_id: usize) {
    TASK_MANAGER.record_syscall_times(syscall_id);
}

And we need to call this method in the syscall function.

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

pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize {
    record_syscall_times(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_YIELD => sys_yield(),
        SYSCALL_GET_TIME => sys_get_time(args[0] as *mut TimeVal, args[1]),
        _ => panic!("Unsupported syscall_id: {}", syscall_id),
    }
}

Calculate Running Time

At first, we must implement the get_time_ms function to get the current time in milliseconds.

1
2
3
4
5
// os/src/time.rs

pub fn get_time_ms() -> usize {
    time::read() / (CLOCK_FREQ / MILLI_PER_SEC)
}

We have add start_time field to the TaskControlBlock structure, so we can record the start time of the task when first running.

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

impl TaskManager {
    fn run_first_task(&self) -> ! {
        let mut inner = self.inner.exclusive_access();
        let task0 = &mut inner.tasks[0];
        task0.task_status = TaskStatus::Running;
        task0.start_time = Some(get_time_ms()); // record the start time of the task
        let next_task_cx_ptr = &task0.task_cx as *const TaskContext;
        drop(inner);
        let mut _unused = TaskContext::zero_init();
        // before this, we should drop local variables that must be dropped manually
        unsafe {
            __switch(&mut _unused as *mut TaskContext, next_task_cx_ptr);
        }
        panic!("unreachable in run_first_task!");
    }

    fn run_next_task(&self) {
        if let Some(next) = self.find_next_task() {
            let mut inner = self.inner.exclusive_access();
            let current = inner.current_task;
            inner.tasks[next].task_status = TaskStatus::Running;
            // record the start time of the task
            if inner.tasks[next].start_time.is_none() {
                inner.tasks[next].start_time = Some(get_time_ms());
            }
            inner.current_task = next;
            let current_task_cx_ptr = &mut inner.tasks[current].task_cx as *mut TaskContext;
            let next_task_cx_ptr = &inner.tasks[next].task_cx as *const TaskContext;
            drop(inner);
            // before this, we should drop local variables that must be dropped manually
            unsafe {
                __switch(current_task_cx_ptr, next_task_cx_ptr);
            }
            // go back to user mode
        } else {
            panic!("All applications completed!");
        }
    }
}

The we can calculate the running time of the task by the following code:

1
get_time_ms() - self.inner.tasks[current].start_time.unwrap()

Implement A Method for Get TaskInfo

Now we can implement a method to get the TaskInfo structure of a task.

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

impl TaskManager {
    fn get_taskinfo(&self, ) -> TaskInfo {
        let inner = self.inner.exclusive_access();
        let current = inner.current_task;
        let task = inner.tasks[current];

        TaskInfo {
            status: task.task_status,
            syscall_times: task.syscall_times,
            time: get_time_ms() - task.start_time.unwrap(),
        }
    }
}

/// Get the task information of the current task
pub fn get_taskinfo() -> TaskInfo {
    TASK_MANAGER.get_taskinfo()
}

Implement the sys_get_taskinfo System Call

Finally, we can implement the sys_get_taskinfo system call. When the task id is invalid, we should return -1. Otherwise, we should copy the TaskInfo structure to the memory pointed by the ts parameter.

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

pub fn sys_task_info(ti: *mut TaskInfo) -> isize {
    trace!("kernel: sys_task_info");
    let task_info = get_taskinfo();

    unsafe {
        *ti = task_info;
    }

    0
}

Don’t forget to add the sys_get_taskinfo function to the syscall function.

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

pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize {
    record_syscall_times(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_YIELD => sys_yield(),
        SYSCALL_GET_TASKINFO => sys_get_taskinfo(args[0] as usize, args[1] as *mut TaskInfo),
        SYSCALL_GET_TIME => sys_get_time(args[0] as *mut TimeVal, args[1]),
        _ => panic!("Unsupported syscall_id: {}", syscall_id),
    }
}

Test in User Space

After implementing the system call, we can test it in user space. First, we should add some structures and constants to the user library.

 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
// user/src/lib.rs

#[derive(Debug, PartialEq)]
pub enum TaskStatus {
    UnInit,
    Ready,
    Running,
    Exited,
}


#[derive(Debug)]
pub struct TaskInfo {
    pub id: usize,
    pub status: TaskStatus,
    pub syscall_times: [u32; MAX_SYSCALL_NUM],
    pub time: usize,
}

impl TaskInfo {
    pub fn new() -> Self {
        Self {
            id: 0,
            status: TaskStatus::UnInit,
            syscall_times: [0; MAX_SYSCALL_NUM],
            time: 0,
        }
    }
}
1
2
3
4
5
// user/src/syscall.rs

pub const SYSCALL_TASK_INFO: usize = 410;

pub const MAX_SYSCALL_NUM: usize = 411;

Then we can implement the task_info function to get the task information.

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

pub fn sys_get_taskinfo(ti: &TaskInfo) -> isize {
    syscall(SYSCALL_TASK_INFO, [ts as *const _ as usize, 0, 0])
}
1
2
3
4
5
// user/src/lib.rs

pub fn task_info(ti: &TaskInfo) -> isize {
    sys_get_taskinfo(ti)
}

Finally, we can test this lab by run make run TEST=1. The test case had been shown in the analysis section.

Conclusion

In this post, I shared my implementation of the sys_get_taskinfo system call. I will continue to study the rCore Tutorials and share my notes in the future. If you have any questions or suggestions, please email me (Emails are in the about page). Thank you for reading.

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