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.16k stars 218 forks source link

rCore-Tutorial-Book-v3/chapter2/5exercise #34

Open utterances-bot opened 3 years ago

utterances-bot commented 3 years ago

chapter2练习 — rCore-Tutorial-Book-v3 0.1 文档

https://rcore-os.github.io/rCore-Tutorial-Book-v3/chapter2/5exercise.html

lightbulb128 commented 3 years ago

当我把测例的 ch2_write1.rs 和 ch2t_write0.rs 引入 user/src/bin 后,在 os 目录执行 make run,编译器提示 use user_lib::{write, STDOUT}; 一行 no STDOUT in the root 考虑到 STDOUT = 1 是常数,我把两个文件中都改成了 use user_lib::{write}; const STDOUT: usize = 1; 后再执行 make run,编译器对于 ch2t_write0.rs 提示 error: no global memory allocator found but one is required; link to std or add #[global_allocator] to a static item that implements the GlobalAlloc trait. ... 如何解决?

umaYnit commented 3 years ago

将ch2t_write0里的use alloc::slice;改为use core::slice;

DeathWish5 commented 3 years ago

当我把测例的 ch2_write1.rs 和 ch2t_write0.rs 引入 user/src/bin 后,在 os 目录执行 make run,编译器提示 use user_lib::{write, STDOUT}; 一行 no STDOUT in the root 考虑到 STDOUT = 1 是常数,我把两个文件中都改成了 use user_lib::{write}; const STDOUT: usize = 1; 后再执行 make run,编译器对于 ch2t_write0.rs 提示 error: no global memory allocator found but one is required; link to std or add #[global_allocator] to a static item that implements the GlobalAlloc trait. ... 如何解决?

你可以在测例仓库中 make ch2 然后就可以得到 bin 文件,将 bin 直接拷贝到目标加载地址就好了。

DeathWish5 commented 3 years ago

将ch2t_write0里的use alloc::slice;改为use core::slice;

收到。

lightbulb128 commented 3 years ago

简答题第二题有两个第4小问。

wyfcyx commented 3 years ago

Oh 我脑抽了 [facepalm] 不能删自己的评论太尴尬了

可以在 issue 里面手动删除。

Xiangze-Li commented 3 years ago

测例 ch2t_write0.rs 中写 const STACK_SIZE: usize = 0x1000; 即设定栈大小是 4 KiB; 而 batch.rs 中写 const USER_STACK_SIZE: usize = 4096 * 2;, 即用户栈的大小是 8 KiB.

这似乎会导致该测例中的第三个断言 assert_eq!(write(STDOUT, slice::from_raw_parts((bottom - 5) as *const _, 10)), -1); 失败. 即, 测例认为输出的 [bottom-5, bottom+5) 会溢出用户栈, 但它实际上是在用户栈的中央.

是我对可以合法输出的内存范围理解有误还是这个测例确实写错了?

umaYnit commented 3 years ago

可以去看测试用例仓库的guide,里面有讲到 @Xiangze-Li

用户程序用户栈大小为一个页,也就是0x1000 (4k),且按照0x1000对其。统一大小方便测试。

KiWeng commented 3 years ago

注解第四条中find . -name *.rej是否应为find . -name "*.rej"

huanght18 commented 3 years ago

个人的一点小小改动:把Tutorial两个分支间的差异做成patch,然后apply到自己的当前工作区上,而不是把自己在Tutorial上的改动做成patch应用到Tutorial的新分支上。貌似前者的冲突更少qaq

leoleoasd commented 3 years ago

同问:测试用例里写的栈大小为一个页,但是batch.rs里写的为两个页。

mailofzxf commented 3 years ago

“可以正确执行正确执行目标用户测例”: “正确执行”重复了。

vcheckzen commented 2 years ago

同问:测试用例里写的栈大小为一个页,但是batch.rs里写的为两个页。

在第二章的测试中,我们对于内核有如下仅仅为了测试方便的要求,请调整你的内核代码来符合这些要求。

用户栈大小必须为 4096,且按照 4096 字节对齐。这一规定可以在实验4开始删除,仅仅为通过 lab2/3 测例设置。

yuchanns commented 2 years ago

2.1 L40:刚进入 __restore 时,a0 代表了什么值。请指出 __restore 的两种使用情景。

a0 表示 TrapContext 的起始地址。 __restore 的两种使用场景:

  1. 直接调用 __restore 符号,内核开始切换到执行用户程序(特权从 S 跳转到 U)。
  2. 从陷入恢复并继续执行用户程序:用户程序进行系统调用时,发生陷入 ,__alltraps 会切换特权到 S 执行 call trap_handler ;在处理完陷入后,指令会继续向下执行 __restore 从而实现 U 特权级和上下文的恢复,继续执行用户程序。

如果我们尝试把 __alltraps__restore 顺序调换,就会发现第一个陷入结束后无法恢复上下文和特权级,从而全部变成非法指令

yuchanns commented 2 years ago

2.2 L46-L51:这几行汇编代码特殊处理了哪些寄存器?这些寄存器的的值对于进入用户态有何意义?请分别解释。

首先此时的 sp 是初始化完毕的 TrapContext 。回忆初始化的代码:

impl TrapContext {
    pub fn app_init_context(entry: usize, sp: usize) -> Self {
        let mut sstatus = sstatus::read();
        sstatus.set_spp(SPP::User);
        let mut cx = Self {
            x: [0; 32],
            sstatus,
            sepc: entry,
        };
        cx.set_sp(sp);
        cx
    }
}

可以知道 32*8(sp)sstatus33*8(sp)sepc2*8(sp)spsstatus 用于进入用户态时实现特权级的切换,sepc 用于指定用户程序入口(因为最后sret会将 sepc 的值设置到 pc 上,成为下一条待执行指令的地址),sp 用于指定用户程序执行时使用的栈。


sstatussepcsret这些信息信息可以通过阅读 riscv-privileged-20211203获得 。

yuchanns commented 2 years ago

2.3 L53-L59:为何跳过了 x2x4

因为 x2 保存的是 sp值 ,x4 保存的是 thread pointer值。

x2 指向了当前的内核栈,如果覆盖成用户栈,会导致执行异常。

x4 在线程中使用,这里没用到,内容为 0x0 ,目前我测试加不加不影响运行结果(不知道对不对

验证方式:加入不跳过 x2x4 的指令,然后在 gdb debug 中打断点,查看寄存器信息,以及即将执行的指令信息,可以观察到寄存器被覆盖的过程。

(gdb) b __restore
Breakpoint 1 at 0x802190b8
(gdb) c
Continuing.

Breakpoint 1, 0x00000000802190b8 in __restore ()
(gdb) i r sp
sp             0x8020fca0       0x8020fca0 <boot_stack+64656>
(gdb) x/10i $pc
=> 0x802190b8 <__restore>:      mv      sp,a0
   0x802190ba <__restore+2>:    ld      t0,256(sp)
   0x802190bc <__restore+4>:    ld      t1,264(sp)
   0x802190be <__restore+6>:    ld      t2,16(sp)
   0x802190c0 <__restore+8>:    csrw    sstatus,t0
   0x802190c4 <__restore+12>:   csrw    sepc,t1
   0x802190c8 <__restore+16>:   csrw    sscratch,t2
   0x802190cc <__restore+20>:   ld      ra,8(sp)
   0x802190ce <__restore+22>:   ld      sp,16(sp)
   0x802190d0 <__restore+24>:   ld      gp,24(sp)
(gdb) b *0x802190ce
Breakpoint 2 at 0x802190ce
(gdb) c
Continuing.

Breakpoint 2, 0x00000000802190ce in __restore ()
(gdb) si
0x00000000802190d0 in __restore ()
(gdb) i r sp
sp             0x8023d000       0x8023d000 <ladybug::batch::USER_STACK+8192>
yuchanns commented 2 years ago

2.4 L63:该指令之后,sp 和 sscratch 中的值分别有什么意义?

sp 指向用户栈,并把内核栈保存到 sscratch 。下面 sret 后切换成用户态执行用户程序时,用户程序将会使用用户栈,并在发生陷入时通过 sscratch 切换到内核栈执行内核态系统调用

yuchanns commented 2 years ago

2.5 __restore:中发生状态切换在哪一条指令?为何该指令执行之后会进入用户态?

根据 gdb debug 观察:

(gdb) b *(__restore+82)
Breakpoint 1 at 0x8021a0fe
(gdb) b *0x80400000
Breakpoint 2 at 0x80400000
(gdb) c
Continuing.

Breakpoint 1, 0x000000008021a0fe in __restore ()
(gdb) x/i $pc
=> 0x8021a0fe <__restore+82>:   sret
(gdb) i r sstatus
sstatus        0x0      0
(gdb) si

Breakpoint 2, 0x0000000080400000 in ?? ()
(gdb) i r sstatus
sstatus        0x20     32

发生在 sret 。因为 sret 会应用 sstatus 上设定的特权级。

When an SRET instruction (see Section 3.3.2) is executed to return from the trap handler, the privilege level is set to user mode if the SPP bit is 0, or supervisor mode if the SPP bit is 1; SPP is then set to 0. ──摘自 riscv-privileged-20211203 - 4.1.1 Supervisor Status Register (sstatus)

yuchanns commented 2 years ago

2.6 L13:该指令之后,spsscratch 中的值分别有什么意义?

切换成内核栈,并保存用户栈到 sscratch 用于后续陷入处理完毕后的恢复。

2.7 从 U 态进入 S 态是哪一条指令发生的?

ecall 发生。ecall 会根据所处的环境检查调用是否合法,产生不同的异常,并提升权限到对应系统调用的级别。

yuchanns commented 2 years ago

个人见解,如果不对,欢迎来打我纠正错误!

wangzeping722 commented 2 years ago

刚切换到分支时,直接 make run TEST=1 会编译失败。 大概报错内容如下:

...
error[E0658]: use of unstable library feature 'asm': inline assembly is not stable enough for use and is subject to change
  --> /home/wero/.cargo/git/checkouts/riscv-ab2abd16c438337b/11d43cf/src/register/macros.rs:10:21
   |
10 |                     core::arch::asm!("csrrs {0}, {1}, x0", out(reg) r, const $csr_number);
   |                     ^^^^^^^^^^^^^^^
   |
  ::: /home/wero/.cargo/git/checkouts/riscv-ab2abd16c438337b/11d43cf/src/register/hypervisorx64/vstvec.rs:41:1
   |
41 | read_csr_as!(Vstvec, 517, __read_vstvec);
   | ----------------------------------------- in this macro invocation
   |
   = note: see issue #72016 <https://github.com/rust-lang/rust/issues/72016> for more information
   = help: add `#![feature(asm)]` to the crate attributes to enable
   = note: this error originates in the macro `read_csr` (in Nightly builds, run with -Z macro-backtrace for more info)
...
make: *** [Makefile:56:kernel] 错误 101

原因是,文件 rust-toolchain 中的 rust 版本未更新,切换到跟ch1一样的版本 nightly-2022-04-11,重新尝试编译,依然会报错,不过错误内容不一样了,是下面这种依赖缺失的报错:

error: cannot find macro `asm` in this scope
  --> src/bin/bad_instruction.rs:13:9
   |
13 |         asm!("sret");
   |         ^^^
   |
   = note: consider importing this macro:
           core::arch::asm

我们根据提示在对应的文件中引入mod就行了。

use core::arch::asm;

但是有疑问的是,为啥这些mod没有引用呢,希望有大佬解答一下?

操作完成之后就可以编译成功了。

typhigh commented 2 years ago

@wangzeping722 应该是老版本通过#![feature(asm)]指定,新版本通过 use core::arch::asm指定。自然基于老版本写的代码也没有use core::arch::asm;

b1ankcat commented 1 week ago

对于实践作业,test1_write0.rs里设置的STACK_SIZE大小是0x1000,而os里的USER_STACK_SIZE大小是0x2000,所以test1_write0.rs的第三个用例会一直报错