rCore-Tutorial-Book-v3 icon indicating copy to clipboard operation
rCore-Tutorial-Book-v3 copied to clipboard

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

Open utterances-bot opened this issue 3 years ago • 23 comments

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

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

utterances-bot avatar Jul 15 '22 08:07 utterances-bot

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

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

kkocdko avatar Jul 15 '22 08:07 kkocdko

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跳完栈切换完指令后,仍能完成增加计时的运算?

TD-Sky avatar Mar 15 '23 09:03 TD-Sky

2.4 第三点的描述感觉不对

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

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

kayoch1n avatar May 06 '23 08:05 kayoch1n

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

wyfcyx avatar May 06 '23 17:05 wyfcyx

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

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

kayoch1n avatar May 08 '23 08:05 kayoch1n

关于第三个问题支持浮点运算,我觉得可以提一下需要事先将 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.

kayoch1n avatar May 09 '23 02:05 kayoch1n

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

HangX-Ma avatar Jul 03 '23 08:07 HangX-Ma

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

OccupyMars2025 avatar Jul 16 '23 13:07 OccupyMars2025

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

OccupyMars2025 avatar Jul 16 '23 13:07 OccupyMars2025

"编程题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 avatar Jul 16 '23 14:07 OccupyMars2025

  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 avatar Jul 16 '23 17:07 OccupyMars2025

@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的时间

OccupyMars2025 avatar Jul 16 '23 17:07 OccupyMars2025

@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的记录,这点确实有问题。

HangX-Ma avatar Jul 16 '23 17:07 HangX-Ma

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

OccupyMars2025 avatar Jul 16 '23 18:07 OccupyMars2025

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

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

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

HangX-Ma avatar Jul 16 '23 18:07 HangX-Ma

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

qinmz avatar Nov 02 '23 03:11 qinmz

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

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

HangX-Ma avatar Nov 02 '23 03:11 HangX-Ma

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

TL-SN avatar Jan 10 '24 14:01 TL-SN