Skip to content

Commit 685c5c7

Browse files
committed
ch4:优化代码; reports 更新
1 parent ac93d02 commit 685c5c7

File tree

7 files changed

+229
-48
lines changed

7 files changed

+229
-48
lines changed

os/breakpoints.gdb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
b os::task::TaskManager::run_first_task
1+
b os::task::TaskManager::run_first_task
2+
b os::task::run_first_task
3+
b os::trap::trap_return

os/src/mm/memory_set.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ impl MemorySet {
5555
}
5656
}
5757
let mut map_perm = MapPermission::U;
58-
if prot & 1 != 0 { map_perm |= MapPermission::R }
58+
if prot & 1 != 0 { map_perm |= MapPermission::R } // 用户1,2,4,对应的是2,4,8
5959
if prot & 2 != 0 { map_perm |= MapPermission::W }
6060
if prot & 4 != 0 { map_perm |= MapPermission::X }
6161

os/src/mm/page_table.rs

Lines changed: 21 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -243,52 +243,37 @@ pub fn translated_byte_buffer(token: usize, ptr: *const u8, len: usize) -> Vec<&
243243
}
244244

245245
/// 内核获取当前程序的虚拟地址对应物理地址
246+
/// 参考ch4 recitation 实现
246247
pub fn app_vaddr_to_paddr(token: usize, vaddr: *const u8) -> Option<usize> {
247-
let pt = PageTable::from_token(token);
248+
let app_page_table = PageTable::from_token(token);
248249
let va = VirtAddr::from(vaddr as usize);
249-
let pte = pt.find_pte(va.floor());
250-
match pte {
251-
Some(pte) => {
252-
Some(super::PhysAddr::from(pte.ppn()).0 + va.page_offset())
253-
}
254-
_ => None
255-
}
250+
// 如果表达式返回None,则函数在此返回None;如果表达式返回 Some(pte_value),则 pte得到解包的pte_value
251+
let pte = app_page_table.find_pte(va.floor())?;
252+
253+
// 如果代码执行到这里,说明 find_pte 返回了 Some,并且 pte 已解包。
254+
// 计算物理地址,并将其包裹在 Some 中返回。
255+
Some(super::PhysAddr::from(pte.ppn()).0 + va.page_offset())
256256
}
257257

258-
/// 内核获取当前程序的虚拟地址对应物理地址,并配合检查
258+
/// 内核获取当前程序的虚拟地址对应物理地址,并配合检查:
259+
/// 如果要求某个权限但页面没有该权限,则返回 None
259260
#[allow(non_snake_case)]
260261
pub fn app_vaddr_to_paddr_prot(token: usize, vaddr: *const u8, prot: usize) -> Option<usize> {
261-
let pt = PageTable::from_token(token);
262+
let app_page_table = PageTable::from_token(token);
262263
let va = VirtAddr::from(vaddr as usize);
263-
let pte = pt.find_pte(va.floor());
264+
let pte = app_page_table.find_pte(va.floor())?;
265+
if !pte.is_valid() || !pte.user_available() {
266+
return None;
267+
}
268+
264269
let R = (prot >> 1) & 1;
265270
let W = (prot >> 2) & 1;
266271
let X = (prot >> 3) & 1;
267-
println!("R, W, X: {}, {}, {}", R, W, X);
268-
match pte {
269-
Some(pte) => {
270-
let mut r: bool = true;
271-
let mut w: bool = true;
272-
let mut x: bool = true;
273-
if R == 1 {
274-
r = pte.readable();
275-
}
276-
if W == 1 {
277-
w = pte.writable();
278-
}
279-
if X == 1 {
280-
x = pte.executable();
281-
}
282-
println!("prot: {}", prot);
283-
println!("r={}, w={}, x={}", r, w, x);
284-
if pte.user_available() && r && w && x && pte.is_valid() {
285-
Some(super::PhysAddr::from(pte.ppn()).0 + va.page_offset())
286-
}
287-
else {
288-
None
289-
}
290-
}
291-
_ => None
272+
if (R == 1 && !pte.readable())
273+
|| (W == 1 && !pte.writable())
274+
|| (X == 1 && !pte.executable()) {
275+
return None;
292276
}
277+
Some(super::PhysAddr::from(pte.ppn()).0 + va.page_offset())
293278
}
294279

os/src/syscall/process.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub fn sys_yield() -> isize {
2424

2525

2626

27-
/// YOUR JOB: get time with second and microsecond
27+
///TODO: get time with second and microsecond
2828
/// HINT: You might reimplement it with virtual memory management.
2929
/// HINT: What if [`TimeVal`] is splitted by two pages ?
3030
/// tz时区,不管
@@ -42,18 +42,17 @@ pub fn sys_get_time(ts: *mut TimeVal, _tz: usize) -> isize {
4242
0
4343
}
4444

45-
/// TODO: Finish sys_trace to pass testcases
45+
///TODO: Finish sys_trace to pass testcases
4646
/// HINT: You might reimplement it with virtual memory management.
4747
/// 这个系统调用有三种功能,根据 trace_request 的值不同,执行不同的操作:
48-
/// trace_request==0,则 id 应被视作 *const u8 ,表示读取当前任务 id 地址处一个字节的无符号整数值。此时应忽略 data 参数。返回值为 id 地址处的值。
49-
/// trace_request==1,则 id 应被视作 *mut u8 ,表示写入 data (作为 u8,即只考虑最低位的一个字节)到该用户程序 id 地址处。返回值应为0。
48+
/// trace_request==0,则 id 应被视作 *const u8 ,读取当前任务 id 地址处一个字节的无符号整数值。此时应忽略 data 参数。返回值为 id 地址处的值。
49+
/// trace_request==1,则 id 应被视作 *mut u8 ,写入 data (作为 u8,即只考虑最低位的一个字节)到该用户程序 id 地址处。返回值应为0。
5050
/// trace_request==2,表示查询当前任务调用编号为 id 的系统调用的次数,返回值为这个调用次数。本次调用也计入统计。
5151
/// 在读取(trace_request 为 0)时,如果对应地址用户不可见或不可读,则返回值应为 -1(isize 格式的 -1,而非 u8)。
5252
/// 在写入(trace_request 为 1)时,如果对应地址用户不可见或不可写,则返回值应为 -1(isize 格式的 -1,而非 u8)。
5353
/// 否则,忽略其他参数,返回值为 -1。
5454
pub fn sys_trace(trace_request: usize, id: usize, data: usize) -> isize {
5555
trace!("kernel: sys_trace");
56-
// println!("kernel: sys_trace!");
5756
let token = TASK_MANAGER.get_current_token();
5857
let mut prot = 0;
5958
if trace_request == 0 {
@@ -103,15 +102,14 @@ pub fn sys_trace(trace_request: usize, id: usize, data: usize) -> isize {
103102
/// 5. 物理内存不足 ?
104103
/// 返回值:执行成功则返回 0,错误返回 -1
105104
pub fn sys_mmap(start: usize, len: usize, prot: usize) -> isize {
106-
println!("#### sys_mmap ####");
107105
if (start % PAGE_SIZE != 0) || (prot & (!0x7) != 0) || (prot & 0x7 == 0) {
108106
return -1;
109107
}
110108
let mut task_manager_inner = TASK_MANAGER.inner.exclusive_access();
111109
let cur = task_manager_inner.current_task;
112110

113111
let res = task_manager_inner.tasks[cur].memory_set
114-
.map(start, len, prot);
112+
.map(start, len, prot);
115113
res
116114
}
117115

os/src/task/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pub use context::TaskContext;
3737
/// existing functions on `TaskManager`.
3838
pub struct TaskManager {
3939
/// total number of tasks
40-
num_app: usize,
40+
pub num_app: usize,
4141
/// use inner value to get mutable access
4242
pub inner: UPSafeCell<TaskManagerInner>,
4343
}

reports/lab1.md

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,121 @@
1-
占位符
1+
## 总结本次实验
2+
本次实验主要实现以下内容:
3+
- 对结构体 `TaskControlBlock` 添加了成员 `syscall_times: [u32; MAX_SYSCALL_NUM]`,记录当前任务使用的每种系统调用的数量。
4+
- `syscall_id` 就是数字,所以以它作为键访问 `syscall_times`。
5+
- 增加全局变量 `MAX_SYSCALL_NUM`,表示最大系统调用数量。
6+
-`TaskManager` 实现 `add_syscall_times(syscall_id)` 方法,用于更新当前正在执行的任务的 `syscall_times`
7+
- 当前任务调用 `syscall` 时,将其 `syscall_times` 中对应条目加一。
8+
-`TaskManager` 实现 `get_syscall_times()` 方法,返回正在执行的任务的 `syscall_times`
9+
- 按照实验要求实现 `sys_trace`
10+
11+
12+
## 简答作业
13+
14+
##### 问题 1
15+
正确进入 U 态后,程序的特征还应有:使用 S 态特权指令,访问 S 态寄存器后会报错。 请同学们可以自行测试这些内容(运行 三个 bad 测例 (`ch2b_bad_*.rs`) ), 描述程序出错行为,同时注意注明你使用的 sbi 及其版本。
16+
17+
**答:**
18+
sbi 版本:
19+
```
20+
[rustsbi] RustSBI version 0.3.0-alpha.2, adapting to RISC-V SBI v1.0.0
21+
```
22+
23+
运行程序,输出如下:
24+
```log
25+
I am ch2b_bad_address.
26+
[kernel] PageFault in application, bad addr = 0x0, bad instruction = 0x804003c8, kernel killed it.
27+
I am ch2b_bad_instructions.
28+
[kernel] IllegalInstruction in application, kernel killed it.
29+
I am ch2b_bad_register.
30+
[kernel] IllegalInstruction in application, kernel killed it.
31+
```
32+
- 第一个程序尝试写入非法内存,被阻止并被杀死。
33+
- 第二个程序尝试执行 `sret` 指令,`sret` 是 S-Mode 指令,用户态无法执行,被阻止并被杀死。
34+
- 第三个程序尝试执行 `csrr sstatus` 指令,`sstatus` 寄存器只能在 S-Mode 或更高等级下访问,用户态无法使用,被阻止并被杀死。
35+
36+
##### 问题 2
37+
深入理解 [trap.S](https://github.com/LearningOS/rCore-Camp-Code-2025S/blob/ch3/os/src/trap/trap.S) 中两个函数 `__alltraps` 和 `__restore` 的作用,并回答如下问题:
38+
39+
1. **L40:刚进入 `__restore` 时,`sp` 代表了什么值。请指出 `__restore` 的两种使用情景。**
40+
```nasm
41+
ld t0, 32*8(sp)
42+
ld t1, 33*8(sp)
43+
ld t2, 2*8(sp)
44+
csrw sstatus, t0
45+
csrw sepc, t1
46+
csrw sscratch, t2
47+
```
48+
49+
**答:**
50+
- 刚进入时,`sp` 指向内核栈。
51+
- `__restore` 既可以用于开始执行一个 app,也可以用于在处理完 trap 后跳转回 app 的执行流。
52+
53+
54+
2. **L43-L48:这几行汇编代码特殊处理了哪些寄存器?这些寄存器的的值对于进入用户态有何意义?请分别解释。**
55+
```
56+
ld t0, 32*8(sp)
57+
ld t1, 33*8(sp)
58+
ld t2, 2*8(sp)
59+
csrw sstatus, t0
60+
csrw sepc, t1
61+
csrw sscratch, t2
62+
```
63+
64+
**答:**
65+
- 这几行汇编代码先处理的是 `sstatus` 、`sepc` 和 `sscratch` 寄存器。
66+
- `sstatus` 给出 Trap 发生前 CPU 所处特权级(S/U)等信息,进入用户态需要恢复;
67+
- `sepc` 是 trap 处理完后应跳转的目标地址,恢复这个寄存器,程序才能正确跳转到原先控制流。
68+
- `sscratch` 在上面代码运行后被恢复为指向内核栈。这个用于处理内核栈和用户栈的交换。
69+
70+
71+
3. **L50-L56:为何跳过了 `x2` 和 `x4`?**
72+
```
73+
ld x1, 1*8(sp)
74+
ld x3, 3*8(sp)
75+
.set n, 5
76+
.rept 27
77+
LOAD_GP %n
78+
.set n, n+1
79+
.endr
80+
```
81+
**答:**
82+
- `sp` 已通过 `mv sp, a0` 显式设置为内核栈顶地址,后续通过 `csrrw sp, sscratch, sp` 切换为用户栈指针,所以无须从栈中恢复
83+
- `x4` 目前未使用,无须恢复。
84+
85+
4. **L60:该指令之后,`sp` 和 `sscratch` 中的值分别有什么意义?**
86+
```
87+
csrrw sp, sscratch, sp
88+
```
89+
**答:**
90+
- 在该指令之后,`sp->内核栈`,`sscratch->用户栈`。
91+
92+
5. **`__restore`:中发生状态切换在哪一条指令?为何该指令执行之后会进入用户态?**
93+
94+
**答:**
95+
- 状态切换发生在 `sret` 指令。
96+
- `sret` 会根据 ` sstatus.SPP` 位将特权级从 S-mode 降为 U-mode, 然后将 `sepc` 的值加载到 PC,也就是跳转用户态下一条待执行指令。
97+
98+
6. **L13:该指令之后,`sp` 和 `sscratch` 中的值分别有什么意义?**
99+
```
100+
csrrw sp, sscratch, sp
101+
```
102+
103+
**答:**
104+
- 交换,让 `sp->用户栈`,`sscratch->内核栈`。
105+
106+
107+
108+
7. **从 U 态进入 S 态是哪一条指令发生的?**
109+
110+
**答:**
111+
- 用户程序的 `ecall` 指令。
112+
113+
114+
## 荣誉准则
115+
我参考了 **以下资料** ,还在代码中对应的位置以注释形式记录了具体的参考来源及内容:
116+
- [管理多道程序 - rCore-Camp-Guide-2025S 文档](https://learningos.cn/rCore-Camp-Guide-2025S/chapter3/3multiprogramming.html#id3)
117+
- [清华大学云盘ch3](https://cloud.tsinghua.edu.cn/d/eec08e3c8f224e27b01d/files/?p=%2Frcore%20%E5%AE%9E%E9%AA%8C%E4%B8%89%20%E9%A9%AC%E6%80%9D%E6%BA%90.mp4)
118+
119+
我独立完成了本次实验除以上方面之外的所有工作,包括代码与文档。 我清楚地知道,从以上方面获得的信息在一定程度上降低了实验难度,可能会影响起评分。
120+
121+
我从未使用过他人的代码,不管是原封不动地复制,还是经过了某些等价转换。 我未曾也不会向他人(含此后各届同学)复制或公开我的实验代码,我有义务妥善保管好它们。 我提交至本实验的评测系统的代码,均无意于破坏或妨碍任何计算机系统的正常运转。 我清楚地知道,以上情况均为本课程纪律所禁止,若违反,对应的实验成绩将按“-100”分计。

reports/lab2.md

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,77 @@
1-
占位符
1+
## 总结本次实验
2+
本次实验实现:
3+
- `sys_get_time`:向 `ts` 位置写入当前时间。难点在于,这一章我们实现了虚拟内存,用户传来的 `ts` 位置为虚拟地址,我们需要写入的是虚拟地址对应的物理地址。不过,由于内核实现了恒等映射,所以我们仅需要转换 `ts` 为物理地址,然后向对应位置写入时间结构体 `TimeVal`
4+
- `sys_trace`, 根据 `trace_request` 的值不同,执行不同的操作。实现方案和 `sys_get_time` 类似,多家一个目标地址权限判断。
5+
- `sys_mmap``sys_unmap`,申请内存和释放内存。申请和释放空间都要修改对应的页表。
6+
## 问答题
7+
##### 请列举 SV39 页表页表项的组成,描述其中的标志位有何作用?
8+
PTE 中,`[53:10]` 这 44 位是物理页号,最低的 8 位 `[7:0]` 则是标志位,它们的含义如下
9+
- V(Valid):如果设置,表示该页表项是有效的,对应的虚拟地址有物理映射。如果清除,表示无效,访问会触发页错误。
10+
- R(Read)/W(Write)/X(eXecute):分别控制索引到这个页表项的对应虚拟页面是否允许读/写/执行;
11+
- U(User):如果设置,表示用户模式(User Mode)的代码可以访问该页。如果清除,通常表示只有内核模式(Supervisor/Kernel Mode)可以访问。
12+
- 当 `sstatus` 寄存器中的 `SUM` 位置1,`S` 特权级可以访问 `U` 位为 `1` 的页,但是 `S` 特权级的程序常运行在 `SUM` 位清空的条件下,如果 `S` 特权级直接访问会出现 `page fault`
13+
- G:全局位。如果设置,表示该页映射对所有地址空间都是全局的,TLB 在进行上下文切换时可能不需要刷新该条目。
14+
- A(Accessed):访问位。硬件会在该页被访问(读或写)时自动设置此位。操作系统可以定期清除此位来追踪哪些页最近被使用过(用于页面置换算法)。
15+
- D(Dirty):脏位。处理器记录自从页表项上的这一位被清零之后,页表项的对应虚拟页面是否被修改过。操作系统可以根据此位判断页是否被修改过,决定在页面置换时是否需要写回硬盘。
16+
17+
18+
##### 缺页
19+
> 一、缺页指的是进程访问页面时页面不在页表中或在页表中无效的现象,此时 MMU 将会返回一个中断, 告知 os 进程内存访问出了问题。os 选择填补页表并重新执行异常指令或者杀死进程。**请问哪些异常可能是缺页导致的?**(发生缺页时,描述相关重要寄存器的值,上次实验描述过的可以简略)
20+
21+
下列异常可能由缺页导致:
22+
- 访问非法地址:不在当前程序页表中或对应页不可用。
23+
- 违反标志位:只读页面写,读写页面执行等。
24+
- 物理页用尽:内存耗尽。
25+
26+
发生缺页时,相关重要寄存器值如下:
27+
- `scause`:包含导致错误的具体代码,
28+
- `stval`:存储导致错误的虚拟地址。
29+
- `sepc`:存储触发缺页异常的指令地址。
30+
31+
32+
> 二、缺页有两个常见的原因,其一是 Lazy 策略,也就是直到内存页面被访问才实际进行页表操作。 比如,一个程序被执行时,进程的代码段理论上需要从磁盘加载到内存。但是 os 并不会马上这样做, 而是会保存 `.text` 段在磁盘的位置信息,在这些代码第一次被执行时才完成从磁盘的加载操作。这样做有哪些好处?
33+
34+
好处:延迟加载可以加快程序启动时间,且可以更有效的利用内存:程序的执行不会立即用到所有代码和数据,延迟加载了避免不必要的代码和数据放在内存中。
35+
36+
> 三、其实,我们的 `mmap` 也可以采取 Lazy 策略,比如:一个用户进程先后申请了 10G 的内存空间, 然后用了其中 1M 就直接退出了。按照现在的做法,我们显然亏大了,进行了很多没有意义的页表操作。处理 10G 连续的内存页面,对应的 SV39 页表大致占用多少内存 (估算数量级即可)?请简单思考如何才能实现 Lazy 策略,缺页时又如何处理?描述合理即可,不需要考虑实现。
37+
38+
`mmap` 时可以不分配内存,只记录有这么回事;真正的分配等到程序第一次访问分配空间时,由缺页处理程序分配。
39+
40+
> 四、缺页的另一个常见原因是 swap 策略,也就是内存页面可能被换到磁盘上了,导致对应页面失效。此时页面失效如何表现在页表项 (PTE) 上?
41+
42+
PTE 的 `Valid` 位无效。
43+
44+
45+
##### 双页表与单页表
46+
47+
> 为了防范侧信道攻击,我们的 os 使用了双页表。但是传统的设计一直是单页表的,也就是说, 用户线程和对应的内核线程共用同一张页表,只不过内核对应的地址只允许在内核态访问。 (备注:这里的单/双的说法仅为自创的通俗说法,并无这个名词概念,详情见 KPTI )
48+
49+
单页表的情况下每个进程拥有一张页表,这张页表中包含了该进程的用户空间映射以及完整的内核空间映射。**内核空间映射在任何用户进程的页表中都是相同的**。通过页表项(PTE)的权限位控制用户态无法访问内核页面。
50+
51+
> 在单页表情况下,如何更换页表?
52+
53+
`satp` 寄存器为目标程序的根页表,之后立即刷新 TLB。
54+
55+
> 单页表情况下,如何控制用户态无法访问内核页面?(tips: 看看上一题最后一问)
56+
57+
置用户程序对应的内核页面的 PTE 特权级位为 S。
58+
59+
> 单页表有何优势?(回答合理即可)
60+
61+
速度快:由于每个进程页表都映射了内核区域,所以 trap 发生时操作系统不需要切换页表(即无须修改 `satp` 寄存器),并且需要刷新 TLB 的次数和开销减少了(比如从进程切换到内核无须刷新 TLB,从内核切换到进程,只需要清除和进程相关的 TLB 而内核相关的 TLB 可以继续保留)。
62+
63+
> 双页表实现下,何时需要更换页表?假设你写一个单页表操作系统,你会选择何时更换页表(回答合理即可)?
64+
65+
双页表情况下每当用户态和内核态之间切换时都要换页表,因为进程页表没有映射全部内核区域。单页表情况下只需在进程上下文切换时换页表。
66+
67+
68+
## 荣誉准则
69+
70+
我参考了 **以下资料** ,还在代码中对应的位置以注释形式记录了具体的参考来源及内容:
71+
72+
- [第四章:地址空间 - rCore-Tutorial-Book-v3 文档](https://rcore-os.cn/rCore-Tutorial-Book-v3/chapter4/index.html)
73+
- [清华大学云盘ch4]([清华大学云盘](https://cloud.tsinghua.edu.cn/d/eec08e3c8f224e27b01d/files/?p=%2Frcore%20%E5%AE%9E%E9%AA%8C%E5%9B%9B%20%E6%9B%B9%E9%9A%BD%E8%AF%9A.mp4))
74+
75+
我独立完成了本次实验除以上方面之外的所有工作,包括代码与文档。 我清楚地知道,从以上方面获得的信息在一定程度上降低了实验难度,可能会影响起评分。
76+
77+
我从未使用过他人的代码,不管是原封不动地复制,还是经过了某些等价转换。 我未曾也不会向他人(含此后各届同学)复制或公开我的实验代码,我有义务妥善保管好它们。 我提交至本实验的评测系统的代码,均无意于破坏或妨碍任何计算机系统的正常运转。 我清楚地知道,以上情况均为本课程纪律所禁止,若违反,对应的实验成绩将按“-100”分计。

0 commit comments

Comments
 (0)