diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8bdcb4a..c36579a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -15,5 +15,5 @@ jobs: with: python-version: 3.x - - run: pip --trusted-host pypi.org --trusted-host files.pythonhosted.org install mkdocs-material + - run: pip install mkdocs-material - run: mkdocs gh-deploy --force diff --git a/.gitignore b/.gitignore index 208a37d..109ccc6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ # will have compiled files and executables **/debug/ **/target/ +**/Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk diff --git a/docs/general/coding_convention.md b/docs/general/coding_convention.md new file mode 100644 index 0000000..1d09ce6 --- /dev/null +++ b/docs/general/coding_convention.md @@ -0,0 +1,67 @@ +# 代码规范 + +本页面列举了一些常见的代码规范要求。 + +部分要求并不强制,但是建议尽量遵守和学习。 + +## Git 相关 + +=== "提交历史" + + - 每个提交都应该有一定的意义,例如实现了新功能,修复了一个问题,定义了新的函数; + - 比较复杂的程序,要边开发边提交,而不是写完了再一次性提交; + - 不强求线性历史,**但是不允许使用 force push**。 + +=== "提交消息" + + - 简单明了地描述这个提交的内容; + - 建议用英文写,用中文写也可以; + - 不要编写的过于详细或过于简略; + - 可以采用一些格式,例如 [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#examples); + - 不掺杂个人情绪; + - 用户名和邮箱应该配置成自己的真实姓名和邮箱; + - 可以添加一些 Emoji,[gitmoji](https://gitmoji.dev/) 为提交说明中使用的 Emoji 提出了一些建议,可以参考。 + +## 代码风格 + +=== "简洁" + + 保证代码的简洁: + + - 有整齐的缩进,建议用空格缩进而非 tab,两个空格或者四个空格作为一级缩进都可以; + - 每一行不要有太多字符,例如不超过 80 / 100 个字符; + +=== "注释" + + 在代码中编写适当的注释: + + - 在比较复杂的代码块前,描述代码的功能; + - 过于简单的代码,一般不写注释; + - 函数一般要编写注释,包括其功能,参数和输出; + - 建议用英文,中文也可以,但是注意要用 UTF-8 编码。 + - 遵循 [Rustdoc 的约定](https://doc.rust-lang.org/rustdoc/how-to-write-documentation.html) + +=== "空白" + + 代码中应该有适当的空格和空行: + + - 函数中,实现不同功能的代码间需要添加空行; + - 操作符前后应该有空格,例如 c = a + b; + - 保持前后一致的风格。 + +=== "命名" + + 变量的命名,应该符合一定的规范: + + - 符合 Rust 编译器建议的变量命名规范; + - 尽量用英文单词,而不是中文拼音首字母; + - 命名与上下文要相关; + - 不用类似 `a, b, c, d` 的命名方式。 + + +我们建议使用工具来辅助代码风格的检查: + +- 使用 `rustfmt`,`cargo fmt --all` 命令来格式化代码; +- 使用 `clippy`,`cargo clippy` 来检查代码风格。 + +!!! note "请注意,由于项目 target 不尽相同,`clippy` 需要在每一个 `package` 下使用。" diff --git a/docs/faq.md b/docs/general/faq.md similarity index 100% rename from docs/faq.md rename to docs/general/faq.md diff --git a/docs/index.md b/docs/index.md index 7024d32..ee697e6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,12 +1,16 @@ # 中山大学 YatSenOS v2 操作系统实验教程 -A Rust x86_64 OS lab tutorial. +## 实验说明 + +本文档提供了一套基于 Rust、面向 UEFI 和 x86_64 的操作系统课程实验方案。 + +我们期望基于低汇编、避免重复造轮子的宗旨,利用 Rust 语言优秀的包管理和底层支持,借助现有的优秀工程化底层封装,为学生提供一个低负担、现代、面向高级语言的操作系统实验指南。 ## 实验大纲 每一次实验区间为两周。 -- [实验零:环境搭建与实验准备](./labs/0x00-env.md) +- [实验零:环境搭建与实验准备](./labs/0x00/index.md) 1. Rust 学习和巩固,了解标准库提供的基本数据结构和功能。 2. QEMU 与 Rust 环境搭建,尝试使用 QEMU 启动 UEFI Shell。 diff --git a/docs/labs/0x00-env.md b/docs/labs/0x00-env.md deleted file mode 100644 index 90b88e0..0000000 --- a/docs/labs/0x00-env.md +++ /dev/null @@ -1 +0,0 @@ -# 实验零:环境搭建与实验准备 diff --git a/docs/labs/0x00/index.md b/docs/labs/0x00/index.md new file mode 100644 index 0000000..a3eb98d --- /dev/null +++ b/docs/labs/0x00/index.md @@ -0,0 +1,45 @@ +# 实验零:环境搭建与实验准备 + +!!! tip "代码是一场无声的交流,有些人是优秀的诗人,能够将抽象的想法转化为优雅的语言,而有些人则是忠实的翻译者,将逻辑转换成计算机可理解的语言。" + +## 实验目的 + +1. Rust 学习和巩固,了解标准库提供的基本数据结构和功能。 +2. QEMU 与 Rust 环境搭建,尝试使用 QEMU 启动 UEFI Shell。 +3. 了解 x86 汇编、计算机的启动过程,UEFI 的启动过程,实现 UEFI 下的 `Hello, world!`。 + +## 实验环境 + +我们支持并推荐如下平台进行实验: + +- Ubuntu 22.04 LTS +- Ubuntu 22.04 LTS with WSL 2 +- macOS with Apple Silicon(请自行安装相关依赖) +- 其他可行的平台,但我们不提供技术支持 + +需要安装的基本软件环境,括号中提供在 Ubuntu 中对应的包名: + +- QEMU x86_64 (qemu-system-x86) +- Rust nightly toolchain (rustup) +- make, gcc, gdb 等基本编译工具 (build-essential) + +## 实验基础知识 + +!!! note "善用 LLM 进行学习" + + 对于现代计算机专业的学生,我们建议并要求大家学习借助 LLM(Large Language Model)进行学习,这是一种非常有效的学习方法,可以帮助你更快的学习到知识。 + + 对于不理解的知识点和概念,我们建议优先参考文档、借助 LLM 进行实践,在仍然无法解决的情况下再向他人提问。 + +对于本次实验内容,你需要参考学习如下实验资料: + +- [Linux 使用指导](../../wiki/linux.md) +- [Rust 语言学习](../../wiki/rust.md) +- [UEFI 启动过程](../../wiki/uefi.md) +- [QEMU 使用参考](../../wiki/qemu.md) + +## 实验任务与要求 + +1. 请各位同学独立完成作业,任何抄袭行为都将使本次作业判为 0 分。 +2. 请参考 [代码规范](../../general/coding_convention.md) 进行实验代码编写。 +3. 依据 [实验任务](./tasks.md) 完成实验。 diff --git a/docs/labs/0x00/tasks.md b/docs/labs/0x00/tasks.md new file mode 100644 index 0000000..f6600d5 --- /dev/null +++ b/docs/labs/0x00/tasks.md @@ -0,0 +1,344 @@ +# 实验零:环境搭建与实验准备 + +!!! warning "在执行每一条命令前,请你对将要进行的操作进行思考" + + **为了你的数据安全和不必要的麻烦,请谨慎使用 `sudo`,并确保你了解每一条指令的含义。** + + **1. 实验文档给出的命令不需要全部执行** + + **2. 不是所有的命令都可以无条件执行** + + **3. 不要直接复制粘贴命令执行** + +## 安装 Linux 系统和项目开发环境 + +Linux 有许多发行版,这里出于环境一致性考虑,推荐使用 Ubuntu 22.04。 + +其他发行版(如 Debian,Arch,Kali)也可以满足实验需求,但**请注意内核版本、QEMU 版本都不应低于本次实验的参考标准**。 + +### 使用 WSL2 + +对于 Windows 10/11 的用户来说,可以使用 WSL(Windows Subsystem Linux)来安装 Linux 系统,WSL 意为面向 Windows 的 Linux 子系统,微软为其提供了很多特性方便我们使用,我们可以在 Windows 上运行 Linux 程序。 + +你可以使用如下指令在 Windows 上安装 WSL2: + +```bash +wsl --install -d Ubuntu +``` + +上述指令将会安装 WSL2 的全部依赖,并下载 Ubuntu 作为默认的发行版本。在安装过程中可能会重启电脑,安装完成后,你可以在 Windows 的应用列表中找到 Ubuntu,点击运行即可。 + +关于其他的配置,可以在网上找到大量的参考资料,请自行搜索阅读,或寻求 LLM 的帮助。 + +### 使用 Vmware Workstation + +参考 [Vmware Workstation 安装 Ubuntu 22.04 LTS](https://zhuanlan.zhihu.com/p/569274366) 教程。 + +### 安装项目开发环境 + +在正确安装 Linux 系统后,我们需要安装和配置开发环境,包括 gcc, make, qemu 等。 + +为了保障 Linux 软件源的正常、快速访问,请参考 [Ubuntu 软件仓库镜像使用帮助](https://help.mirrors.cernet.edu.cn/ubuntu/) 提供的文档进行软件源更换。 + +!!! note "校内镜像源" + + 我们还提供有**仅供校内、不确保一定可用**的内网镜像源:[matrix 镜像站](https://mirrors.matrix.moe) + + 请注意,使用上述镜像站会让你享受到更好的下载速度,但你同时也需要**承担不可用时带来的风险,并具有自主更换到其他镜像站的能力**。 + +1. 使用以下命令更新 apt 源: + + ```bash + sudo apt update && sudo apt upgrade + ``` + +2. 安装 qemu 等工具: + + ```bash + sudo apt install qemu-system-x86 build-essential gdb + ``` + +3. 安装 rustup: + + !!! note "rustup 安装过程中存在一些可配置选项,请按照默认选项进行安装。" + + ```bash + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + source "$HOME/.cargo/env" + ``` + +在安装完成后,请使用如下命令,确保你的相关软件包**不低于**如下标准: + +```bash +$ rustc --version +rustc 1.74.0 (79e9716c9 2023-11-13) + +$ qemu-system-x86_64 --version +QEMU emulator version 6.2.0 (Debian 1:6.2+dfsg-2ubuntu6.15) + +$ gcc --version +gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 + +$ gdb --version +GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1 +``` + +## 尝试使用 Rust 进行编程 + +我们预留了一些 Rust 编程任务,请你学习 Rust 并尝试在 Linux 环境下实现他们。 + +!!! tip "在你不熟悉新语言的时候,我们非常推荐你借助 LLM 进行学习。" + +1. 写一个控制行程序,完成下列任务: + + 1. 进行一个 5s 的倒计时,过程中输出剩余秒数,并在最后输出 `Hello World!`。 + 2. 读取并输出 `/etc/hosts`,如果此文件不存在,则程序主动 panic。 + 3. 接收一个字符串,并尝试打开字符串所指出的文件,如果文件不存在,输出 `File not found!`。 + +2. 实现一个进行字节数转换的函数,并格式化输出: + + 1. 实现函数 `humanized_size(size: u64) -> (f64, &'static str)` 将字节数转换为人类可读的大小和单位 + + 使用 1024 进制,并使用二进制前缀(B, KiB, MiB, GiB)作为单位 + + 2. 补全格式化代码,使得你的实现能够通过如下测试: + + ```rust + #[test] + fn test_humanized_size() { + let byte_size = 1554056; + let (size, unit) = humanized_size(byte_size); + assert_eq!("Size : 1.4821 MiB", format!(/* FIXME */)); + } + ``` + +3. **自行搜索学习如何利用现有的 crate** 在终端中输出彩色的文字 + + 输出一些带有颜色的字符串,并尝试直接使用 `print!` 宏输出一到两个相同的效果。 + + !!! tip "如果你想进一步了解,可以尝试搜索 **ANSI 转义序列**" + + +4. 使用 `enum` 对类型实现同一化 + + 实现一个 `enum Shape` 并实现一个函数 `fn area(shape: Shape) -> f64`,使得 `area` 函数能够计算 `Shape` 的面积。 + + - 你可能需要使用模式匹配来达到相应的功能 + - 请实现 `Rectangle` 和 `Circle` 两种 `Shape`,并使得 `area` 函数能够正确计算它们的面积 + - 使得你的实现能够通过如下测试: + + ```rust + #[test] + fn test_area() { + let rectangle = Shape::Rectangle { + width: 10.0, + height: 20.0, + }; + let circle = Shape::Circle { radius: 10.0 }; + + assert_eq!(area(rectangle), 200.0); + assert_eq!(area(circle), 314.1592653589793); + } + ``` + +5. 实现一个元组结构体 `UniqueId(u16)` + + 使得每次调用 `UniqueId::new()` 时总会得到一个新的不重复的 `UniqueId`。 + + - 你可以在函数体中定义 `static` 变量来存储一些全局状态 + - 你可以尝试使用 `std::sync::atomic::AtomicU16` 来确保多线程下的正确性(无需进行验证) + - 使得你的实现能够通过如下测试: + + ```rust + #[test] + fn test_unique_id() { + let id1 = UniqueId::new(); + let id2 = UniqueId::new(); + assert_ne!(id1, id2); + } + ``` + + + +## 运行 UEFI Shell + +### 初始化你的仓库 + +本实验设计存在一定的**前后依赖关系**,你可能需要在实验过程中自己逐步构建自己的操作系统。 + +为了更好的管理你的代码、更好的展示你的进度,建议使用 git 来管理本次实验代码。 + +!!! note "请注意,git 可以离线使用,我们并不要求你将代码上传到远程仓库。" + +1. 克隆本仓库到本地: + + ```bash + $ git clone https://github.com/YatSenOS/YatSenOS-Tutorial-Volume-2 + ``` + +2. 参考[实验 0x00 参考代码](https://github.com/YatSenOS/YatSenOS-Tutorial-Volume-2/tree/main/src/0x00/)的文件结构,初始化你的仓库。 + + 选择一个合适的目录,并拷贝此文件夹的内容到你的仓库中: + + !!! warning "不要直接运行如下代码,选择你自己的工作文件夹" + + ```bash + $ cp -Lr YatSenOS-Tutorial-Volume-2/src/0x00 /path/to/your/workdir + ``` + + !!! note "我们使用 `/path/to/your/workdir` 指代你自己的工作区,**请将其替换为你自己的工作区路径**" + +3. 初始化你的仓库: + + ```bash + $ cd /path/to/your/workdir + $ git init + $ git add . + $ git commit -m "init" + ``` + +4. 通过如下方式校验文件是否完整: + + ```bash + $ git ls-tree --full-tree -r --name-only HEAD + .gitignore + Cargo.toml + Makefile + assets/OVMF.fd + pkg/boot/.cargo/config + pkg/boot/Cargo.toml + pkg/boot/src/main.rs + rust-toolchain.toml + ``` + +### 使用 QEMU 启动 UEFI Shell + +UEFI Shell 是一个基于 UEFI 的命令行工具,它可以让我们在 UEFI 环境下进行一些简单的操作。 + +在不挂载任何硬盘的情况下,我们可以使用如下命令启动 UEFI Shell: + +!!! note "OVMF 是面向虚拟机的 UEFI 固件,参考 [UEFI 使用参考](../../wiki/uefi.md#ovmf)" + +```bash +qemu-system-x86_64 -bios ./assets/OVMF.fd -net none -nographic +``` + +!!! note "QEMU 的相关参数含义,参考 [QEMU 使用参考](../../wiki/qemu.md)" + +你将会看到如下输出: + +```log +UEFI Interactive Shell v2.2 +EDK II +UEFI v2.70 (EDK II, 0x00010000) +Mapping table + BLK0: Alias(s): + PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0) +Press ESC in 4 seconds to skip startup.nsh or any other key to continue. +Shell> +``` + +!!! tip "使用 Ctrl + A 后输入 X 可以退出 QEMU" + +## YSOS 启动! + +### 配置 Rust Toolchain + +仓库提供的 `rust-toolchain.toml` 文件指定了需要使用的 Rust 工具链版本: + +```toml +[toolchain] +channel = "nightly" +profile = "minimal" +components = [ "rust-src", "rustfmt", "clippy" ] +targets = [ "x86_64-unknown-uefi" ] +``` + +为了编译 UEFI 程序,我们需要使用 `x86_64-unknown-uefi` 编译目标。 + +同时,我们需要使用 `rust-src` 组件来编译标准库,使用 `rustfmt` 组件来格式化代码,使用 `clippy` 组件来获取一些代码编写建议。 + +为了编译内核和启用一些面向裸机的特性,我们需要使用 `nightly` 版本的 Rust 工具链。 + +在配置好的工作区中执行编译时,Rust 会自动下载并安装对应的工具链。 + +### 运行第一个 UEFI 程序 + +编译一个 UEFI 程序时,我们没有操作系统所提供的标准库,也没有操作系统提供的 Interpreter,因此我们需要使用 `#![no_std]` 来声明我们的程序不依赖标准库,使用 `#![no_main]` 来声明我们的程序不依赖操作系统的入口函数。 + +同时,我们需要使用 `core` 和 `alloc` crate 来提供一些基本的数据结构和功能,使用 `uefi` crate 来提供 UEFI 程序运行时所需要的各种信息。 + +有关 [core](https://docs.rs/core/) crate 的介绍: + +> **The Rust Core Library** +> +> The Rust Core Library is the **dependency-free** foundation of The **Rust Standard Library**. It is the portable glue between the language and its libraries, **defining the intrinsic and primitive building blocks of all Rust code**. It links to no upstream libraries, no system libraries, and no libc. +> +> The core library is minimal: **it isn’t even aware of heap allocation**, nor does it provide concurrency or I/O. These things require platform integration, and this library is **platform-agnostic**. + +有关 [alloc](https://docs.rs/alloc/) crate 的介绍: + +> **The Rust core allocation and collections library** +> This library provides smart pointers and collections for managing heap-allocated values. +> +> This library, like core, normally doesn’t need to be used directly since its contents are re-exported in the std crate. Crates that use the `#![no_std]` attribute however will typically not depend on std, so they’d use this crate instead. + +有关 [uefi](https://docs.rs/uefi/) crate 的介绍: + +> Rusty wrapper for the [Unified Extensible Firmware Interface](https://uefi.org). +> +> See the [Rust UEFI Book](https://rust-osdev.github.io/uefi-rs/HEAD/) for a tutorial, how-tos, and overviews of some important UEFI concepts. For more details of UEFI, see the latest [UEFI Specification](https://uefi.org/specifications). + +!!! note "获取详细信息,参考 [Rust 语言基础](../../wiki/rust.md#善用-docsrs)" + +在 `pkg/boot/src/main.rs` 中,完善如下的代码,修改注释部分,使用你的学号进行输出: + +```rust +#![no_std] +#![no_main] + +#[macro_use] +extern crate log; +extern crate alloc; + +use core::arch::asm; +use uefi::prelude::*; + +#[entry] +fn efi_main(image: uefi::Handle, mut system_table: SystemTable) -> Status { + uefi_services::init(&mut system_table).expect("Failed to initialize utilities"); + log::set_max_level(log::LevelFilter::Info); + + let std_num = /* FIXME */; + + loop { + info!("Hello World from UEFI bootloader! @ {}", std_num); + + for _ in 0..0x10000000 { + unsafe { + asm!("nop"); + } + } + } +} +``` + +`efi_main` 通过 `#[entry]` 被指定为 UEFI 程序的入口函数,`efi_main` 函数的参数 `system_table` 是一个 `SystemTable` 类型的变量,它包含了 UEFI 程序运行时所需要的各种信息,如内存映射、文件系统、图形界面等。 + +在 `efi_main` 函数中,首先对 `system_table` 和 `log` 进行初始化,然后进入一个死循环,每次循环输出一条日志后等待一段时间。 + +在项目根目录下运行 `make run`,预期得到如下输出: + +```bash +[ INFO]: pkg/boot/src/main.rs@017: Hello World from UEFI bootloader! +[ INFO]: pkg/boot/src/main.rs@017: Hello World from UEFI bootloader! +``` + +至此,你已经做好了编写 OS 的准备工作。 + +## 思考题 + +1. 了解现代操作系统(Windows)的启动过程,`legacy BIOS` 和 `UEFI` 的区别是什么? +2. 尝试解释 Makefile 中的命令做了哪些事情? +3. 利用 `cargo` 的包管理和 `docs.rs` 的文档,我们可以很方便的使用第三方库。这些库的源代码在哪里?它们是什么时候被编译的? +4. 为什么我们需要使用 `#[entry]` 而不是直接使用 `main` 函数作为程序的入口? diff --git a/docs/wiki/linux.md b/docs/wiki/linux.md new file mode 100644 index 0000000..fd7b8c8 --- /dev/null +++ b/docs/wiki/linux.md @@ -0,0 +1,15 @@ +# Linux 使用指导 + +Linux 是一个开源的类 Unix 操作系统内核,它是一个典型的多用户、多任务的操作系统,可以运行在各种平台上,如服务器、PC、手机等。常见的 Linux 发行版有 Ubuntu、Debian、Arch、Kali 等。 + +与常规的 GUI 交互方式不同,Linux 系统通常使用命令行来与用户进行交互,命令行是一种通过键入命令来完成与计算机的交互的方式,它可以让用户完成一个操作系统所能提供的一切功能。 + +本次操作系统实验的最终目标也是实现一个能够和用户进行命令行交互的操作系统,因此我们推荐你多多使用命令行来完成实验。 + +你可以通过下面的一些链接来对命令行的使用进行学习,也可以把它们作为参考文档随用随取: + +- [The Missing Semester](https://missing-semester-cn.github.io/2020/shell-tools) +- [UNIX basics tutorial](https://berkeley-scf.github.io/tutorial-unix-basics/) +- [GNU/Linux Command-Line Tools Summary](https://tldp.org/LDP/GNU-Linux-Tools-Summary/html/index.html) +- [「实用技能拾遗」课程 S1 by TonyCrane](https://slides.tonycrane.cc/PracticalSkillsTutorial/2023-spring-cs/#/) +- [「实用技能拾遗」课程 S2 by TonyCrane](https://slides.tonycrane.cc/PracticalSkillsTutorial/2023-fall-ckc/#/) diff --git a/docs/wiki/qemu.md b/docs/wiki/qemu.md new file mode 100644 index 0000000..1a66f6d --- /dev/null +++ b/docs/wiki/qemu.md @@ -0,0 +1,20 @@ +# QEMU 使用参考 + +QEMU 是一个开源的虚拟机软件,它可以模拟多种硬件平台,如 x86、ARM、MIPS 等,可以运行在多种操作系统上,如 Linux、Windows、macOS 等。 + +可以使用类似于如下的命令行运行 QEMU: + +```sh +$ qemu-system-x86_64 -bios ./ovmf.fd -net none \ + -m 96M -drive format=raw,file=fat:rw:./esp -nographic +``` + +其中 `-bios` 指定了 UEFI 的固件,`-net none` 指定了网络设备,`-m` 指定了内存大小,`-drive` 指定了硬盘,`-nographic` 指定了不使用图形界面,转而将串口 IO 重定向到标准输入输出。 + +为了退出 QEMU,可以使用 Ctrl + A 后输入 X 退出。 + +在调试时,可以使用 `-s` 参数来启动 GDB 调试服务,是 `-gdb tcp:1234` 的简写,并使用 `-S` 参数来暂停 CPU 的执行,等待 GDB 连接。 + +当遇到 Triple Fault 时,可以使用 `-no-reboot` 参数来阻止 QEMU 重启。并使用 `-d int,cpu_reset` 参数来打印中断和 CPU 重置的调试信息,这部分对于中断调试很有帮助。 + +可以参考 [官方文档](https://www.qemu.org/docs/master/system/index.html) 获取更多的 QEMU 使用信息。 diff --git a/docs/wiki/rust.md b/docs/wiki/rust.md new file mode 100644 index 0000000..1611088 --- /dev/null +++ b/docs/wiki/rust.md @@ -0,0 +1,50 @@ +# Rust 语言基础 + +## 语言学习 + +Rust 是一门系统编程语言,它有更强的类型检查和内存安全保证,可以避免很多 C/C++ 中常见的内存错误,如缓冲区溢出、空指针引用等。 + +Rust 语言的基础语法可以参考 [Rust圣经](https://course.rs/) 或者 [Rust Programming Language](https://doc.rust-lang.org/book/) 等资料。 + +当熟悉 Rust 的语法与特性后,可以尝试去完成 [Rustlings](https://github.com/rust-lang/rustlings) 的练习,这些练习可以帮助你更好的理解 Rust 语言的特性。 + +其他可参考的学习资料: + +- [Rust 之旅(交互式课程)](https://tourofrust.com/00_zh-cn.html) +- [Rust by Example](https://doc.rust-lang.org/rust-by-example/) +- [Rust Cookbook](https://rust-lang-nursery.github.io/rust-cookbook/) +- [清华的 Rust 课程](https://lab.cs.tsinghua.edu.cn/rust/) + +值得注意的是,本实验内容并不要求你对 Rust 语言有深入的了解,只需要你能够 **理解并使用** Rust 语言的以下内容: + +- **基本语法** + + 变量绑定、常量、表达式、基本类型、条件语句、模式匹配、函数 + +- **所有权与结构化数据** + + 所有权、移动语义、借用与可变引用、结构体、元组结构体、单位元结构体、枚举 + +- **标准库** + + `String`、`Vec`、`Result`、`Option`、错误处理、单元测试 + +- **泛型、特型与生命周期** + + 泛型、特型、标准库提供的常用特性、生命周期入门 + +- **项目管理与常用库** + + Cargo 项目结构、命名规范、智能指针 + +## 善用 `docs.rs` + +Rust 提供了 [docs.rs](https://docs.rs/) 来帮助我们查看 crate 的文档,你可以在其中搜索你需要的 crate,然后查看其文档。 + +在 Rust 中,你可以通过特殊的语法,借助 Markdown 的语法来编写文档,这些内容被称作文档注释,你可以在 [注释和文档](https://course.rs/basic/comment.html) 部分了解到这些内容。 + +这些文档注释会被编译器提取出来,然后生成文档,并转换为 HTML 以供查看。 + +你可以通过上述文档进一步详细了解这些内容。 + +由于 Rust 通过源码进行依赖分发,所以对于你不了解的实现、函数内容,**可以通过编辑器的跳转能力,直接查看源码及其文档的内容**。 diff --git a/docs/wiki/uefi.md b/docs/wiki/uefi.md new file mode 100644 index 0000000..546da66 --- /dev/null +++ b/docs/wiki/uefi.md @@ -0,0 +1,37 @@ +# UEFI 启动过程 + +## 计算机的启动 + +在「计算机组成原理」课程中,各位应该都编写过一个简单的 CPU,而这个 CPU 带有一段预先编写好的程序。在那时,我们的 CPU 只需要支持最简单的固定的外部设备,因此我们可以直接编写程序来满足这些外部设备的需要,并且把程序固化到 CPU 的 ROM 中。 + +但是,现实中的计算机(指 IBM PC)具有多种不同的设备,而 CPU 需要适应多种外部设备和执行环境,因此人们发明了 DDR、PCI、PS/2、USB 等多种不同的通信协议,从而构建了 CPU 和外部设备通信的标准;同时,计算机通过引入 BIOS 这个中间层来实现设备的初始化。 + +在现在的计算机中,主板芯片组(如 AMD B450)会带有一块小型的 ROM,其中存放了初始化计算机的各种设备的代码。主板制造商通过连接 CPU 的地址线,使得芯片组上的程序的入口地址和 CPU 通电后的默认指令地址相同。这样,计算机在启动后就会执行这段代码,来检测内存、初始化主板芯片组、检测设备,而这段代码就被称为 BIOS。 + +在完成了基本的初始化过程后,BIOS 将加载磁盘扇区 0 的内容,放置在 `0x7C00` 地址,然后跳转到该地址执行引导程序。引导程序将完成探测内存布局、加载操作系统内核等工作,并最终进入到操作系统内核中。 + +这里叙述的引导过程有所简化,没有讨论在没有内存的情况下如何完成内存的初始化等内容。知乎用户[老狼](https://www.zhihu.com/people/mikewolfwoo)有一系列文章叙述了计算器 BIOS 和 UEFI 相关的知识,包括 Cache As RAM、可信启动等,各位读者可以简单参考作为补充。 + +## BIOS 与 UEFI + +为了有效地让操作系统(引导程序)能够知晓系统的情况(如内存布局,也就是内存地址的分布情况),BIOS 向操作系统提供了一系列 [系统调用](https://wiki.osdev.org/BIOS),这些调用通过中断触发。当触发中断后,BIOS 之前在 CPU 中注册的中断处理程序就会被执行,从而执行对应的功能。 + +BIOS 本身没有任何的规范定义,而是各个厂家在漫长的历史实践中,建立了一系列约定,使得各个不同厂家生产的计算机能够获得相似的表现。但是,因为缺乏规范,这样的历史实践并不能保证在所有的计算机上表现一致。 + +此外,在 BIOS 启动的引导程序运行时,系统的内存布局存在一个特殊的约定,从而使得引导程序不必获得完整的内存布局就可以执行一些简单的工作。这段内存的大小为 1MB,对于较大的引导程序来说有些紧张。同时,一个扇区的大小只有 512B,这也限制了引导程序的代码大小。因此,现在的引导扇区程序(如 Grub)往往采用两阶段引导的模式,第一阶段称为 Boot,放置在引导扇区中;而第二阶段称为 Loader,放置在磁盘的一个特殊区域;从而突破 BIOS 的代码和内存限制。 + +最后,BIOS 基于中断的函数调用产生了两个问题。其一是,中断调用的效率较低,导致计算机启动缓慢。其二是,引导程序和操作系统为了实现高级功能,往往会注册自己的中断处理程序,这可能和 BIOS 发生冲突。 + +为了更好地解决这一问题,Intel 联合 PC 厂商建立了 UEFI 标准,这是下一代的计算机固件的接口定义。有了 UEFI,操作系统开发人员能够更好地去实现操作系统的功能,编写更加复杂的引导程序,甚至可以在引导程序或固件管理界面访问网络、显卡等高级功能。 + +## OVMF + +OVMF 是一个开源的 UEFI 固件,它是 TianoCore 项目的一部分,由 Intel 发起。 + +OVMF 项目的目标是为虚拟机提供一个 UEFI 固件,从而使得虚拟机能够运行 UEFI 操作系统。 + +OVMF通过实现UEFI规范,使得在虚拟化环境中能够模拟真实硬件的启动过程。它允许虚拟机系统使用像真实计算机一样的固件和引导方式,提供更加真实和高性能的虚拟化体验。 + +OVMF的开源性质使得开发者能够修改和定制固件,以适应不同的虚拟化场景和需求。它在虚拟化和云计算领域广泛应用,为虚拟机提供了更好的启动和运行环境。 + +实验中在仓库中为大家提供了一个 OVMF 固件,位于 `assets/OVMF.fd`。 diff --git a/mkdocs.yml b/mkdocs.yml index 8312d65..ddecfd8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -18,34 +18,56 @@ theme: name: Switch to light mode features: - navigation.top + - navigation.tabs - navigation.footer - navigation.tracking + - content.code.copy + - content.code.select + - content.tabs.link + - toc.follow font: code: JetBrains Mono nav: - - 实验说明: index.md + - 主页: + - 实验说明: index.md + - 代码规范要求: general/coding_convention.md + - 常见问题及解答: general/faq.md - 实验指南: - - 实验零:环境搭建与实验准备: labs/0x00-env.md - - 实验一:操作系统的启动: labs/0x01-boot.md - - 实验二:中断处理: labs/0x02-interrupt.md - - 实验三:内核线程与缺页异常: labs/0x03-kernel-threads-and-page-fault.md - - 实验四:用户程序与系统调用: labs/0x04-userspace.md - - 实验五:fork 的实现、并发与锁机制: labs/0x05-fork-and-lock.md - - 实验六:硬盘驱动与文件系统: labs/0x06-filesystem.md - - 实验七:更好的内存管理: labs/0x07-memory-management.md - - 实验八:扩展实验: labs/0x08-further.md - - 常见问题及解答: faq.md + - 实验零:实验准备: + - 概览: labs/0x00/index.md + - 任务: labs/0x00/tasks.md + - 实验一:操作系统的启动: labs/0x01-boot.md + - 实验二:中断处理: labs/0x02-interrupt.md + - 实验三:内核线程与缺页异常: labs/0x03-kernel-threads-and-page-fault.md + - 实验四:用户程序与系统调用: labs/0x04-userspace.md + - 实验五:fork 与并发: labs/0x05-fork-and-lock.md + - 实验六:硬盘驱动与文件系统: labs/0x06-filesystem.md + - 实验七:更好的内存管理: labs/0x07-memory-management.md + - 实验八:扩展实验: labs/0x08-further.md + - 实验资料: + - Linux 使用指导: wiki/linux.md + - Rust 语言基础: wiki/rust.md + - UEFI 启动过程: wiki/uefi.md + - QEMU 使用参考: wiki/qemu.md markdown_extensions: - attr_list: - - pymdownx.tasklist: - custom_checkbox: true - pymdownx.highlight: linenums: true + use_pygments: true + pygments_lang_class: true - pymdownx.superfences: + - pymdownx.tabbed: + slugify: !!python/object/apply:pymdownx.slugs.slugify {kwds: {case: lower}} + alternate_style: true - pymdownx.arithmatex: generic: true + - admonition: + - toc: + permalink: true + slugify: !!python/object/apply:pymdownx.slugs.slugify {kwds: {case: lower}} + extra_javascript: - scripts/mathjax.js diff --git a/src/0x00/.gitignore b/src/0x00/.gitignore new file mode 100644 index 0000000..bc64b0a --- /dev/null +++ b/src/0x00/.gitignore @@ -0,0 +1,5 @@ +/esp +/.vscode +**/target +**/.gdb_history +**/.DS_Store diff --git a/src/0x00/Cargo.toml b/src/0x00/Cargo.toml new file mode 100644 index 0000000..37a8dd0 --- /dev/null +++ b/src/0x00/Cargo.toml @@ -0,0 +1,12 @@ +[workspace] +resolver = "2" +members = [ + "pkg/boot", +] + +[profile.release-with-debug] +inherits = "release" +debug = true + +[profile.release-with-debug.package."*"] +debug = false diff --git a/src/0x00/Makefile b/src/0x00/Makefile new file mode 100644 index 0000000..8b3cc76 --- /dev/null +++ b/src/0x00/Makefile @@ -0,0 +1,57 @@ +OVMF := assets/OVMF.fd +ESP := esp +BUILD_ARGS := +QEMU_ARGS := -m 64M +QEMU_OUTPUT := -nographic +MODE ?= release +CUR_PATH := $(shell pwd) +DBG_INFO := false + +ifeq (${MODE}, release) + BUILD_ARGS := --release +endif + +.PHONY: build run debug clean launch intdbg \ + target/x86_64-unknown-uefi/$(MODE)/ysos_boot.efi + +run: build launch + +launch: + @qemu-system-x86_64 \ + -bios ${OVMF} \ + -net none \ + $(QEMU_ARGS) \ + $(QEMU_OUTPUT) \ + -drive format=raw,file=fat:rw:${ESP} + +intdbg: + @qemu-system-x86_64 \ + -bios ${OVMF} \ + -net none \ + $(QEMU_ARGS) \ + $(QEMU_OUTPUT) \ + -drive format=raw,file=fat:rw:${ESP} \ + -no-reboot -d int,cpu_reset + +debug: + @qemu-system-x86_64 \ + -bios ${OVMF} \ + -net none \ + $(QEMU_ARGS) \ + $(QEMU_OUTPUT) \ + -drive format=raw,file=fat:rw:${ESP} \ + -s -S + +clean: + @cargo clean + +build: $(ESP) + +$(ESP): $(ESP)/EFI/BOOT/BOOTX64.EFI + +$(ESP)/EFI/BOOT/BOOTX64.EFI: target/x86_64-unknown-uefi/$(MODE)/ysos_boot.efi + @mkdir -p $(@D) + cp $< $@ + +target/x86_64-unknown-uefi/$(MODE)/ysos_boot.efi: pkg/boot + cd pkg/boot && cargo build $(BUILD_ARGS) diff --git a/src/0x00/assets/OVMF.fd b/src/0x00/assets/OVMF.fd new file mode 120000 index 0000000..be14a62 --- /dev/null +++ b/src/0x00/assets/OVMF.fd @@ -0,0 +1 @@ +../../../assets/OVMF.fd \ No newline at end of file diff --git a/src/0x00/pkg/boot/.cargo/config b/src/0x00/pkg/boot/.cargo/config new file mode 100644 index 0000000..9cd8b31 --- /dev/null +++ b/src/0x00/pkg/boot/.cargo/config @@ -0,0 +1,6 @@ +[build] +target = "x86_64-unknown-uefi" + +[unstable] +build-std-features = ["compiler-builtins-mem"] +build-std = ["core", "compiler_builtins", "alloc"] diff --git a/src/0x00/pkg/boot/Cargo.toml b/src/0x00/pkg/boot/Cargo.toml new file mode 100644 index 0000000..cc9298e --- /dev/null +++ b/src/0x00/pkg/boot/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "ysos_boot" +version = "0.0.1" +edition = "2021" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +uefi = "0.26" +uefi-services = { version = "0.23" } +log = "0.4" diff --git a/src/0x00/pkg/boot/src/main.rs b/src/0x00/pkg/boot/src/main.rs new file mode 100644 index 0000000..06cd5d5 --- /dev/null +++ b/src/0x00/pkg/boot/src/main.rs @@ -0,0 +1,4 @@ +#![no_std] +#![no_main] + +// TODO diff --git a/src/0x00/rust-toolchain.toml b/src/0x00/rust-toolchain.toml new file mode 100644 index 0000000..7e0a9ea --- /dev/null +++ b/src/0x00/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "nightly" +profile = "minimal" +components = [ "rust-src", "rustfmt", "clippy" ] +targets = [ "x86_64-unknown-uefi" ]