寫在前面 本隨筆是非常菜的菜雞寫的。如有問題請及時提出。 可以聯繫:[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_suspended
和mark_current_exited
中加停表呢?:
如果發生任務切換,那麼更複雜一些:
這時候應該看到,我們應該在__swtich
前後刷新停表,並且記錄時間.
這是時候修改TaskManager
的方法,這裡refresh_stop_watch
的位置沒有加到mark_current_suspended
和mark_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_start
和user_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 個雙精度寄存器中最右邊的一半。
可一看到這裡的寄存器分為了:
- 浮點臨時寄存器
- 浮點保存寄存器
- 浮點參數和返回值寄存器
查閱技術手冊:
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源碼中似乎也沒有進行保存.
那麼其實對於我的能力而言,這裡的內容有兩個部分沒有完成:
- 實際設計一個任務來測試保存和不保存浮點寄存器的區別
- 沒有找到Linux浮點寄存器保存相關的內容
第四題
編寫應用程式或擴展內核,能夠統計任務切換的大致開銷
這個其實上來是一臉懵的,首先先思考一下任務切換開銷到底開銷在哪了:
- 對於CPU時間的占用
- 對於記憶體的占用
這題其實是沒什麼頭緒的,但是看了答案豁然開朗,只需要統計__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
就停止了,它會一直執行__restore
到sret
.
最後在完整回顧一下trap
流程:
引用評論區的討論
- 進入執行switch::__switch這個rust函數時, ra 寄存器會被設置為 當前指令的下一個指令,所以在這個函數執行時, switch::__switch 的下一個指令的地址會被存儲在當前task A 的TaskContext.ra 中,
- 當執行 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 時間被記錄下來了 - 基於以上 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的時候實際上內核態還是可以接收到中斷的.
參考答案似乎說的很有道理,但是和第三題一樣實際上:
- 沒有對應測試APP
- 最後也是開放性答案,沒有講清楚具體怎麼解決問題
#TODO
第六題
#TODO
似乎是一個看起來很簡單但是執行起來很難的問題.
問答題
第一題
協作式調度與搶占式調度的區別是什麼?
協作式調度是經由APP本身在執行耗時操作的時候主動執行yield
釋放CPU.
搶占式調度是除了APP本身主動放棄CPU以外,使用定時器中斷每隔一個時間片強制切換任務.
第二題
中斷、異常和系統調用有何異同之處?
中斷和異常都是Trap,而系統調用是一種異常.就是Environment call from U-mode
這個異常.
第三題
RISC-V支持哪些中斷/異常?
這個第二章作業題其實已經解釋過了, 我直接把圖貼過來.
但是這裡發現Exception Code
有很多是空著的.
這裡直接看riscv-privileged對Supervisor 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,
}
可以看到TaskContext
和TrapContext
都保存了一些寄存器內容,而TrapContext
保存了更多的寄存器.
特權級上下文切換可以