[rCore學習筆記 026]第三章作業

来源:https://www.cnblogs.com/chenhan-winddevil/p/18414400
-Advertisement-
Play Games

寫在前面 本隨筆是非常菜的菜雞寫的。如有問題請及時提出。 可以聯繫:[email protected] GitHhub:https://github.com/WindDevil (目前啥也沒有 編程題 第一題 擴展內核,能夠顯示操作系統切換任務的過程。 首先先回憶一下操作系統切換任務的過程. 因此只 ...


寫在前面

本隨筆是非常菜的菜雞寫的。如有問題請及時提出。

可以聯繫:[email protected]

GitHhub:https://github.com/WindDevil (目前啥也沒有

編程題

第一題

擴展內核,能夠顯示操作系統切換任務的過程。

首先先回憶一下操作系統切換任務的過程.

因此只需要在這些關鍵節點加上println!即可.

首先是在Trap的時候輸出,這裡其餘的都有了,只增加了Interrupt::SupervisorTimer的輸出:

// 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(); // 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) => {
            println!("[kernel] PageFault in application, kernel killed it.");
            exit_current_and_run_next();
        }
        Trap::Exception(Exception::IllegalInstruction) => {
            println!("[kernel] IllegalInstruction in application, kernel killed it.");
            exit_current_and_run_next();
        }
        Trap::Interrupt(Interrupt::SupervisorTimer) => {
            println!("\nTimer interrupt,time slice used up!");
            set_next_trigger();
            println!("Next timer set!");
            suspend_current_and_run_next();
        }
        _ => {
            panic!(
                "Unsupported trap {:?}, stval = {:#x}!",
                scause.cause(),
                stval
            );
        }
    }
    cx
}

修改Task,在修改當前任務的狀態之後輸出:

// os/src/task/mod.rs

/// 當前任務主動放棄 CPU 使用權
pub fn suspend_current_and_run_next()
{
    mark_current_suspended();
    println!("\nTask {} suspended", TASK_MANAGER.inner.exclusive_access().current_task);
    run_next_task();
}

/// 當前任務退出
pub fn exit_current_and_run_next()
{
    mark_current_exited();
    println!("\nTask {} exited", TASK_MANAGER.inner.exclusive_access().current_task);
    run_next_task();
}

在尋找到下一個task後,輸出下一個task.

// os/src/task/mod.rs

impl TaskManager
{
... ...
    fn run_next_task(&self)
    {
        if let Some(next) = self.find_next_task()
        {
            println!("\nFound next task {}", next);
            let mut inner = self.inner.exclusive_access();
            let current = inner.current_task;
            inner.current_task = next;
            inner.tasks[next].task_status = TaskStatus::Running;
            let current_task_cx_ptr = &mut inner.tasks[current].task_cx as *mut TaskContext;
            let next_task_cx_ptr = &mut inner.tasks[next].task_cx as *const TaskContext;
            drop(inner);
            unsafe
            {
                __switch(current_task_cx_ptr, next_task_cx_ptr);
            }
            println!("\nTask {} has been switched out", next);
        }
        else
        {
            println!("All applications completed!");
            shutdown(false);
        }
    }
}

這時候make run,輸出:

[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______       __    __      _______.___________.  _______..______   __
|   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
|  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
|      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
|  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
| _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
[rustsbi] Implementation     : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name      : riscv-virtio,qemu
[rustsbi] Platform SMP       : 1
[rustsbi] Platform Memory    : 0x80000000..0x88000000
[rustsbi] Boot HART          : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address   : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
[kernel] Hello, world!
[kernel] trap init end
power_3 [10000/200000]
power_3 [20000/200000]
power_3 [30000/200000]
power_3 [40000/200000]
power_3 [50000/200000]
power_3 [60000/200000]
power_3 [70000/200000]
power_3 [80000/200000]
power_3 [90000/200000]
power_3 [100000/200000]
power_3 [110000/200000]
power_3 [120000/200000]
power_3 [130000/200000]
power_3 [140000/200000]
power_3 [150000/200000]
power_3 [160000/200000]
power_3 [170000/200000]
power_3 [180000/200000]
power_3 [190000/200000]
power_3 [200000/200000]
3^200000 = 871008973(MOD 998244353)
Test power_3 OK!
[kernel] Application exited with code 0

Task 0 exited

Found next task 1
power_7 [10000/160000]
power_7 [20000/160000]
power_7 [30000/160000]
power_7 [40000/160000]
power_7 [50000/160000]
power_7 [60000/160000]
power_7 [70000/160000]
power_7 [80000/160000]
power_7 [90000/160000]
power_7 [100000/160000]
power_7 [110000/160000]
power_7 [120000/160000]
power_7 [130000/160000]
power_7 [140000/160000]
power_7 [150000/160000]
power_7 [160000/160000]
7^160000 = 667897727(MOD 998244353)
Test power_7 OK!
[kernel] Application exited with code 0

Task 1 exited

Found next task 2
power_7 [10000/160000]
power_7 [20000/160000]
power_7 [30000/160000]
power_7 [40000/160000]
power_7 [50000/160000]
power_7 [60000/160000]
power_7 [70000/160000]
power_7 [80000/160000]
power_7 [90000/160000]
power_7 [100000/160000]
power_7 [110000/160000]
power_7 [120000/160000]
power_7 [130000/160000]
power_7 [140000/160000]
power_7 [150000/160000]
power_7 [160000/160000]
7^160000 = 667897727(MOD 998244353)
Test power_7 OK!
[kernel] Application exited with code 0

Task 2 exited

Found next task 3

Task 3 suspended

Found next task 3

Task 3 has been switched out

Timer interrupt,time slice used up!
Next timer set!

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out
Test sleep OK!
[kernel] Application exited with code 0

Task 3 exited
All applications completed!

這裡為了輸出更符合標準,可能需要在每句字元串前邊加上[kernel], 這樣可以分辨這句LOG是從哪裡輸出出來的.

第二題

擴展內核,能夠統計每個應用執行後的完成時間:用戶態完成時間和內核態完成時間。

這個只需要略微明白mtime的作用就行,另外,如果你是一直看我博客到現在的,我現在提醒你可以去做第一章沒有做完的作業了.

原本的打算是實現一個線程安全的數組,但是這樣的寫法說明我對OOP的理解學到了直腸里,主打一個腦子不好使.

其實只需在每一個任務控制塊裡邊加入一個任務運行開始時間的變數就行.

// os/src/task/task.rs

#[derive(Copy, Clone)]
pub struct TaskControlBlock 
{
    pub task_status: TaskStatus,
    pub task_cx: TaskContext,
    pub task_start_time: isize,
}

不要忘記初始化的時候初始化這個變數:

// os/src/task/mod.rs

lazy_static!
{
    /// 全局單例的任務管理器
    pub static ref TASK_MANAGER: TaskManager = 
    {
        let num_app = get_num_app();
        let mut tasks = [TaskControlBlock{
            task_status: TaskStatus::UnInit,
            task_cx: TaskContext::zero_init(),
            task_start_time: -1,
        }
        ; MAX_APP_NUM];
        for(i,task) in tasks.iter_mut().enumerate()
        {
            task.task_cx = TaskContext::goto_restore(init_app_cx(i));
            task.task_status = TaskStatus::Ready;
        }
        TaskManager
        {
            num_app,
            inner: unsafe
            {
                UPSafeCell::new(TaskManagerInner
                {
                    tasks,
                    current_task: 0,
                })
            }
        }
    };
}

這裡註意要初始化為-1,判斷有沒有初始化過.

然後要在切換任務的時候獲取當前時間,再退出時輸出任務時間:

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;
        let next_task_cx_ptr = &task0.task_cx as *const TaskContext;
        let mut _unused = TaskContext::zero_init();
        inner.tasks[0].task_start_time = get_time_us() as isize;
        drop(inner);
        unsafe 
        {
            __switch(&mut _unused as *mut TaskContext, next_task_cx_ptr)
        }
        panic!("unreachable in run_first_task");    
    }

    fn mark_current_suspended(&self)
    {
        let mut inner = self.inner.exclusive_access();
        let current = inner.current_task;
        inner.tasks[current].task_status = TaskStatus::Ready;
    }

    fn mark_current_exited(&self)
    {
        let mut inner = self.inner.exclusive_access();
        let current = inner.current_task;
        println!("\nTask {} RunTime:{}~{} {}us", current, get_time_us(),inner.tasks[current].task_start_time,get_time_us() as isize -inner.tasks[current].task_start_time);
        inner.tasks[current].task_status = TaskStatus::Exited;
    }

    fn find_next_task(&self) -> Option<usize>
    {
        let inner = self.inner.exclusive_access();
        let current = inner.current_task;
        (current + 1..current+1+self.num_app)
            .map(|id| id%self.num_app)
            .find(|id| inner.tasks[*id].task_status == TaskStatus::Ready)
    }

    fn run_next_task(&self)
    {
        if let Some(next) = self.find_next_task()
        {
            println!("\nFound next task {}", next);
            let mut inner = self.inner.exclusive_access();
            let current = inner.current_task;
            inner.current_task = next;
            if inner.tasks[next].task_start_time == -1
            {
                inner.tasks[next].task_start_time = get_time_us() as isize;
            }
            inner.tasks[next].task_status = TaskStatus::Running;
            let current_task_cx_ptr = &mut inner.tasks[current].task_cx as *mut TaskContext;
            let next_task_cx_ptr = &mut inner.tasks[next].task_cx as *const TaskContext;
            drop(inner);
            unsafe
            {
                __switch(current_task_cx_ptr, next_task_cx_ptr);
            }
            println!("\nTask {} has been switched out", next);
        }
        else
        {
            println!("All applications completed!");
            shutdown(false);
        }
    }
}


lazy_static!
{
    /// 全局單例的任務管理器
    pub static ref TASK_MANAGER: TaskManager = 
    {
        let num_app = get_num_app();
        let mut tasks = [TaskControlBlock{
            task_status: TaskStatus::UnInit,
            task_cx: TaskContext::zero_init(),
            task_start_time: -1,
        }
        ; MAX_APP_NUM];
        for(i,task) in tasks.iter_mut().enumerate()
        {
            task.task_cx = TaskContext::goto_restore(init_app_cx(i));
            task.task_status = TaskStatus::Ready;
        }
        TaskManager
        {
            num_app,
            inner: unsafe
            {
                UPSafeCell::new(TaskManagerInner
                {
                    tasks,
                    current_task: 0,
                })
            }
        }
    };
}

然後make run:

[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______       __    __      _______.___________.  _______..______   __
|   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
|  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
|      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
|  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
| _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
[rustsbi] Implementation     : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name      : riscv-virtio,qemu
[rustsbi] Platform SMP       : 1
[rustsbi] Platform Memory    : 0x80000000..0x88000000
[rustsbi] Boot HART          : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address   : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
[kernel] Hello, world!
[kernel] trap init end
power_3 [10000/200000]
power_3 [20000/200000]
power_3 [30000/200000]
power_3 [40000/200000]
power_3 [50000/200000]
power_3 [60000/200000]
power_3 [70000/200000]
power_3 [80000/200000]
power_3 [90000/200000]
power_3 [100000/200000]
power_3 [110000/200000]
power_3 [120000/200000]
power_3 [130000/200000]
power_3 [140000/200000]
power_3 [150000/200000]
power_3 [160000/200000]
power_3 [170000/200000]
power_3 [180000/200000]
power_3 [190000/200000]
power_3 [200000/200000]
3^200000 = 871008973(MOD 998244353)
Test power_3 OK!
[kernel] Application exited with code 0

Task 0 RunTime:12921~5597 7330us

Task 0 exited

Found next task 1
power_7 [10000/160000]
power_7 [20000/160000]
power_7 [30000/160000]
power_7 [40000/160000]
power_7 [50000/160000]
power_7 [60000/160000]
power_7 [70000/160000]
power_7 [
Timer interrupt,time slice used up!
Next timer set!

Task 1 suspended

Found next task 2
power_7 [10000/160000]
power_7 [20000/160000]
power_7 [30000/160000]
power_7 [40000/160000]
power_7 [50000/160000]
power_7 [60000/160000]
power_7 [70000/160000]
power_7 [80000/160000]
power_7 [90000/160000]
power_7 [100000/160000]
power_7 [110000/160000]
power_7 [120000/160000]
power_7 [130000/160000]
power_7 [140000/160000]
power_7 [150000/160000]
power_7 [160000/160000]
7^160000 = 667897727(MOD 998244353)
Test power_7 OK!
[kernel] Application exited with code 0

Task 2 RunTime:19099~16545 2554us

Task 2 exited

Found next task 3

Task 3 suspended

Found next task 1

Task 2 has been switched out
80000/160000]
power_7 [90000/160000]
power_7 [100000/160000]
power_7 [110000/160000]
power_7 [120000/160000]
power_7 [130000/160000]
power_7 [140000/160000]
power_7 [150000/160000]
power_7 [160000/160000]
7^160000 = 667897727(MOD 998244353)
Test power_7 OK!
[kernel] Application exited with code 0

Task 1 RunTime:21086~13182 7904us

Task 1 exited

Found next task 3

Task 1 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out
Test sleep OK!
[kernel] Application exited with code 0

Task 3 RunTime:23113~19237 3876us

Task 3 exited
All applications completed!

但是這樣似乎記錄的是任務創建和任務結束之間的時間差,和我們目標中的還不一樣,我們希望統計的是用戶態和內核態的完成時間.

那麼不僅沒有區分用戶態和內核態,而且還沒有再任務不運行的時候停止計時.

這裡再加一個成員就行:

// os/src/task/task.rs
... ...
pub struct TaskControlBlock 
{
	... ...
    pub task_start_time: isize,
    pub task_running_time: isize,
}
... ...

在兩種進行任務切換的情況下,計算任務運行時的差值:

// os/src/task/mod.rs
... ...
    fn mark_current_suspended(&self)
    {
		... ...
        inner.tasks[current].task_running_time += get_time_us() as isize - inner.tasks[current].task_start_time;
        ... ...
    }

    fn mark_current_exited(&self)
    {
        ... ...
        inner.tasks[current].task_running_time += get_time_us() as isize - inner.tasks[current].task_start_time;
        println!("\nTask {} RunTime:{} us", current, inner.tasks[current].task_running_time);
        ... ...
    }
... ...

這裡註意要每次運行的時候都更新task_start_time:

 fn run_next_task(&self)
    {
        if let Some(next) = self.find_next_task()
        {
			... ...
            inner.tasks[next].task_start_time = get_time_us() as isize;
			... ...
        }
        else
        {
			... ...
        }
    }

也要註意初始化這個變數為0:

lazy_static!
{
    /// 全局單例的任務管理器
    pub static ref TASK_MANAGER: TaskManager = 
    {
		... ...
        let mut tasks = [TaskControlBlock{
		... ...
            task_running_time: 0,
        }
        ; MAX_APP_NUM];
        for(i,task) in tasks.iter_mut().enumerate()
        {
			... ...
        }
        TaskManager
        {
			... ...
        }
    };
}

這時候進行運行:

[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______       __    __      _______.___________.  _______..______   __
|   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
|  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
|      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
|  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
| _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
[rustsbi] Implementation     : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name      : riscv-virtio,qemu
[rustsbi] Platform SMP       : 1
[rustsbi] Platform Memory    : 0x80000000..0x88000000
[rustsbi] Boot HART          : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address   : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
[kernel] Hello, world!
[kernel] trap init end
power_3 [10000/200000]
power_3 [20000/200000]
power_3 [30000/200000]
power_3 [40000/200000]
power_3 [50000/200000]
power_3 [60000/200000]
power_3 [70000/200000]
power_3 [80000/200000]
power_3 [90000/200000]
power_3 [100000/200000]
power_3 [110000/200000]
power_3 [120000/200000]
power_3 [130000/200000]
power_3 [140000/200000]
power_3 [150000/200000]
power_3 [160000/200000]
power_3 [170000/200000]
power_3 [180000/200000]
power_3 [190000/200000]
power_3 [200000/200000]
3^200000 = 871008973(MOD 998244353)
Test power_3 OK!
[kernel] Application exited with code 0

Task 0 RunTime:4285us

Task 0 exited

Found next task 1
power_7 [10000/160000]
power_7 [20000/160000]
power_7 [30000/160000]
power_7 [40000/160000]
power_7 [50000/160000]
power_7 [60000/160000]
power_7 [70000/160000]
power_7 [80000/160000]
power_7 [90000/160000]
power_7 [100000/160000]
power_7 [110000/160000]
power_7 [120000/160000]
power_7 [130000/160000]
power_7 [140000/160000]
power_7 [150000/160000]
power_7 [160000/160000]
7^160000 = 667897727(MOD 998244353)
Test power_7 OK!
[kernel] Application exited with code 0

Task 1 RunTime:4896us

Task 1 exited

Found next task 2
power_7 [
Timer interrupt,time slice used up!
Next timer set!

Task 2 suspended

Found next task 3

Task 3 suspended

Found next task 2

Task 3 has been switched out
10000/160000]
power_7 [20000/160000]
power_7 [30000/160000]
power_7 [40000/160000]
power_7 [50000/160000]
power_7 [60000/160000]
power_7 [70000/160000]
power_7 [80000/160000]
power_7 [90000/160000]
power_7 [100000/160000]
power_7 [110000/160000]
power_7 [120000/160000]
power_7 [130000/160000]
power_7 [140000/160000]
power_7 [150000/160000]
power_7 [160000/160000]
7^160000 = 667897727(MOD 998244353)
Test power_7 OK!
[kernel] Application exited with code 0

Task 2 RunTime:4611us

Task 2 exited

Found next task 3

Task 2 has been switched out
Test sleep OK!
[kernel] Application exited with code 0

Task 3 RunTime:291us

Task 3 exited
All applications completed!

這裡看到,Task 3 RunTime:291us,和user/src/bin/03sleep.rs的內容相匹配,由於我們的系統頻率為os/src/boards/qemu.rs所記述的10000000,因此任務的作用是是睡眠300us:

#![no_std]
#![no_main]

#[macro_use]
extern crate user_lib;

use user_lib::{get_time, yield_};

#[no_mangle]
fn main() -> i32 {
    let current_timer = get_time();
    let wait_for = current_timer + 3000;
    while get_time() < wait_for {
        yield_();
    }
    println!("Test sleep OK!");
    0
}

但是似乎搞得又不太對,只是記錄了每個任務的執行時間,那麼內核態時間用戶態時間呢?

看了很久參考答案,發現根本沒有成功得到停表的精髓,其實就是只用了一個時間戳,然後只計算時間戳和當前時間之間的插值而已,別想的太複雜.

這裡看到參考答案暈了的原因是沒有發現__switch前後的內核時間應該分別記錄到兩個任務中,同一個函數的時間可以不記錄在同一個地方.

這裡是不發生任務切換的情況,這時候腦海中就一直想為什麼參考答案要在mark_current_suspendedmark_current_exited中加停表呢?:

如果發生任務切換,那麼更複雜一些:

這時候應該看到,我們應該在__swtich前後刷新停表,並且記錄時間.

這是時候修改TaskManager的方法,這裡refresh_stop_watch的位置沒有加到mark_current_suspendedmark_current_exited,這是因為理解不同,我認為發生了__switch之後才算真正完成了切換,而答案中則認為Task狀態發生改變就是發生了切換:

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;
        let next_task_cx_ptr = &task0.task_cx as *const TaskContext;
        let mut _unused = TaskContext::zero_init();
        inner.stop_watch = get_time_us();
        drop(inner);
        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()
        {
            println!("\nFound next task {}", next);
            let mut inner = self.inner.exclusive_access();
            let current = inner.current_task;
            inner.current_task = next;
            inner.tasks[next].task_status = TaskStatus::Running;
            let current_task_cx_ptr = &mut inner.tasks[current].task_cx as *mut TaskContext;
            let next_task_cx_ptr = &mut inner.tasks[next].task_cx as *const TaskContext;
            inner.refresh_stop_watch();
            drop(inner);
            unsafe
            {
                __switch(current_task_cx_ptr, next_task_cx_ptr);
            }
            println!("\nTask {} has been switched out", next);
        }
        else
        {
            println!("All applications completed!");
            shutdown(false);
        }
    }
... ...
    /// 統計內核時間,從現在開始算的是用戶時間
    fn user_time_start(&self) {
        let mut inner = self.inner.exclusive_access();
        let current = inner.current_task;
        inner.tasks[current].kernel_time += inner.refresh_stop_watch();
    }

    /// 統計用戶時間,從現在開始算的是內核時間
    fn user_time_end(&self) {
        let mut inner = self.inner.exclusive_access();
        let current = inner.current_task;
        inner.tasks[current].user_time += inner.refresh_stop_watch();
    }
}

其中用到的refresh_stop_watch是給TaskManagerInner實現的方法,這裡不給TaskManager實現這個方法而是給TaskManagerInner實現這個方法的原因是,因為記錄的時候往往要拿到inner的所有權,才能獲得當前任務信息,如果是給這時候如果是TaskManager的方法,那麼肯定會需要再度獲取inner的所有權,會造成所有權處理的混亂,並且會增加時間計算不准確:

impl TaskManagerInner
{
    fn refresh_stop_watch(&mut self) -> usize
    {
        let start_time = self.stop_watch;
        self.stop_watch = get_time_us();
        self.stop_watch - start_time
    }
}

這裡封裝user_time_startuser_time_end兩個函數:

/// 獲取當前任務的內核態運行時間
pub fn user_time_start()
{
    TASK_MANAGER.user_time_start();
}

/// 獲取當前任務的用戶態運行時間
pub fn user_time_end()
{
    TASK_MANAGER.user_time_end();
}

同時在trap模塊中加入記錄時間的函數:

#[no_mangle]
/// handle an interrupt, exception, or system call from user space
pub fn trap_handler(cx: &mut TrapContext) -> &mut TrapContext {
    user_time_start();
    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) => {
            println!("[kernel] PageFault in application, kernel killed it.");
            exit_current_and_run_next();
        }
        Trap::Exception(Exception::IllegalInstruction) => {
            println!("[kernel] IllegalInstruction in application, kernel killed it.");
            exit_current_and_run_next();
        }
        Trap::Interrupt(Interrupt::SupervisorTimer) => {
            println!("\nTimer interrupt,time slice used up!");
            set_next_trigger();
            println!("Next timer set!");
            suspend_current_and_run_next();
        }
        _ => {
            panic!(
                "Unsupported trap {:?}, stval = {:#x}!",
                scause.cause(),
                stval
            );
        }
    }
    user_time_end();
    cx
}

這時候執行make run:

[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______       __    __      _______.___________.  _______..______   __
|   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
|  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
|      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
|  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
| _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
[rustsbi] Implementation     : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name      : riscv-virtio,qemu
[rustsbi] Platform SMP       : 1
[rustsbi] Platform Memory    : 0x80000000..0x88000000
[rustsbi] Boot HART          : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address   : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
[kernel] Hello, world!
[kernel] trap init end
power_3 [10000/200000]
power_3 [20000/200000]
power_3 [30000/200000]
power_3 [40000/200000]
power_3 [50000/200000]
power_3 [60000/200000]
power_3 [70000/200000]
power_3 [80000/200000]
power_3 [90000/200000]
power_3 [100000/200000]
power_3 [110000/200000]
power_3 [120000/200000]
power_3 [130000/200000]
power_3 [140000/200000]
power_3 [150000/200000]
power_3 [160000/200000]
power_3 [170000/200000]
power_3 [180000/200000]
power_3 [190000/200000]
power_3 [200000/200000]
3^200000 = 871008973(MOD 998244353)
Test power_3 OK!
[kernel] Application exited with code 0
Task 0 exited,kernel time:1926 user time:3263 

Task 0 exited

Found next task 1
power_7 [10000/160000]
power_7 [20000/160000]
power_7 [30000/160000]
power_7 [40000/160000]
power_7 [50000/160000]
power_7 [60000/160000]
power_7 [70000/160000]
power_7 [80000/160000]
power_7 [90000/160000]
power_7 [100000/160000]
power_7 [110000/160000]
power_7 [120000/160000]
power_7 [130000/160000]
power_7 [140000/160000]
power_7 [150000/160000]
power_7 [160000/160000]
7^160000 = 667897727(MOD 998244353)
Test power_7 OK!
[kernel] Application exited with code 0
Task 1 exited,kernel time:1065 user time:1955 

Task 1 exited

Found next task 2
power_7 [10000/160000]
power_7 [20000/160000]
power_7 [30000/160000]
power_7 [40000/160000]
power_7 [50000/160000]

Timer interrupt,time slice used up!
Next timer set!

Task 2 suspended

Found next task 3

Task 3 suspended

Found next task 2

Task 3 has been switched out
power_7 [60000/160000]
power_7 [70000/160000]
power_7 [80000/160000]
power_7 [90000/160000]
power_7 [100000/160000]
power_7 [110000/160000]
power_7 [120000/160000]
power_7 [130000/160000]
power_7 [140000/160000]
power_7 [150000/160000]
power_7 [160000/160000]
7^160000 = 667897727(MOD 998244353)
Test power_7 OK!
[kernel] Application exited with code 0
Task 2 exited,kernel time:1009 user time:2060 

Task 2 exited

Found next task 3

Task 2 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out
Test sleep OK!
[kernel] Application exited with code 0
Task 3 exited,kernel time:83 user time:355 

Task 3 exited
All applications completed!

第三題

編寫浮點應用程式A,並擴展內核,支持面向浮點應用的正常切換與搶占。

也許這就是緣分吧,今天剛看到這個視頻.

這讓我想起之前硬漢說的有關於MCU 原子操作和臨界段保護 的問題.

那麼我們知道的是浮點應用計算是分為好幾步的,而不是原子操作,也就是進行任務切換的時候可能會存在浮點數計算計算到一半的情況.

那麼回想之前的做法,我們需要進行保存上下文的操作.

首先可以查閱技術手冊.理解RISC-V的浮點數計算相關知識.

RV32F 和 RV32D 的浮點寄存器。單精度寄存器占用了 32 個雙精度寄存器中最右邊的一半。

可一看到這裡的寄存器分為了:

  1. 浮點臨時寄存器
  2. 浮點保存寄存器
  3. 浮點參數和返回值寄存器

查閱技術手冊:
RISC-V 有足夠多的寄存器來達到兩全其美的結果:既能將操作數存放在寄存器中,同 時也能減少保存和恢復寄存器的次數。其中的關鍵在於,在函數調用的過程中不保留部分寄 存器存儲的值,稱它們為臨時寄存器;另一些寄存器則對應地稱為保存寄存器。不再調用其 它函數的函數稱為葉函數。當一個葉函數只有少量的參數和局部變數時,它們可以都被存儲 在寄存器中,而不會“溢出(spilling)”到記憶體中。但如果函數參數和局部變數很多,程式 還是需要把寄存器的值保存在記憶體中,不過這種情況並不多見。
函數調用中其它的寄存器,要麼被當做保存寄存器來使用,在函數調用前後值不變;要 麽被當做臨時寄存器使用,在函數調用中不保留。函數會更改用來保存返回值的寄存器,因 此它們和臨時寄存器類似;用來給函數傳遞參數的寄存器也不需要保留,因此它們也類似於 臨時寄存器。對於其它一些寄存器,調用者需要保證它們在函數調用前後保持不變:比如用 於存儲返回地址的寄存器和存儲棧指針的寄存器。

這張圖列出了寄存器的 RISC-V 應用程式 二進位介面(ABI)名稱和它們在函數調用中是否保留的規定。

這裡我思考了是不是臨時寄存器就不需要保存上下文,而保存寄存器就需要保存上下文這個問題,但是回頭一想,在trap.S里我們選擇了保存x0~x31(一部分特殊的寄存器暫時不用保存),這裡吧也是有臨時寄存器和保存寄存器的.

那麼都需要保存了.查閱技術手冊:

  • fsd:這個指令通常用來存儲浮點數。它的全稱可以是“float store double”,但確切的名字取決於具體的架構。fsd 指令會將一個浮點寄存器中的值存儲到指定的記憶體地址上。例如,在MIPS架構中,fsd 會將一個雙精度浮點數從浮點寄存器寫入記憶體。
  • fld:這個指令通常用於載入浮點數。它的全稱可以是“float load double”。fld 指令從記憶體地址讀取一個雙精度浮點數值,並將其載入到指定的浮點寄存器中。這同樣是在某些架構如MIPS中使用的命令。

這時候的trap.S

.altmacro
.macro SAVE_GP n
    sd x\n, \n*8(sp)
.endm
.macro LOAD_GP n
    ld x\n, \n*8(sp)
.endm
    .section .text
    .globl __alltraps
    .globl __restore
    .align 2
__alltraps:
    csrrw sp, sscratch, sp
    # now sp->kernel stack, sscratch->user stack
    # allocate a TrapContext on kernel stack
    addi sp, sp, -34*8
    # save general-purpose registers
    sd x1, 1*8(sp)
    # skip sp(x2), we will save it later
    sd x3, 3*8(sp)
    # skip tp(x4), application does not use it
    # save x5~x31
    .set n, 5
    .rept 27
        SAVE_GP %n
        .set n, n+1
    .endr
    # we can use t0/t1/t2 freely, because they were saved on kernel stack
    csrr t0, sstatus
    csrr t1, sepc
    sd t0, 32*8(sp)
    sd t1, 33*8(sp)
    # read user stack from sscratch and save it on the kernel stack
    csrr t2, sscratch
    # 浮點寄存器
    fsd f0, 34*8(sp)
    fsd f1, 35*8(sp)
    fsd f2, 36*8(sp)
    fsd f3, 37*8(sp)
    fsd f4, 38*8(sp)
    fsd f5, 39*8(sp)
    fsd f6, 40*8(sp)
    fsd f7, 41*8(sp)
    fsd f8, 42*8(sp)
    fsd f9, 43*8(sp)
    fsd f10, 44*8(sp)
    fsd f11, 45*8(sp)
    fsd f12, 46*8(sp)
    fsd f13, 47*8(sp)
    fsd f14, 48*8(sp)
    fsd f15, 49*8(sp)
    fsd f16, 50*8(sp)
    fsd f17, 51*8(sp)
    fsd f18, 52*8(sp)
    fsd f19, 53*8(sp)
    fsd f20, 54*8(sp)
    fsd f21, 55*8(sp)
    fsd f22, 56*8(sp)
    fsd f23, 57*8(sp)
    fsd f24, 58*8(sp)
    fsd f25, 59*8(sp)
    fsd f26, 60*8(sp)
    fsd f27, 61*8(sp)
    fsd f28, 62*8(sp)
    fsd f29, 63*8(sp)
    fsd f30, 64*8(sp)
    fsd f31, 65*8(sp)

    sd t2, 2*8(sp)
    # set input argument of trap_handler(cx: &mut TrapContext)
    mv a0, sp
    call trap_handler

__restore:
    # case1: start running app by __restore
    # case2: back to U after handling trap
    # now sp->kernel stack(after allocated), sscratch->user stack
    # restore sstatus/sepc
    ld t0, 32*8(sp)
    ld t1, 33*8(sp)
    ld t2, 2*8(sp)
    csrw sstatus, t0
    csrw sepc, t1
    csrw sscratch, t2
    # restore general-purpuse registers except sp/tp
    ld x1, 1*8(sp)
    ld x3, 3*8(sp)
    .set n, 5
    .rept 27
        LOAD_GP %n
        .set n, n+1
    .endr
    # 浮點寄存器
    fld f0, 34*8(sp)
    fld f1, 35*8(sp)
    fld f2, 36*8(sp)
    fld f3, 37*8(sp)
    fld f4, 38*8(sp)
    fld f5, 39*8(sp)
    fld f6, 40*8(sp)
    fld f7, 41*8(sp)
    fld f8, 42*8(sp)
    fld f9, 43*8(sp)
    fld f10, 44*8(sp)
    fld f11, 45*8(sp)
    fld f12, 46*8(sp)
    fld f13, 47*8(sp)
    fld f14, 48*8(sp)
    fld f15, 49*8(sp)
    fld f16, 50*8(sp)
    fld f17, 51*8(sp)
    fld f18, 52*8(sp)
    fld f19, 53*8(sp)
    fld f20, 54*8(sp)
    fld f21, 55*8(sp)
    fld f22, 56*8(sp)
    fld f23, 57*8(sp)
    fld f24, 58*8(sp)
    fld f25, 59*8(sp)
    fld f26, 60*8(sp)
    fld f27, 61*8(sp)
    fld f28, 62*8(sp)
    fld f29, 63*8(sp)
    fld f30, 64*8(sp)
    fld f31, 65*8(sp)

    # release TrapContext on kernel stack
    addi sp, sp, 34*8
    # now sp->kernel stack, sscratch->user stack
    csrrw sp, sscratch, sp
    sret

但是這時候參閱練習參考答案,發現仍有不周到之處.

此外,支持浮點指令可能還需要(包括但不限於)以下條件:

  • 機器本身支持浮點指令
  • Rust 編譯目標包含浮點指令
    • 在 os/Makefile 中的 TARGET := riscv64gc-unknown-none-elf 支持浮點指令,而對應的 riscv64imac 則不支持。
    • 如果機器本身支持但使用riscv64imac作為編譯目標,仍然可以通過強行插入指令的方式來支持浮點,如 fld fs0, 280(sp) 在 RISCV 指令集中表示為機器碼 0x2472 ,就可以在上面的 trap.S 中插入
      .short 0x2472 # fld fs0, 280(sp)
      來支持浮點指令
  • 需要通過控制浮點控制狀態寄存器(如 fcsr)來檢查FPU狀態。詳見 https://five-embeddev.com/riscv-isa-manual/latest/machine.html#machine-trap-vector-base-address-register-mtvec

這時候就需要參閱linux或rCore的源碼了.

在rCore的倉庫中發現了這個:rcore-os/trapframe-rs: Handle TrapFrame across kernel and user space on multiple ISAs. (github.com),但是它似乎也沒有對浮點寄存器進行保存.

那麼這時候需要看Linux的上下文保存了.

我們看到在Linux源碼中似乎也沒有進行保存.

那麼其實對於我的能力而言,這裡的內容有兩個部分沒有完成:

  1. 實際設計一個任務來測試保存和不保存浮點寄存器的區別
  2. 沒有找到Linux浮點寄存器保存相關的內容

第四題

編寫應用程式或擴展內核,能夠統計任務切換的大致開銷

這個其實上來是一臉懵的,首先先思考一下任務切換開銷到底開銷在哪了:

  1. 對於CPU時間的占用
  2. 對於記憶體的占用

這題其實是沒什麼頭緒的,但是看了答案豁然開朗,只需要統計__switch耗時即可.

這裡註意,是所有的開銷,而不是單純的一個的開銷

但是仔細看答案的實現,參考hangx-ma在評論區的評論和他和Mars在評論區的討論:

/// 切換的開始時間
static mut SWITCH_TIME_START: usize = 0;
/// 切換的總時間
static mut SWITCH_TIME_COUNT: usize = 0;

unsafe fn __switch(current_task_cx_ptr: *mut TaskContext, next_task_cx_ptr: *const TaskContext) {
    SWITCH_TIME_START = get_time_us();
    switch::__switch(current_task_cx_ptr, next_task_cx_ptr);
    SWITCH_TIME_COUNT += get_time_us() - SWITCH_TIME_START;
}

fn get_switch_time_count() -> usize {
    unsafe { SWITCH_TIME_COUNT }
}

第一次啟動的任務,後續會轉到__restore函數,這樣就會導致函數爛尾,就是一個反常識的情況,但是如果用彙編的方式來理解就很好理解了,__restore會直接回到用戶態,這時候後邊的代碼就被跳過了.

回顧trap.S:

__alltraps:
	... ...
    call trap_handler

__restore:
	 ... ...
    sret

trap_handler的內容中函數的內容更換為:

__alltraps:
	... ...
    # 一些代碼邏輯
goto __restore # 實際上RISCV沒有goto這個控制轉移指令
	# 後續的代碼邏輯

__restore:
	 ... ...
    sret

這裡一定要特別註意一點,這是彙編,也就是__alltraps不會執行到call trap_handler就停止了,它會一直執行__restoresret.

最後在完整回顧一下trap流程:

引用評論區的討論

  1. 進入執行switch::__switch這個rust函數時, ra 寄存器會被設置為 當前指令的下一個指令,所以在這個函數執行時, switch::__switch 的下一個指令的地址會被存儲在當前task A 的TaskContext.ra 中,
  2. 當執行 switch::__switch 的 ret 指令時, pc 寄存器會設置為 task B 的 TaskContext.ra, 此時 task B 的 TaskContext.ra 有且只能取 兩個值中的一個:1. 如果 task B 是初次運行,那麼task B 的 TaskContext.ra 的值應為 __restore, 然後回到用戶態,從頭開始執行 task B, 註意,這個 task switch 時間是沒有被記錄的; 2. 如果 task B 不是初次運行, 那麼 task B 的 TaskContext.ra 的值應為 switch::__switch 的下一條指令的地址, 所以執行 ret 指令後 會執行  SWITCH_TIME_COUNT += get_time_us() - SWITCH_TIME_START; , 所以, 這個 task switch 時間被記錄下來了
  3. 基於以上 2 點, 我認為官方對 task switch 時間的統計方法沒有問題,只是缺少了一部分

這樣就很好理解了,在官方解答的基礎上,只需要添加一個在__restore後進行時間統計的功能就行了.

做出如下修改:

// os/src/task/mod.rs

/// 切換的開始時間
pub static mut SWITCH_TIME_START: usize = 0;
/// 切換的總時間
pub static mut SWITCH_TIME_COUNT: usize = 0;

unsafe fn __switch(current_task_cx_ptr: *mut TaskContext, next_task_cx_ptr: *const TaskContext) {
    SWITCH_TIME_START = get_time_us();
    switch::__switch(current_task_cx_ptr, next_task_cx_ptr);
    SWITCH_TIME_COUNT += get_time_us() - SWITCH_TIME_START;
}

fn get_switch_time_count() -> usize {
    unsafe { SWITCH_TIME_COUNT }
}

impl TaskManager
{
	 fn run_next_task(&self) 
    {
        if let Some(next) = self.find_next_task()
        {
			.... ...
        }
        else
        {
            println!("All applications completed!");
            println!("task switch time: {} us", get_switch_time_count());
            shutdown(false);
        }
    }
}
// os/src/task/context.rs

impl TaskContext {
... ...
    /// set task context {__restore ASM funciton, kernel stack, s_0..12 }
    pub fn goto_restore(kstack_ptr: usize) -> Self {
        extern "C" {
            fn __pre_restore();
        }
        Self {
            ra: __pre_restore as usize,
            sp: kstack_ptr,
            s: [0; 12],
        }
    }
}
// os/src/trap/mod.rs

/// 計算時間
#[no_mangle]
pub unsafe extern "C" fn switch_cost(cx: &mut TrapContext) -> &mut TrapContext {
    SWITCH_TIME_COUNT += get_time_us() - SWITCH_TIME_START;
    cx
}
// os/src/trap/trap.S
... ...
__pre_restore:
    mv a0, sp
    call switch_cost
    mv sp, a0
    j __restore

隨後make run:

[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______       __    __      _______.___________.  _______..______   __
|   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
|  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
|      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
|  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
| _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
[rustsbi] Implementation     : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name      : riscv-virtio,qemu
[rustsbi] Platform SMP       : 1
[rustsbi] Platform Memory    : 0x80000000..0x88000000
[rustsbi] Boot HART          : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address   : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
[kernel] Hello, world!
[kernel] trap init end
power_3 [10000/200000]
power_3 [20000/200000]
power_3 [30000/200000]
power_3 [40000/200000]
power_3 [50000/200000]
power_3 [60000/200000]
power_3 [70000/200000]
power_3 [80000/200000]
power_3 [90000/200000]
power_3 [100000/200000]
power_3 [110000/200000]
power_3 [120000/200000]
power_3 [130000/200000]
power_3 [140000/200000]
power_3 [150000/200000]
power_3 [160000/200000]
power_3 [170000/200000]
power_3 [180000/200000]
power_3 [190000/200000]
power_3 [200000/200000]
3^200000 = 871008973(MOD 998244353)
Test power_3 OK!
[kernel] Application exited with code 0
Task 0 exited,kernel time:1635 user time:3015 

Task 0 exited

Found next task 1
power_7 [10000/160000]
power_7 [20000/160000]
power_7 [30000/160000]
power_7 [40000/160000]
power_7 [50000/160000]
power_7 [60000/160000]
power_7 [70000/160000]
power_7 [80000/160000]
power_7 [90000/160000]
power_7 [100000/160000]
power_7 [110000/160000]
power_7 [120000/160000]
power_7 [130000/160000]
power_7 [140000/160000]
power_7 [150000/160000]
power_7 [160000/160000]
7^160000 = 667897727(MOD 998244353)
Test power_7 OK!
[kernel] Application exited with code 0
Task 1 exited,kernel time:955 user time:1320 

Task 1 exited

Found next task 2
power_7 [10000/160000]
power_7 [20000/160000]
power_7 [30000/160000]
power_7 [40000/160000]
power_7 [50000/160000]
power_7 [60000/160000]
power_7 [70000/160000]
power_7 [80000/160000]
power_7 [90000/160000]
power_7 [100000/160000]
power_7 [110000/160000]
power_7 [120000/160000]
power_7 [130000/160000]
power_7 [140000/160000]
power_7 [150000/160000]
power_7 [160000/160000]
7^160000 = 667897727(MOD 998244353)
Test power_7 OK!
[kernel] Application exited with code 0
Task 2 exited,kernel time:923 user time:1449 

Task 2 exited

Found next task 3

Timer interrupt,time slice used up!
Next timer set!

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out

Task 3 suspended

Found next task 3

Task 3 has been switched out
Test sleep OK!
[kernel] Application exited with code 0
Task 3 exited,kernel time:104 user time:1506 

Task 3 exited
All applications completed!
task switch time: 35 us

第五題

這題對應了問答題的第六題,在sie設置為1的時候實際上內核態還是可以接收到中斷的.

參考答案似乎說的很有道理,但是和第三題一樣實際上:

  1. 沒有對應測試APP
  2. 最後也是開放性答案,沒有講清楚具體怎麼解決問題

#TODO

第六題

#TODO 似乎是一個看起來很簡單但是執行起來很難的問題.

問答題

第一題

協作式調度與搶占式調度的區別是什麼?

協作式調度是經由APP本身在執行耗時操作的時候主動執行yield釋放CPU.

搶占式調度是除了APP本身主動放棄CPU以外,使用定時器中斷每隔一個時間片強制切換任務.

第二題

中斷、異常和系統調用有何異同之處?

中斷和異常都是Trap,而系統調用是一種異常.就是Environment call from U-mode這個異常.

第三題

RISC-V支持哪些中斷/異常?

這個第二章作業題其實已經解釋過了, 我直接把圖貼過來.

但是這裡發現Exception Code有很多是空著的.

這裡直接看riscv-privilegedSupervisor cause register (scause)的描述,它剛好記載了S特權級所有進入Trap的可能情況.

第四題

如何判斷進入操作系統內核的起因是由於中斷還是異常?

這裡想到關於trap_handler的代碼:

#[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(); // 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) => {
            println!("[kernel] PageFault in application, bad addr = {:#x}, bad instruction = {:#x}, kernel killed it.", stval, cx.sepc);
            exit_current_and_run_next();
        }
        Trap::Exception(Exception::IllegalInstruction) => {
            println!("[kernel] IllegalInstruction in application, kernel killed it.");
            exit_current_and_run_next();
        }
        Trap::Interrupt(Interrupt::SupervisorTimer) => {
            set_next_trigger();
            suspend_current_and_run_next();
        }
        _ => {
            panic!(
                "Unsupported trap {:?}, stval = {:#x}!",
                scause.cause(),
                stval
            );
        }
    }
    cx
}

可以看到是處理了scause.cause,應該是scause寄存器的cause位.

上一題 對應起來就可以理解了.

第五題

在 RISC-V 中斷機制中,PLIC 和 CLINT 各起到了什麼作用?

這裡在我們已有的參考書里搜都找不到.

因此使用一個trick,這樣搜索.得到一本關於PLIC參考手冊.

同樣的方法得到一本關於 CLINT參考手冊.

那麼要回答這個問題只需要看手冊的 Introduction 部分就行了.

關於PLIC:
This document contains the RISC-V platform-level interrupt controller (PLIC) specification (was removed from RISC-V Privileged Spec v1.11-draft) , which defines an interrupt controller specifically designed to work in the context of RISC-V systems. The PLIC multiplexes various device interrupts onto the external interrupt lines of Hart contexts, with hardware support for interrupt priorities. This specification defines the general PLIC architecture and the operation parameters. PLIC supports up-to 1023 interrupts (0 is reserved) and 15872 contexts, but the actual number of interrupts and context depends on the PLIC implementation. However, the implement must adhere to the offset of each register within the PLIC operation parameters. The PLIC which claimed as PLIC-Compliant standard PLIC should follow the implementations mentioned in sections below.

機翻:
這份文檔包含了 RISC-V 平臺級中斷控制器 (PLIC) 的規格說明(該規格曾從 RISC-V 特權規格 v1.11 草案中移除),它定義了一個專門為 RISC-V 系統設計的中斷控制器。PLIC 將各種設備中斷復用到 Hart 上下文的外部中斷線上,並提供了對中斷優先順序的硬體支持。本規格說明定義了通用的 PLIC 架構和操作參數。PLIC 支持最多 1023 個中斷(0 保留不用)和 15872 個上下文,但實際的中斷數量和上下文數量取決於 PLIC 的具體實現。然而,實現必須遵循 PLIC 操作參數中每個寄存器的偏移量。聲稱符合 PLIC 標準的 PLIC 應當遵循下文中提到的實現。

關於CLINT:
This RISC-V ACLINT specification defines a set of memory mapped devices which provide interprocessor interrupts (IPI) and timer functionalities for each HART on a multi-HART RISC-V platform. These HART-level IPI and timer functionalities are required by operating systems, bootloaders and firmwares running on a multi-HART RISC-V platform. The SiFive Core-Local Interruptor (CLINT) device has been widely adopted in the RISC-V world to provide machine-level IPI and timer functionalities. Unfortunately, the SiFive CLINT has a unified register map for both IPI and timer functionalities and it does not provide supervisor-level IPI functionality. The RISC-V ACLINT specification takes a more modular approach by defining separate memory mapped devices for IPI and timer functionalities. This modularity allows RISC-V platforms to omit some of the RISC-V ACLINT devices for when the platform has an alternate mechanism. In addition to modularity, the RISC-V ACLINT specification also defines a dedicated memory mapped device for supervisor-level IPIs. The Table 1 below shows the list of devices defined by the RISC-V ACLINT specification.

機翻:
RISC-V ACLINT 規格說明定義了一組記憶體映射設備,這些設備為多 Hart RISC-V 平臺上每個 Hart 提供了處理器間中斷 (IPI) 和定時器功能。這些 Hart 級別的 IPI 和定時器功能是多 Hart RISC-V 平臺上運行的操作系統、引導載入程式和固件所必需的。SiFive 的 Core-Local Interruptor (CLINT) 設備已在 RISC-V 領域被廣泛採用,用於提供機器級別的 IPI 和定時器功能。不幸的是,SiFive CLINT 設備具有統一的寄存器映射,既用於 IPI 又用於定時器功能,並且不提供監督級別 (supervisor-level) 的 IPI 功能。RISC-V ACLINT 規格說明採取了更為模塊化的方法,通過定義獨立的記憶體映射設備來分別提供 IPI 和定時器功能。這種模塊化允許 RISC-V 平臺省略某些 RISC-V ACLINT 設備,當平臺有替代機制時。除了模塊化之外,RISC-V ACLINT 規格說明還定義了一個專門的記憶體映射設備用於監督級別的 IPI 功能。下表 1 顯示了 RISC-V ACLINT 規格說明定義的設備列表。

總結:
CLINT 處理時鐘中斷 (MTI) 和核間的軟體中斷 (MSI);PLIC 處理外部來源的中斷 (MEI)。

第六題

基於RISC-V 的操作系統支持中斷嵌套?請給出進一步的解釋說明。

~~這裡根據本章學到的知識,得出的結論應該是支持中斷嵌套. ~~

模糊的記憶是:當RISC-V接收到中斷後會修改對應寄存器的值以屏蔽和此中斷同等級和低等級的所有中斷.

RISC-V原生不支持中斷嵌套。(在S態的內核中)只有 sstatus 的 SIE 位為 1 時,才會開啟中斷,再由 sie 寄存器控制哪些中斷可以觸發。觸發中斷時,sstatus.SPIE 置為 sstatus.SIE,而 sstatus.SIE 置為0;當執行 sret 時,sstatus.SIE置為 sstatus.SPIE,而 sstatus.SPIE 置為1。這意味著觸發中斷時,因為 sstatus.SIE 為0,所以無法再次觸發中斷。

這裡不知道是不是因為提了 操作系統 因此不考慮M態的中斷會出現在操作系統中,因此是不支持中斷嵌套的.

第七題

本章提出的任務的概念與前面提到的進程的概念之間有何區別與聯繫?

說實話 前面似乎沒有提到進程的概念 .

感覺需要等到第五章. #TODO

這裡直接抄答案了:

  • 聯繫:任務和進程都有自己獨立的棧、上下文信息,任務是進程的“原始版本”,在第五章會將目前的用戶程式從任務升級為進程。
  • 區別:任務之間沒有地址空間隔離,實際上是能相互訪問到的;進程之間有地址空間隔離,一個進程無法訪問到另一個進程的地址。

第八題

簡單描述一下任務的地址空間中有哪些類型的數據和代碼。

可參照 user/src/linker.ld

  • .text:任務的代碼段,其中開頭的 .text.entry 段包含任務的入口地址
  • .rodata:只讀數據,包含字元串常量,如測例中的 println!("Test power_3 OK!"); 實際列印的字元串存在這裡
  • .data:需要初始化的全局變數
  • .bss:未初始化或初始為0的全局變數。

除此之外,在內核中為每個任務構造的用戶棧 os/src/loader.rs:USER_STACK也屬於各自任務的地址。

第九題

任務控制塊保存哪些內容?

這個直接照抄我們之前畫的圖就行了.

看最後的TaskControlBlock的結構.

第十題

任務上下文切換需要保存與恢復哪些內容?

這個同第九題,直接看TaskContext的內容.

第十一題

特權級上下文和任務上下文有何異同?

任務上下文的內容同第九題即可,特權級上下文的內容直接照抄第二章的內容.

// os/src/trap/context.rs

#[repr(C)]
pub struct TrapContext {
    pub x: [usize; 32],
    pub sstatus: Sstatus,
    pub sepc: usize,
}

可以看到TaskContextTrapContext都保存了一些寄存器內容,而TrapContext保存了更多的寄存器.

特權級上下文切換可以

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在軟體行業,經常會聽到一句話“文不如表,表不如圖”說明瞭圖形在軟體應用中的重要性。同樣在WPF開發中,為了程式美觀或者業務需要,經常會用到各種個樣的圖形。今天以一些簡單的小例子,簡述WPF開發中幾何圖形(Geometry)相關內容,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 之前寫過兩篇關於Roslyn源生成器生成源代碼的用例,今天使用Roslyn的代碼修複器CodeFixProvider實現一個cs文件頭部註釋的功能, 代碼修複器會同時涉及到CodeFixProvider和DiagnosticAnalyzer, 實現FileHeaderAnalyzer 首先我們知道修 ...
  • 本文為大家介紹下.NET解壓/壓縮zip文件。雖然解壓縮不是啥核心技術,但壓縮性能以及進度處理還是需要關註下,針對使用較多的zip開源組件驗證,給大家提供個技術選型參考 之前在《.NET WebSocket高併發通信阻塞問題 - 唐宋元明清2188 - 博客園 (cnblogs.com)》講過,團隊 ...
  • 1. 生成式 AI 簡介 https://imp.i384100.net/LXYmq3 2. Python 語言 https://imp.i384100.net/5gmXXo 3. 統計和 R https://youtu.be/ANMuuq502rE?si=hw9GT6JVzMhRvBbF 4. 數 ...
  • 先看一下效果吧: 我們直接通過改造一下原版的TreeView來實現上面這個效果 我們先創建一個普通的TreeView 代碼很簡單: <TreeView> <TreeViewItem Header="人事部"/> <TreeViewItem Header="技術部"> <TreeViewItem He ...
  • 前言 推薦一款基於.NET 8、WPF、Prism.DryIoc、MVVM設計模式、Blazor以及MySQL資料庫構建的企業級工作流系統的WPF客戶端框架-AIStudio.Wpf.AClient 6.0。 項目介紹 框架採用了 Prism 框架來實現 MVVM 模式,不僅簡化了 MVVM 的典型 ...
  • docker安裝普羅米修斯+Granfan並監控容器 一、基本概念 ​ 1、之間的關係 ​ prometheus與grafana之間是相輔相成的關係。作為完美的分散式監控系統的Prometheus,就想布加迪威龍一樣示例和動力強勁。在猛的車也少不了儀錶盤來觀察。於是優雅的可視化平臺Grafana出現 ...
  • 大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是JLink命令行以及JFlash對於下載演算法的作用地址範圍認定。 最近痞子衡在給一個 RT1170 客戶定製一個 Infineon MirrorBit 類型 64MB Flash 的 SEGGER 下載演算法,做完之後在 JFlash 下 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...