Skip to content

Commit

Permalink
Lab 5:fork 的实现、并发与锁机制 (#12)
Browse files Browse the repository at this point in the history
Co-authored-by: Zhe Tang <[email protected]>
Co-authored-by: Zztrans <[email protected]>
  • Loading branch information
3 people authored Mar 6, 2024
1 parent bf76fdd commit 655b7ff
Show file tree
Hide file tree
Showing 19 changed files with 1,101 additions and 44 deletions.
15 changes: 8 additions & 7 deletions docs/labs/0x00/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@

我们推荐在以下环境进行实验:

- Windows 10/11
- Ubuntu 22.04 LTS
- Ubuntu 22.04 LTS on WSL 2
- Ubuntu 22.04 LTS (jammy) on WSL 2 **(推荐 Windows 用户选择)**
- Windows 10/11 **(Windows 原生备选,GDB 相关功能无法使用)**
- Ubuntu 22.04 LTS (jammy)
- macOS with Apple Silicon

以上环境经过我们的测试和验证,可以正常进行实验。对于其他常用的 Linux 发行版,通常也可以正常进行实验,但我们不提供技术支持。
Expand All @@ -27,15 +27,16 @@

本实验在 Windows 上进行项目开发是**完全可行的**,但是我们提供的各种工具的选项可能有所出入。

在 Windows 平台上我们建议通过 VSCode + Python + CodeLLDB 插件进行开发、调试。
在 Windows + WSL 2 平台上,建议使用 VSCode (Remote WSL) 连接到 WSL2 进行开发、调试。

在 Linux 平台上我们建议通过 VSCode (Remote) + Python / make + GDB 结合 gef 进行开发、调试。

在 Windows 平台上我们建议通过 VSCode + Python + CodeLLDB 插件进行开发、调试。

- 对于选择使用 Linux 的同学,请参考 [Linux 环境配置](../../wiki/linux.md) 进行配置,**文档包含 Linux 相关安装指南**

- 对于选择使用 Windows 的同学,请参考 [Windows 环境配置](../../wiki/windows.md) 进行配置。

- 对于选择使用 Linux 的同学,请参考 [Linux 环境配置](../../wiki/linux.md) 进行配置。

- 对于选择使用 macOS 的同学,请安装 `brew` 和相应工具,参考 [Linux 环境配置](../../wiki/linux.md) 进行配置。

## 尝试使用 Rust 进行编程
Expand Down Expand Up @@ -166,7 +167,7 @@
使得每次调用 `UniqueId::new()` 时总会得到一个新的不重复的 `UniqueId`。

- 你可以在函数体中定义 `static` 变量来存储一些全局状态
- 你可以尝试使用 `std::sync::atomic::AtomicU16` 来确保多线程下的正确性(无需进行验证)
- 你可以尝试使用 `std::sync::atomic::AtomicU16` 来确保多线程下的正确性(无需进行验证,相关原理将在 Lab 5 介绍,此处不做要求
- 使得你的实现能够通过如下测试:

```rust
Expand Down
6 changes: 1 addition & 5 deletions docs/labs/0x01/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -506,11 +506,7 @@ println!("{}", record.args());

2. 😋 尝试在进入内核并初始化串口驱动后,使用 escape sequence 来清屏,并编辑 `get_ascii_header()` 中的字符串常量,输出你的学号信息。

3. 🤔 尝试修改 `logger` 的初始化函数,使得日志等级能够根据编译时的环境变量 `LOG_LEVEL` 来决定编译产物的日志等级。

!!! note "提示"

你可以使用 `match option_env!("LOG_LEVEL")` 来判断环境变量的值,它将会在编译时被替换为环境变量的值。
3. 🤔 尝试添加字符串型启动配置变量 `log_level`,并修改 `logger` 的初始化函数,使得内核能够根据启动参数进行日志输出。

3. 🤔 尝试使用调试器,在内核初始化之后中断,查看、记录并解释如下的信息:

Expand Down
4 changes: 3 additions & 1 deletion docs/labs/0x02/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ lazy_static! {

你需要参考上下文,在 `src/memory/gdt.rs` 中补全 TSS 的中断栈表,为 Double Fault 和 Page Fault 准备独立的栈。

!!! warning "仅修改 `interrupt_stack_table`,不要修改用作示例的 `privilege_stack_table`"

## 注册中断处理程序

!!! note "请阅读 [CPU 中断处理](../../wiki/interrupts.md) 部分,学习中断基本知识。"
Expand Down Expand Up @@ -551,7 +553,7 @@ static int init_serial() {
}

#[inline]
pub fn try_get_key() -> Option<Key> {
pub fn try_pop_key() -> Option<Key> {
INPUT_BUF.pop()
}
```
Expand Down
13 changes: 9 additions & 4 deletions docs/labs/0x03/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,19 @@ as_handler!(teapot);
对于参数部分,`ProcessContext` 的简单声明如下:

```rust
#[repr(C)]
pub struct ProcessContext {
value: ProcessContextValue,
}

#[repr(C)]
pub struct ProcessContextValue {
pub regs: RegistersValue,
pub stack_frame: InterruptStackFrameValue,
}
```

这里的 `ProcessContextValue` 命名和相关的保护处理方法来自 `InterruptStackFrame` 的内部实现,用以防止意外的修改及其导致的非预期行为。
`ProcessContext` 实现了内部 `value``Deref` trait,因此可以直接使用 `ProcessContextValue` 中的字段内容。而 `ProcessContextValue` 相关的保护处理方法参考并实现自 `InterruptStackFrame` 的内部实现,用以防止意外的修改及其导致的非预期行为。

`repr(C)` 用于指定使用 C 语言的结构体布局,以便于在汇编代码中正确处理结构体的字段。

Expand Down Expand Up @@ -292,7 +297,7 @@ pub fn new(init: Arc<Process>) -> Self {

内核栈的起始地址通过配置文件被定义在了 `0xFFFFFF0100000000`,距离内核起始地址 4GiB。默认大小为 512 个 4KiB 的页面,即 2MiB。

在虚拟内存的规划中,任意进程的栈地址空间大小为 4GiB。以内核为例,内核栈的起始地址为 `0xFFFFFF0100000000`,结束地址为 `0xFFFFFF0200000000`
在虚拟内存的规划中,任意进程的栈地址空间大小为 4GiB。以内核为例,内核栈所对应的内存区域的起始地址为 `0xFFFFFF0100000000`,结束地址为 `0xFFFFFF0200000000`

!!! note "缺页异常?"

Expand Down Expand Up @@ -366,7 +371,7 @@ pub const STACK_INIT_TOP: u64 = STACK_MAX - 8;
+---------------------+
```

!!! tip "**以 PID 2 为例:<br/>初始化分配的栈的页面为 `0x3FFEFFFFF000``0x3FFF00000000`<br/>默认栈顶地址为 `0x3FFEFFFFFFF8`**"
!!! tip "**以 PID 2 为例:<br/>初始化分配的栈的页面为 `0x3FFEFFFFF000``0x3FFF00000000`(区间左闭右开)<br/>默认栈顶地址为 `0x3FFEFFFFFFF8`**"

有关于用户进程的其他部分内存布局的说明,将在下一次实验中详细讨论。

Expand Down Expand Up @@ -503,7 +508,7 @@ pub const STACK_INIT_TOP: u64 = STACK_MAX - 8;

```rust
pub fn new_test_thread(id: &str) -> ProcessId {
let proc_data = ProcessData::new();
let mut proc_data = ProcessData::new();
proc_data.set_env("id", id);

crate::proc::spawn_kernel_thread(
Expand Down
50 changes: 34 additions & 16 deletions docs/labs/0x04/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,7 @@ pub fn exit(ret: isize, context: &mut ProcessContext) {
pub fn wait(init: proc::ProcessId) {
loop {
if proc::still_alive(init) {
x86_64::instructions::hlt();
x86_64::instructions::hlt(); // Why? Check reflection question 5
} else {
break;
}
Expand All @@ -797,11 +797,13 @@ pub fn still_alive(pid: ProcessId) -> bool {
}
```

对于具体的进程操作、目录操作等功能,将会移步到用户态程序进行实现。为了给予用户态程序操作进程、等待进程退出的能力,这里还缺少最后两个系统调用需要实现`spawn``waitpid`对这两个系统调用有如下约定
对于具体的进程操作、目录操作等功能,将会移步到用户态程序进行实现。为了给予用户态程序操作进程、等待进程退出的能力,这里还缺少最后几个系统调用需要实现`spawn``getpid``waitpid`对这些系统调用有如下约定

```rust
// path: &str (arg0 as *const u8, arg1 as len) -> pid: u16
Syscall::Spawn => { /* ... */ },
// None -> pid: u16
Syscall::GetPid => { /* ... */ },
// pid: arg0 as u16 -> status: isize
Syscall::WaitPid => { /* ... */},
```
Expand All @@ -810,15 +812,13 @@ Syscall::WaitPid => { /* ... */},

> 你可以自由定义你的内核与用户态的交互方式了!
!!! note "关于 `WaitPid` 的问题"
!!! note "关于 `waitpid` 的问题"

你可能会发现,`WaitPid` 需要返回特殊状态,以区分进程正在运行还是已经退出
在这里的简单实现下,`waitpid` 不进行阻塞,应当立刻返回进程的当前状态。从而本次实验中,在用户态使用忙等待的方式判断进程是否退出

不过非常糟糕,当前进程的返回值也是一个 `isize` 类型的值,这意味着如果按照现在的设计,势必存在一些返回值和“正在运行”的状态冲突。
但是`waitpid` 需要返回特殊状态,以区分进程正在运行还是已经退出。这非常糟糕,当前进程的返回值也是一个 `isize` 类型的值,这意味着如果按照现在的设计,势必存在一些返回值和“正在运行”的状态冲突。不过在本次实验中,这并不会造成太大的问题

不过在简单的实验中,这并不会造成太大的问题,而此问题的解决方案也留作加分项供大家研究。

**另请注意,`WaitPid` 虽然名为 `wait` 但是并不会进行阻塞,应当立刻返回进程的当前状态。**
此问题的更好解决方案将留作 Lab 5 的加分项供大家探索。

## 运行 Shell

Expand Down Expand Up @@ -923,19 +923,33 @@ The factorial of 999999 under modulo 1000000007 is 128233642.

从本次实验及先前实验的所学内容出发,结合进程的创建、链接、执行、退出的生命周期,参考系统调用的调用过程(可以仅以 Linux 为例),解释程序的运行。

5. `x86_64::instructions::hlt` 做了什么?为什么这样使用?

6. 有同学在某个回南天迷蒙的深夜遇到了奇怪的问题:

只有当进行用户输入(触发了串口输入中断)的时候,会触发奇怪的 Page Fault,然而进程切换、内存分配甚至 `fork` 等系统调用都很正常。

**经过<s>近三个小时的</s>排查,发现他将 TSS 中的 `privilege_stack_table` 相关设置注释掉了。**

请查阅资料,了解特权级栈的作用,实验说明这一系列中断的触发过程,尝试解释这个现象。

- 可以使用 `intdbg` 参数,或 `ysos.py -i` 进行数据捕获。
- 留意包含 `0x80` 系统调用、`0x0e` 缺页异常的某三次中断的信息。
- 注意到一个不应当存在的地址……?

或许你可以重新复习一下 Lab 2 的相关内容:[double-fault-exceptions](https://os.phil-opp.com/double-fault-exceptions/)

## 加分项

1. 😋 尝试在 `ProcessData` 中记录代码段的占用情况,并统计当前进程所占用的页面数量,并在打印进程信息时,将进程的内存占用打印出来。

2. 😋 尝试在 `kernel/src/memory/frames.rs` 中实现帧分配器的回收功能 `FrameDeallocator`,作为一个最小化的实现,你可以在 `Allocator` 使用一个 `Vec` 存储被释放的页面,并在分配时从中取出。

3. 🤔 基于帧回收器的实现,在 `elf` 中实现 `unmap_range` 函数,从页表中取消映射一段连续的页面,并使用帧回收器进行回收。利用它实现进程栈的回收(利用 `ProcessData` 中存储的页面信息)。**页表的回收将会在后续实现用实现,暂时不需要处理**

4. 🤔 改进或重新设计进程返回值的相关内容,给予 `WaitPid` 更好的兼容性,描述你的设计和实现。
3. 🤔 基于帧回收器的实现,在 `elf` 中实现 `unmap_range` 函数,从页表中取消映射一段连续的页面,并使用帧回收器进行回收。利用它实现进程栈的回收(利用 `ProcessData` 中存储的页面信息),页表的回收将会在后续实现用实现,暂时不需要处理。

5. 🤔 尝试利用 `UefiRuntime``chrono` crate,获取当前时间,并将其暴露给用户态,以实现 `sleep` 函数。
4. 🤔 尝试利用 `UefiRuntime``chrono` crate,获取当前时间,并将其暴露给用户态,以实现 `sleep` 函数。

`UefiRuntime` 的实现:
`UefiRuntime` 的实现,它可能需要使用锁进行保护

```rust
pub struct UefiRuntime {
Expand All @@ -955,7 +969,7 @@ The factorial of 999999 under modulo 1000000007 is 128233642.
}
```

一个可能的 `sleep` 函数实现:
这里提供一个可能的 `sleep` 函数实现:

```rust
pub fn sleep(millisecs: i64) {
Expand All @@ -968,6 +982,10 @@ The factorial of 999999 under modulo 1000000007 is 128233642.
}
```

在实现后,写一个或更改现有用户程序,验证你的实现是否正确,尝试输出当前时间并等待一段时间。
> 当前实现是纯用户态、采用轮询的,这种实现是很低效的。
>
> 在现代操作系统中,进程会被挂起,并等待对应事件触发后重新被调度。
>
> 虽然不是最好,但是在目前的需求下,这已经足够了。

> 当前实现是纯用户态、采用轮询的,这种实现是很低效的。在现代操作系统中,进程会被挂起,并等待对应事件触发后重新被调度
在实现后,写一个或更改现有用户程序,验证你的实现是否正确,尝试输出当前时间并使用 `sleep` 函数进行等待
3 changes: 0 additions & 3 deletions docs/labs/0x05/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@

鼓励使用 Typst 来进行实验文档的编写,使用可以参考 [使用 Typst 编写报告](../../general/typst.md)

对于本次实验内容,你需要参考学习如下实验资料:


## 实验任务与要求

1. 请各位同学独立完成作业,任何抄袭行为都将使本次作业判为 0 分。
Expand Down
Loading

0 comments on commit 655b7ff

Please sign in to comment.