rcore-os / rCore-Tutorial-Book-v3

A book about how to write OS kernels in Rust easily.
https://rcore-os.github.io/rCore-Tutorial-Book-v3/
GNU General Public License v3.0
1.13k stars 211 forks source link

rCore-Tutorial-Book-v3/chapter3/6answer #141

Open utterances-bot opened 2 years ago

utterances-bot commented 2 years ago

练习参考答案 - rCore-Tutorial-Book-v3 3.6.0-alpha.1 文档

https://rcore-os.github.io/rCore-Tutorial-Book-v3/chapter3/6answer.html

kkocdko commented 2 years ago

编程题 3. 编写浮点应用程序A

请问什么是“浮点应用程序”呢?是类似 ./user/src/bin/01power_5.rs 这样,然后随意计算一个浮点运算任务么?

TD-Sky commented 1 year ago
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;
}

编程题4的此处,为什么__switch跳完栈切换完指令后,仍能完成增加计时的运算?

kayoch1n commented 1 year ago

2.4 第三点的描述感觉不对

通过时钟中断切换任务: ... 切换到新任务后,在 trap 结尾处遇到函数 user_time_start(),刷新停表,并统计新任务的内核态时间

按照 ch3 的实现,__switch 函数通过直接恢复ra来切到新任务,不会返回到trap的结尾

wyfcyx commented 1 year ago

@kayoch1n 没问题呀,假设是两个任务轮流执行的情况,一个任务时间片用尽进入trap,调用suspend_current_and_run_next,最终调用__switch切换到另一个任务。另一个任务时间片也用尽,通过__switch切换回来的时候,要切换回的ra其实是调用__switch的指令的下一条指令,所以会从__switch返回的地方继续执行,最后走到trap结尾。再看看__switch对ra的处理吧。

kayoch1n commented 1 year ago

@kayoch1n 没问题呀,假设是两个任务轮流执行的情况,一个任务时间片用尽进入trap,调用suspend_current_and_run_next,最终调用__switch切换到另一个任务。另一个任务时间片也用尽,通过__switch切换回来的时候,要切换回的ra其实是调用__switch的指令的下一条指令,所以会从__switch返回的地方继续执行,最后走到trap结尾。再看看__switch对ra的处理吧。

ok了,我的代码有点问题,修改之后是可以返回并且走到trap结尾的

kayoch1n commented 1 year ago

关于第三个问题支持浮点运算,我觉得可以提一下需要事先将 sstatus.fs 设置为一个非0的值,否则尽管编译target包含了浮点指令拓展、程序首次执行浮点指令的时候会抛出illegal instruction异常。

这里3.1.6.6有提到这个点,而且我自己在qemu上试了一下确实会直接抛出异常,设置成1之后可解决这个首次执行异常的问题

When an extension’s status is set to Off, any instruction that attempts to read or write the corresponding state will cause an illegal instruction exception.

HangX-Ma commented 1 year ago
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;
}

编程题4的此处,为什么__switch跳完栈切换完指令后,仍能完成增加计时的运算?

他这里的答案有问题,__switch 之后会跳转到 __restore 恢复到用户态,后面那句只有下一次用户态 trap 后才会执行。 虽然不对我还是测试了一下, 发现 context switch 要花费几百毫秒, 这肯定是不可能的。 可行的办法是在 goto_restore 中更改入口地址:

这里我的测试可能是出了问题,见后续评论与Mars的讨论。

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],
    }
}

之后在 trap.S 末尾加上

__pre_restore:
    mv a0, sp
    call switch_cost
    mv sp, a0
    j __restore

计时的函数是

// os/src/task/mod.rs
pub static mut SWITCH_TASK_START: usize = 0;

pub unsafe fn __switch(current_task_cx_ptr: *mut TaskContext, next_task_cx_ptr: *const TaskContext) {
    SWITCH_TASK_START = get_time_us();
    switch::__switch(current_task_cx_ptr, next_task_cx_ptr);

crate::task::update_switch_cost(get_time_us() - SWITCH_TASK_START); 
}
...
pub fn update_switch_cost(cost: usize) {
    let mut inner = TASK_MANAGER.inner.exclusive_access();
    let current = inner.current_task;
    inner.tasks[current].switch_time += cost;
}

// os/src/trap/mod.rs
#[no_mangle]
pub unsafe extern "C" fn switch_cost (cx: &mut TrapContext) -> &mut TrapContext {
    crate::task::update_switch_cost(get_time_us() - SWITCH_TASK_START); 
    cx
}

TaskControlBlock 中要增加 pub switch_time: usize

OccupyMars2025 commented 1 year ago

https://github.com/OccupyMars2025/reimplement-rCore-Tutorial-v3-from-scratch/issues/4#issue-1806554017

OccupyMars2025 commented 1 year ago

"虽然不对我还是测试了一下, 发现 context switch 要花费几百毫秒, 这肯定是不可能的。 ", 我的测试结果: task switch time: 103 us

OccupyMars2025 commented 1 year ago

"编程题4的此处,为什么switch跳完栈切换完指令后,仍能完成增加计时的运算?" "他这里的答案有问题,switch 之后会跳转到 __restore 恢复到用户态,后面那句只有下一次用户态 trap 后才会执行。 虽然不对我还是测试了一下, 发现 context switch 要花费几百毫秒, 这肯定是不可能的。"

我是这样理解的:

// os/src/task/switch.rs
extern "C" {
    pub fn __switch(
        current_task_cx_ptr: *mut TaskContext,
        next_task_cx_ptr: *const TaskContext
    );
}
unsafe fn __switch(current_task_cx_ptr: *mut TaskContext, next_task_cx_ptr: *const TaskContext) {
    println!("\x1b[31m Start switching from current_task_cx_ptr={:#?} to next_task_cx_ptr={:#?}  \x1b[0m ", current_task_cx_ptr, next_task_cx_ptr);
    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;
    println!("\x1b[31m Switch back!!! current_task_cx_ptr={:#?}, next_task_cx_ptr={:#?}  \x1b[0m ", current_task_cx_ptr, next_task_cx_ptr);
}
  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 时间的统计方法没有问题,只是缺少了一部分
OccupyMars2025 commented 1 year ago
  1. 官方对 task switch 时间的统计方法只计算了 switch to suspended task 的时间
  2. HangX-Ma 对 task switch 时间的统计方法只计算了 switch to unrun task 的时间 所以我综合了这两种方法,给出如下解答: https://github.com/OccupyMars2025/reimplement-rCore-Tutorial-v3-from-scratch/commit/09f18069d5ab13f7c941648ef4cd6a6f611733bd
OccupyMars2025 commented 1 year ago

@HangX-Ma

  1. “此时本该记录context switch的开销”, 你的代码解决了这个问题,是没错,看我之前的回复
  2. “那么在你下一次执行__switch从taskB切换到taskA,记录COUNT的时候,实际上把taskB的运行时间也计算进去了。”, 1. 如果task B是 第一次运行,那么 task B 会从用户态trap, task B 会再次运行 SWITCH_TIME_START = get_time_us(); 这就更新了 SWITCH_TIME_START, 然后 switch 到 task A 去 运行 SWITCH_TIME_COUNT += get_time_us() - SWITCH_TIME_START;, 这就记录了 B switch to A 的时间; 2. 如果 task B 不是第一次运行,只是 suspend, 那么 A switch to B 后会直接运行 task B 的 SWITCH_TIME_COUNT += get_time_us() - SWITCH_TIME_START; , 这就记录了 A switch to B的时间
HangX-Ma commented 1 year ago

@HangX-Ma

  1. “此时本该记录context switch的开销”, 你的代码解决了这个问题,是没错,看我之前的回复

  2. “那么在你下一次执行__switch从taskB切换到taskA,记录COUNT的时候,实际上把taskB的运行时间也计算进去了。”, 1. 如果task B是 第一次运行,那么 task B 会从用户态trap, task B 会再次运行 SWITCH_TIME_START = get_time_us(); 这就更新了 SWITCH_TIME_START, 然后 switch 到 task A 去 运行 SWITCH_TIME_COUNT += get_time_us() - SWITCH_TIME_START;, 这就记录了 B switch to A 的时间; 2. 如果 task B 不是第一次运行,只是 suspend, 那么 A switch to B 后会直接运行 task B 的 SWITCH_TIME_COUNT += get_time_us() - SWITCH_TIME_START; , 这就记录了 A switch to B的时间

确实是会更新,回复完就想到了。我之前的代码少了对后续的switch的记录,这点确实有问题。

OccupyMars2025 commented 1 year ago

@HangX-Ma 你的学习笔记写的很好,我是对着你的学习笔记来学 rCore 的,如果我的实现有什么问题,欢迎直接到我的repo 下提 issue 或者 PR https://github.com/OccupyMars2025/reimplement-rCore-Tutorial-v3-from-scratch/commit/09f18069d5ab13f7c941648ef4cd6a6f611733bd

HangX-Ma commented 1 year ago

@HangX-Ma 你的学习笔记写的很好,我是对着你的学习笔记来学 rCore 的,如果我的实现有什么问题,欢迎直接到我的repo 下提 issue 或者 PR https://github.com/OccupyMars2025/reimplement-rCore-Tutorial-v3-from-scratch/commit/09f18069d5ab13f7c941648ef4cd6a6f611733bd

谢谢认可,笔记主要还是厘清思路。个人学习难免有所缺漏,多多讨论好处良多。

另外,虽然rCore仅有这本导读的评论区作为交流平台时效性差了些,但大家的留言都很有质量,收获很多。希望到时候官方能提供更好的交流平台。(错过了一年一度的训练营也能交流的那种hh)

qinmz commented 8 months ago

@HangX-Ma 您好,您的笔记是在什么平台分享的, 在进行学习rcore过程中,有些地方比较难看懂,希望看下你的笔记

HangX-Ma commented 8 months ago

@HangX-Ma 您好,您的笔记是在什么平台分享的, 在进行学习rcore过程中,有些地方比较难看懂,希望看下你的笔记

GitPage,我的github主页点开就看得到🤣不过后面两张比较草率写了框架没写个人想法,最近准备把rCore捡起来再看看。

TL-SN commented 6 months ago

雀氏,必须先把sstatus的fs字段设置为10 或 11 才能保存浮点寄存器 o^o