diff --git a/source/appendix-a/index.rst b/source/appendix-a/index.rst index 7f264f33..5712a93a 100644 --- a/source/appendix-a/index.rst +++ b/source/appendix-a/index.rst @@ -15,7 +15,7 @@ .. **Rust 语法卡片:迭代器与闭包** -.. 代码第 7 行用到了 Rust 的迭代器与闭包的语法,它们在很多情况下能够提高开发效率。如读者感兴趣的话也可以将其改写为等价的 for +.. 代码第 7 行用到了 Rust 的迭代器与闭包的语法,它们在很多情况下能够提高开发效率。如同学感兴趣的话也可以将其改写为等价的 for .. 循环实现。 .. .. _term-raw-pointer: diff --git a/source/appendix-b/index.rst b/source/appendix-b/index.rst index f1da0afa..1c3d3f8d 100644 --- a/source/appendix-b/index.rst +++ b/source/appendix-b/index.rst @@ -353,7 +353,7 @@ k210 平台上可执行文件和二进制镜像的生成流程 - 第 13 行我们使用 ``dd`` 工具将 bootloader 和二进制镜像拼接到一起,这是因为 k210 平台的写入工具每次只支持写入一个文件,所以我们只能 将二者合并到一起一并写入 k210 的内存上。这样的参数设置可以保证 bootloader 在合并后文件的开头,而二进制镜像在文件偏移量 0x20000 的 - 位置处。有兴趣的读者可以输入命令 ``man dd`` 查看关于工具 ``dd`` 的更多信息。 + 位置处。有兴趣的同学可以输入命令 ``man dd`` 查看关于工具 ``dd`` 的更多信息。 - 第 16 行我们使用烧写工具 ``K210-BURNER`` 将合并后的镜像烧写到 k210 开发板的内存的 ``0x80000000`` 地址上。 参数 ``K210-SERIALPORT`` 表示当前 OS 识别到的 k210 开发板的串口设备名。在 Ubuntu 平台上一般为 ``/dev/ttyUSB0``。 - 第 17 行我们打开串口终端和 k210 开发板进行通信,可以通过键盘向 k210 开发板发送字符并在屏幕上看到 k210 开发板的字符输出。 diff --git a/source/chapter0/0intro.rst b/source/chapter0/0intro.rst index 11663650..4845973b 100644 --- a/source/chapter0/0intro.rst +++ b/source/chapter0/0intro.rst @@ -17,9 +17,9 @@ Remzi H. Arpaci-Dusseau 和 Andrea C. Arpaci-Dusseau 的《Operating Systems: Th 为应对“忽视硬件细节或用复杂硬件”的问题,我们在硬件(x86, ARM, MIPS, RISC-V 等)和编程语言(C, C++, Go, Rust 等)选择方面进行了多年尝试。在 2017 年把 复杂 x86 架构换为 简洁 RISC-V 架构,作为操作系统实验的硬件环境,降低了学生学习硬件细节的负担。在 2018 年引入 Rust 编程语言作为开发操作系统的可选编程语言之一,减少了用C语言编程出现较多运行时缺陷的情况。使得学生以相对较小的开发和调试代价进行操作系统实验。同时,我们把操作系统的概念和原理直接对应到程序代码、硬件规范和操作系统的实际执行中,加强学生对操作系统内涵的实际体验和感受。 -所以本书的目标是以简洁的 RISC-V 架构为底层硬件基础,根据上层应用从小到大的需求,按 OS 发展的历史脉络,逐步讲解如何设计实现能满足“从简单到复杂”应用需求的多个“小”操作系统。并且在设计实现操作系统的过程中,逐步解析操作系统各种概念与原理的知识点,做到有“理”可循和有“码”可查,最终让读者通过操作系统设计与实现来深入地掌握操作系统的概念与原理。 +所以本书的目标是以简洁的 RISC-V 架构为底层硬件基础,根据上层应用从小到大的需求,按 OS 发展的历史脉络,逐步讲解如何设计实现能满足“从简单到复杂”应用需求的多个“小”操作系统。并且在设计实现操作系统的过程中,逐步解析操作系统各种概念与原理的知识点,做到有“理”可循和有“码”可查,最终让同学通过操作系统设计与实现来深入地掌握操作系统的概念与原理。 -在具体撰写过程中,第零章是对操作系统的一个概述,让读者对操作系统的历史、定义、特征等概念上有一个大致的了解。后面的每个章节体现了操作系统的一个微缩的历史发展过程,即从对应用由简到繁的支持角度出发,每章会讲解如何设计一个可运行应用的操作系统,满足应用的阶段性需求。从而读者可以通过配套的操作系统设计实验,了解如何从一个微不足道的“小”操作系统,根据应用需求,添加或增强操作系统功能,逐步形成一个类似 UNIX 的相对完善的“小”操作系统。每一步都小到足以让人感觉到易于掌控。而在每一步结束时,你都有一个支持不同应用执行的“小”操作系统。另外,通过足够详尽的测试程序,可以随时验证读者实现的操作系统在每次更新后是否正常工作。由于实验的代码规模和实现复杂度在一个逐步递增的可控范围内,读者可以结合对应于操作系统设计实验的进一步的原理讲解,来建立操作系统概念原理和实际实现的对应关系,从而能够通过操作系统实验的实践过程来加强对理论概念的理解,通过理论概念来进一步指导操作系统实验的实现与改进。 +在具体撰写过程中,第零章是对操作系统的一个概述,让同学对操作系统的历史、定义、特征等概念上有一个大致的了解。后面的每个章节体现了操作系统的一个微缩的历史发展过程,即从对应用由简到繁的支持角度出发,每章会讲解如何设计一个可运行应用的操作系统,满足应用的阶段性需求。从而同学可以通过配套的操作系统设计实验,了解如何从一个微不足道的“小”操作系统,根据应用需求,添加或增强操作系统功能,逐步形成一个类似 UNIX 的相对完善的“小”操作系统。每一步都小到足以让人感觉到易于掌控。而在每一步结束时,你都有一个支持不同应用执行的“小”操作系统。另外,通过足够详尽的测试程序,可以随时验证同学实现的操作系统在每次更新后是否正常工作。由于实验的代码规模和实现复杂度在一个逐步递增的可控范围内,同学可以结合对应于操作系统设计实验的进一步的原理讲解,来建立操作系统概念原理和实际实现的对应关系,从而能够通过操作系统实验的实践过程来加强对理论概念的理解,通过理论概念来进一步指导操作系统实验的实现与改进。 在你开始阅读与实践本书讲解的内容之前,你需要决定用什么编程语言来完成操作系统实验。你可以选择你喜欢的编程语言和你喜欢的CPU上来实现操作系统。我们推荐的编程语言是 Rust ,我们推荐的架构是 RISC-V。 diff --git a/source/chapter0/3os-hw-abstract.rst b/source/chapter0/3os-hw-abstract.rst index 5d46f923..861c0ec7 100644 --- a/source/chapter0/3os-hw-abstract.rst +++ b/source/chapter0/3os-hw-abstract.rst @@ -10,11 +10,11 @@ chyyuu:我觉得需要给出执行环境(EE),Task,...,上下文(函数,trap,task,进程...),执行流等的描述。 并且有一个图,展示这些概念的关系。这些概念能够有链接,指向进一步实际定义或使用的地方。 -接下来读者可站在操作系统实现的角度来看操作系统。操作系统为了能够更好地管理计算机系统并为应用程序提供便捷的服务,在计算机和操作系统的技术研究和发展的过程中,形成了一系列的核心概念,奠定了操作系统内核设计与实现的基础。 +接下来同学可站在操作系统实现的角度来看操作系统。操作系统为了能够更好地管理计算机系统并为应用程序提供便捷的服务,在计算机和操作系统的技术研究和发展的过程中,形成了一系列的核心概念,奠定了操作系统内核设计与实现的基础。 .. note:: - 在本书中,下面的抽象表示不会仅仅就是一个文字的描述,还会在后续章节对具体操作系统设计与运行的讲述中,以具体化的静态数据结构,动态执行对物理/虚拟资源的变化来展示。从而让读者能够建立操作系统抽象概念与操作系统具体实验之间的内在联系。 + 在本书中,下面的抽象表示不会仅仅就是一个文字的描述,还会在后续章节对具体操作系统设计与运行的讲述中,以具体化的静态数据结构,动态执行对物理/虚拟资源的变化来展示。从而让同学能够建立操作系统抽象概念与操作系统具体实验之间的内在联系。 执行环境 ---------------------------------------- @@ -155,7 +155,7 @@ 操作系统内核中去执行。 在RISC-V的特权级规范文档中,“陷入” 包含中断和异常,而原来意义上的陷入(trap,系统调用)只是exception中的一种情况。另外还有一种 “软件中断” ,它是指软件可以通过写特定寄存器(mip/sip)的特定位(MSIP/SSIP/USIP)来产生的中断。而异常和中断有严格的区分,在记录产生的异常或中断类型的特定寄存器(mcause/scause)中,寄存器最高位为 ``0`` 表示异常,最高位为 ``1`` 表示中断。进一步的详细信息可以可参考RISC-V的特权级规范文档和后面的章节。 - 这些都是从不同的视角来阐释中断、陷入和异常,并没有一个唯一精确的解释。对于读者而言,重点是了解这些术语在后续章节的操作系统设计实现中所表示的具体含义和特征。 + 这些都是从不同的视角来阐释中断、陷入和异常,并没有一个唯一精确的解释。对于同学而言,重点是了解这些术语在后续章节的操作系统设计实现中所表示的具体含义和特征。 .. _term_process: diff --git a/source/chapter1/0intro.rst b/source/chapter1/0intro.rst index a52db7db..fa20e089 100644 --- a/source/chapter1/0intro.rst +++ b/source/chapter1/0intro.rst @@ -129,10 +129,10 @@ 本章代码导读 ----------------------------------------------------- -操作系统虽然是软件,但它不是常规的应用软件,需要运行在没有操作系统的裸机环境中。如果采用通常编程方法和编译手段,无法开发出操作系统。其中一个重要的原因编译器编译出的应用软件在缺省情况下是要链接标准库(Rust 编译器和 C 编译器都是这样的),而标准库是依赖于操作系统(如 Linux、Windows 等)的。所以,本章主要是让读者能够脱离常规应用软件开发的思路,理解如何开发没有操作系统支持的操作系统内核。 +操作系统虽然是软件,但它不是常规的应用软件,需要运行在没有操作系统的裸机环境中。如果采用通常编程方法和编译手段,无法开发出操作系统。其中一个重要的原因编译器编译出的应用软件在缺省情况下是要链接标准库(Rust 编译器和 C 编译器都是这样的),而标准库是依赖于操作系统(如 Linux、Windows 等)的。所以,本章主要是让同学能够脱离常规应用软件开发的思路,理解如何开发没有操作系统支持的操作系统内核。 为了做到这一步,首先需要写出不需要标准库的软件并通过编译。为此,先把一般应用所需要的标准库的组件给去掉,这会导致编译失败。然后再逐步添加不需要操作系统的极少的运行时支持代码,让编译器能够正常编译出不需要标准库的正常程序。但此时的程序没有显示输出,更没有输入等,但可以正常通过编译,这样就为进一步扩展程序内容打下了一个 **可正常编译OS** 的前期基础。具体可看 :ref:`移除标准库依赖 ` 一节的内容。 操作系统代码无法像应用软件那样,可以有方便的调试(Debug)功能。这是因为应用之所以能够被调试,也是由于操作系统提供了方便的调试相关的系统调用。而我们不得不再次认识到,需要运行在没有操作系统的裸机环境中,当然没法采用依赖操作系统的传统调试方法了。所以,我们只能采用 ``print`` 这种原始且有效的调试方法。这样,第二步就是让脱离了标准库的软件有输出,这样,我们就能看到程序的运行情况了。为了简单起见,我们可以先在用户态尝试构建没有标准库的支持显示输出的最小运行时执行环境,比较特别的地方在于如何写内嵌汇编完成简单的系统调用。具体可看 :ref:`构建用户态执行环境 ` 一节的内容。 -接下来就是尝试构建可在裸机上支持显示的最小运行时执行环境。相对于用户态执行环境,读者需要能够做更多的事情,比如如何关机,如何配置软件运行所在的物理内存空间,特别是栈空间,如何清除 ``bss`` 段,如何通过 ``RustSBI`` 的 ``SBI_CONSOLE_PUTCHAR`` 接口简洁地实现信息输出。这里比较特别的地方是需要了解 ``linker.ld`` 文件中对OS的代码和数据所在地址空间布局的描述,以及基于RISC-V 64的汇编代码 ``entry.asm`` 如何进行栈的设置和初始化,以及如何跳转到Rust语言编写 ``rust_main`` 主函数中,并开始内核最小运行时执行环境的运行。具体可看 :ref:`构建裸机执行环境 ` 一节的内容。 +接下来就是尝试构建可在裸机上支持显示的最小运行时执行环境。相对于用户态执行环境,同学需要能够做更多的事情,比如如何关机,如何配置软件运行所在的物理内存空间,特别是栈空间,如何清除 ``bss`` 段,如何通过 ``RustSBI`` 的 ``SBI_CONSOLE_PUTCHAR`` 接口简洁地实现信息输出。这里比较特别的地方是需要了解 ``linker.ld`` 文件中对OS的代码和数据所在地址空间布局的描述,以及基于RISC-V 64的汇编代码 ``entry.asm`` 如何进行栈的设置和初始化,以及如何跳转到Rust语言编写 ``rust_main`` 主函数中,并开始内核最小运行时执行环境的运行。具体可看 :ref:`构建裸机执行环境 ` 一节的内容。 diff --git a/source/chapter1/1app-ee-platform.rst b/source/chapter1/1app-ee-platform.rst index 3ff32236..74246d33 100644 --- a/source/chapter1/1app-ee-platform.rst +++ b/source/chapter1/1app-ee-platform.rst @@ -223,7 +223,7 @@ linux-gnu 系统调用支持的版本 ``riscv64gc-unknown-linux-gnu``,是因 - F/D 拓展:提供单/双精度浮点数运算支持。 - C 拓展:提供压缩指令拓展。 - G 拓展是基本整数指令集 I 再加上标准指令集拓展 MAFD 的总称,因此 riscv64gc 也就等同于 riscv64imafdc。我们剩下的内容都基于该处理器架构完成。除此之外 RISC-V 架构还有很多标准指令集拓展,有一些还在持续更新中尚未稳定,有兴趣的读者可以浏览最新版的 RISC-V 指令集规范。 + G 拓展是基本整数指令集 I 再加上标准指令集拓展 MAFD 的总称,因此 riscv64gc 也就等同于 riscv64imafdc。我们剩下的内容都基于该处理器架构完成。除此之外 RISC-V 架构还有很多标准指令集拓展,有一些还在持续更新中尚未稳定,有兴趣的同学可以浏览最新版的 RISC-V 指令集规范。 Rust 标准库与核心库 ---------------------------------- diff --git a/source/chapter2/1rv-privilege.rst b/source/chapter2/1rv-privilege.rst index 705c66dd..981fe8fc 100644 --- a/source/chapter2/1rv-privilege.rst +++ b/source/chapter2/1rv-privilege.rst @@ -96,7 +96,7 @@ RISC-V 架构中一共定义了 4 种特权级: 回顾第一章,当时只是实现了简单的支持单个裸机应用的库级别的“三叶虫”操作系统,它和应用程序全程运行在 S 模式下,应用程序很容易破坏没有任何保护的执行环境--操作系统。而在后续的章节中,我们会涉及到RISC-V的 M/S/U 三种特权级:其中应用程序和用户态支持库运行在 U 模式的最低特权级;操作系统内核运行在 S 模式特权级(在本章表现为一个简单的批处理系统),形成支撑应用程序和用户态支持库的执行环境;而第一章提到的预编译的 bootloader -- ``RustSBI`` 实际上是运行在更底层的 M 模式特权级下的软件,是操作系统内核的执行环境。整个软件系统就由这三层运行在不同特权级下的不同软件组成。 -在特权级相关机制方面,本书正文中我们重点关心RISC-V的 S/U 特权级, M 特权级的机制细节则是作为可选内容在 :doc:`/appendix-c/index` 中讲解,有兴趣的读者可以参考。 +在特权级相关机制方面,本书正文中我们重点关心RISC-V的 S/U 特权级, M 特权级的机制细节则是作为可选内容在 :doc:`/appendix-c/index` 中讲解,有兴趣的同学可以参考。 .. _term-ecf: .. _term-trap: diff --git a/source/chapter2/2application.rst b/source/chapter2/2application.rst index cb9d475f..eb0fb1ff 100644 --- a/source/chapter2/2application.rst +++ b/source/chapter2/2application.rst @@ -204,7 +204,7 @@ Rust 中的 ``asm!`` 宏的完整格式如下: 第 10 行用于告知编译器将我们在程序中给出的嵌入汇编代码保持原样放到最终构建的可执行文件中。如果不这样做的话,编译器可能会把它和其他代码 一视同仁并放在一起进行一些我们期望之外的优化。为了保证语义的正确性,一些比较关键的汇编代码需要加上该选项。 -上面这一段汇编代码的含义和内容与第一章中的 :ref:`第一章中U-Mode应用程序中的系统调用汇编代码 ` 的是一致的,且与 :ref:`第一章中的RustSBI输出到屏幕的SBI调用汇编代码 ` 涉及的汇编指令一样,但传递参数的寄存器的含义是不同的。有兴趣的读者可以回顾第一章的 ``console.rs`` 和 ``sbi.rs`` 。 +上面这一段汇编代码的含义和内容与第一章中的 :ref:`第一章中U-Mode应用程序中的系统调用汇编代码 ` 的是一致的,且与 :ref:`第一章中的RustSBI输出到屏幕的SBI调用汇编代码 ` 涉及的汇编指令一样,但传递参数的寄存器的含义是不同的。有兴趣的同学可以回顾第一章的 ``console.rs`` 和 ``sbi.rs`` 。 .. note:: diff --git a/source/chapter2/3batch-system.rst b/source/chapter2/3batch-system.rst index cc87176e..c5c6d8e8 100644 --- a/source/chapter2/3batch-system.rst +++ b/source/chapter2/3batch-system.rst @@ -84,7 +84,7 @@ 程序的起始地址,最后一个元素放置最后一个应用程序的结束位置。这样每个应用程序的位置都能从该数组中相邻两个元素中得知。这个数组所在的位置 同样也由全局符号 ``_num_app`` 所指示。 -这个文件是在 ``cargo build`` 的时候,由脚本 ``os/build.rs`` 控制生成的。有兴趣的读者可以参考其代码。 +这个文件是在 ``cargo build`` 的时候,由脚本 ``os/build.rs`` 控制生成的。有兴趣的同学可以参考其代码。 找到并加载应用程序二进制码 ----------------------------------------------- diff --git a/source/chapter2/4trap-handling.rst b/source/chapter2/4trap-handling.rst index d4d1e789..d53946e9 100644 --- a/source/chapter2/4trap-handling.rst +++ b/source/chapter2/4trap-handling.rst @@ -145,7 +145,7 @@ CSR,比如 CPU 所在的特权级。我们要保证它们的变化在我们的 当 MODE 字段为 0 的时候, ``stvec`` 被设置为 Direct 模式,此时进入 S 模式的 Trap 无论原因如何,处理 Trap 的入口地址都是 ``BASE<<2`` , CPU 会跳转到这个地方进行异常处理。本书中我们只会将 ``stvec`` 设置为 Direct 模式。而 ``stvec`` 还可以被设置为 Vectored 模式, - 有兴趣的读者可以自行参考 RISC-V 指令集特权级规范。 + 有兴趣的同学可以自行参考 RISC-V 指令集特权级规范。 而当 CPU 完成 Trap 处理准备返回的时候,需要通过一条 S 特权级的特权指令 ``sret`` 来完成,这一条指令具体完成以下功能: @@ -602,7 +602,7 @@ S 特权级,而它希望能够切换到 U 特权级。在 RISC-V 架构中, .. note:: - 有兴趣的读者可以思考: sscratch 是何时被设置为内核栈顶的? + 有兴趣的同学可以思考: sscratch 是何时被设置为内核栈顶的? diff --git a/source/chapter3/0intro.rst b/source/chapter3/0intro.rst index ba7fc3e0..02ae4151 100644 --- a/source/chapter3/0intro.rst +++ b/source/chapter3/0intro.rst @@ -48,10 +48,10 @@ .. hint:: - 读者也许会有疑问:由于只有一个 处理器,即使这样做,同一时间最多还是只能运行一个应用,还浪费了更多的内存来把所有 + 同学也许会有疑问:由于只有一个 处理器,即使这样做,同一时间最多还是只能运行一个应用,还浪费了更多的内存来把所有 的应用都加载进来。那么这样做有什么意义呢? - 读者可以带着这个问题继续看下去。后面我们会介绍这样做到底能够解决什么问题。 + 同学可以带着这个问题继续看下去。后面我们会介绍这样做到底能够解决什么问题。 实践体验 ------------------------------------- diff --git a/source/chapter3/2task-switching.rst b/source/chapter3/2task-switching.rst index 0bc5f7ef..a4efc044 100644 --- a/source/chapter3/2task-switching.rst +++ b/source/chapter3/2task-switching.rst @@ -167,7 +167,7 @@ Trap 控制流在调用 ``__switch`` 之前就需要明确知道即将切换到 仔细观察的话可以发现 ``TaskContext`` 很像一个普通函数栈帧中的内容。正如之前所说, ``__switch`` 的实现除了换栈之外几乎就是一个普通函数,也能在这里得到体现。尽管如此,二者的内涵却有着很大的不同。 -剩下的汇编代码就比较简单了。读者可以自行对照注释看看图示中的后面几个阶段各是如何实现的。另外,后面会出现传给 ``__switch`` 的两个参数相同,也就是某个 Trap 控制流自己切换到自己的情形,请读者对照图示思考目前的实现能否对它进行正确处理。 +剩下的汇编代码就比较简单了。同学可以自行对照注释看看图示中的后面几个阶段各是如何实现的。另外,后面会出现传给 ``__switch`` 的两个参数相同,也就是某个 Trap 控制流自己切换到自己的情形,请同学对照图示思考目前的实现能否对它进行正确处理。 .. chyyuu:有一个内核态切换的例子。 diff --git a/source/chapter3/3multiprogramming.rst b/source/chapter3/3multiprogramming.rst index 966b8fb7..520619b6 100644 --- a/source/chapter3/3multiprogramming.rst +++ b/source/chapter3/3multiprogramming.rst @@ -41,7 +41,7 @@ **sys_yield 的缺点** - 请读者思考一下, ``sys_yield`` 存在哪些缺点? + 请同学思考一下, ``sys_yield`` 存在哪些缺点? 当应用调用它主动交出 CPU 使用权之后,它下一次再被允许使用 CPU 的时间点与内核的调度策略与当前的总体应用执行情况有关,很有可能远远迟于该应用等待的事件(如外设处理完请求)达成的时间点。这就会造成该应用的响应延迟不稳定或者很长。比如,设想一下,敲击键盘之后隔了数分钟之后才能在屏幕上看到字符,这已经超出了人类所能忍受的范畴。但也请不要担心,我们后面会有更加优雅的解决方案。 @@ -220,7 +220,7 @@ panic!("Unreachable in sys_exit!"); } -它的含义是退出当前的应用并切换到下个应用。在调用它之前我们打印应用的退出信息并输出它的退出码。如果是应用出错也应该调用该接口,不过我们这里并没有实现,有兴趣的读者可以尝试。 +它的含义是退出当前的应用并切换到下个应用。在调用它之前我们打印应用的退出信息并输出它的退出码。如果是应用出错也应该调用该接口,不过我们这里并没有实现,有兴趣的同学可以尝试。 那么 ``suspend_current_and_run_next`` 和 ``exit_current_and_run_next`` 各是如何实现的呢? diff --git a/source/chapter3/4time-sharing-system.rst b/source/chapter3/4time-sharing-system.rst index b9b4ae09..a964402c 100644 --- a/source/chapter3/4time-sharing-system.rst +++ b/source/chapter3/4time-sharing-system.rst @@ -145,7 +145,7 @@ RISC-V 的中断可以分成三类: **RISC-V 架构的 U 特权级中断** - 目前,RISC-V 用户态中断作为代号 N 的一个指令集拓展而存在。有兴趣的读者可以阅读最新版的 RISC-V 特权级架构规范一探究竟。 + 目前,RISC-V 用户态中断作为代号 N 的一个指令集拓展而存在。有兴趣的同学可以阅读最新版的 RISC-V 特权级架构规范一探究竟。 时钟中断与计时器 diff --git a/source/chapter4/3sv39-implementation-1.rst b/source/chapter4/3sv39-implementation-1.rst index ec8f1f1b..8984b904 100644 --- a/source/chapter4/3sv39-implementation-1.rst +++ b/source/chapter4/3sv39-implementation-1.rst @@ -240,7 +240,7 @@ usize 的一种简单包装。我们刻意将它们各自抽象出来而不是 `bitflags `_ 是一个 Rust 中常用来比特标志位的 crate 。它提供了 一个 ``bitflags!`` 宏,如上面的代码段所展示的那样,可以将一个 ``u8`` 封装成一个标志位的集合类型,支持一些常见的集合 -运算。它的一些使用细节这里不展开,请读者自行参考它的官方文档。注意,在使用之前我们需要引入该 crate 的依赖: +运算。它的一些使用细节这里不展开,请同学自行参考它的官方文档。注意,在使用之前我们需要引入该 crate 的依赖: .. code-block:: toml @@ -362,7 +362,7 @@ usize 的一种简单包装。我们刻意将它们各自抽象出来而不是 如果后续再插入一个字符串,那么 **至多分配两个新节点** ,因为如果走的路径上有节点已经存在,就无需重复分配了。 这可以说明,字典树中节点的数目(或者说字典树消耗的内存)是随着插入字符串的数目逐渐线性增加的。 -读者可能很好奇,为何在这里要用相当一部分篇幅来介绍字典树呢?事实上 SV39 分页机制等价于一颗字典树。 :math:`27` 位的 +同学可能很好奇,为何在这里要用相当一部分篇幅来介绍字典树呢?事实上 SV39 分页机制等价于一颗字典树。 :math:`27` 位的 虚拟页号可以看成一个长度 :math:`n=3` 的字符串,字符集为 :math:`\alpha=\{0,1,2,...,511\}` ,因为每一位字符都 由 :math:`9` 个比特组成。而我们也不再维护所谓字符串的计数,而是要找到字符串(虚拟页号)对应的页表项。 因此,每个叶节点都需要保存 :math:`512` 个 :math:`8` 字节的页表项,一共正好 :math:`4\text{KiB}` , @@ -396,7 +396,7 @@ usize 的一种简单包装。我们刻意将它们各自抽象出来而不是 **大页** (Huge Page) - 所谓大页就是某些页的大小(如 :math:`2\text{MiB}` , :math:`1\text{GiB}` )大于常规缺省的页大小(如 :math:`4\text{KiB}` )。本教程中并没有用到大页的知识,这里只是作为拓展,不感兴趣的读者可以跳过。 + 所谓大页就是某些页的大小(如 :math:`2\text{MiB}` , :math:`1\text{GiB}` )大于常规缺省的页大小(如 :math:`4\text{KiB}` )。本教程中并没有用到大页的知识,这里只是作为拓展,不感兴趣的同学可以跳过。 RV64处理器在地址转换过程中,只要表项中的 ``V`` 为 1 且 ``R/W/X`` 不全为 0 就会直接从当前的页表项中取出物理页号,再拼接上页内偏移,就完成最终的地址转换。注意这个过程可以发生在多级页表的任意一级。 如果这一过程并没有发生在多级页表的最深层,那么在地址转换的时候,物理页号对应的物理页帧的起始物理地址的位数与页内偏移的位数都和按缺省页处理时的情况不同了。我们需要按 **大页** 的地址转换方式来处理。 @@ -420,7 +420,7 @@ usize 的一种简单包装。我们刻意将它们各自抽象出来而不是 那么 SV39 多级页表相比线性表到底能节省多少内存呢?这里直接给出结论:设某个应用地址空间实际用到的区域总大小为 :math:`S` 字节,则地址空间对应的多级页表消耗内存为 :math:`\frac{S}{512}` 左右。下面给出了详细分析,对此 -不感兴趣的读者可以直接跳过。 +不感兴趣的同学可以直接跳过。 .. note:: diff --git a/source/chapter4/4sv39-implementation-2.rst b/source/chapter4/4sv39-implementation-2.rst index 3df2df3e..7e0e9a74 100644 --- a/source/chapter4/4sv39-implementation-2.rst +++ b/source/chapter4/4sv39-implementation-2.rst @@ -172,7 +172,7 @@ 使得同一时间最多只有一条 Trap 控制流并发访问内核的各数据结构,此时应该是并没有任何数据竞争风险的。那么 加锁的原因其实有两点: - 1. 在不触及 ``unsafe`` 的情况下实现 ``static mut`` 语义。如果读者还有印象, + 1. 在不触及 ``unsafe`` 的情况下实现 ``static mut`` 语义。如果同学还有印象, :ref:`前面章节 ` 我们使用 ``RefCell`` 提供了内部可变性去掉了 声明中的 ``mut`` ,然而麻烦的在于 ``static`` ,在 Rust 中一个类型想被实例化为一个全局变量,则 该类型必须先告知编译器自己某种意义上是线程安全的,这个过程本身是 ``unsafe`` 的。 @@ -182,7 +182,7 @@ 是 Rust 所推荐的。 2. 方便后续拓展到真正存在数据竞争风险的多核环境下运行。 - 这里引入了一些新概念,比如什么是线程,又如何定义线程安全?读者可以先不必深究,暂时有一个初步的概念即可。 + 这里引入了一些新概念,比如什么是线程,又如何定义线程安全?同学可以先不必深究,暂时有一个初步的概念即可。 我们需要添加该 crate 的依赖: @@ -467,7 +467,7 @@ **unsafe 真的就是“不安全”吗?** - 下面是笔者关于 ``unsafe`` 一点可能不太正确的理解,不感兴趣的读者可以跳过。 + 下面是笔者关于 ``unsafe`` 一点可能不太正确的理解,不感兴趣的同学可以跳过。 当我们在 Rust 中使用 unsafe 的时候,并不仅仅是为了绕过编译器检查,更是为了告知编译器和其他看到这段代码的程序员: “ **我保证这样做是安全的** ” 。尽管,严格的 Rust 编译器暂时还不能确信这一点。从规范 Rust 代码编写的角度, @@ -478,7 +478,7 @@ 解释,我们可以将 ``PhysPageNum`` 也看成一种 RAII 的风格,即它控制着一个物理页帧资源的访问。首先,这不会导致 use-after-free 的问题,因为在内核运行全期整块物理内存都是可以访问的,它不存在被释放后无法访问的可能性;其次, 也不会导致并发冲突。注意这不是在 ``PhysPageNum`` 这一层解决的,而是 ``PhysPageNum`` 的使用层要保证任意两个线程 - 不会同时对一个 ``PhysPageNum`` 进行操作。读者也应该可以感觉出这并不能算是一种好的设计,因为这种约束从代码层面是很 + 不会同时对一个 ``PhysPageNum`` 进行操作。同学也应该可以感觉出这并不能算是一种好的设计,因为这种约束从代码层面是很 难直接保证的,而是需要系统内部的某种一致性。虽然如此,它对于我们这个极简的内核而言算是很合适了。 .. chyyuu 上面一段提到了线程??? diff --git a/source/chapter4/5kernel-app-spaces.rst b/source/chapter4/5kernel-app-spaces.rst index c4b983c4..2cecc9bf 100644 --- a/source/chapter4/5kernel-app-spaces.rst +++ b/source/chapter4/5kernel-app-spaces.rst @@ -33,7 +33,7 @@ } 其中 ``VPNRange`` 描述一段虚拟页号的连续区间,表示该逻辑段在地址区间中的位置和长度。它是一个迭代器,可以使用 Rust -的语法糖 for-loop 进行迭代。有兴趣的读者可以参考 ``os/src/mm/address.rs`` 中它的实现。 +的语法糖 for-loop 进行迭代。有兴趣的同学可以参考 ``os/src/mm/address.rs`` 中它的实现。 .. note:: @@ -215,7 +215,7 @@ 总大小,且切片中的数据会被对齐到逻辑段的开头,然后逐页拷贝到实际的物理页帧。 从第 36 行开始的循环会遍历每一个需要拷贝数据的虚拟页面,在数据拷贝完成后会在第 48 行通过调用 ``step`` 方法,该 - 方法来自于 ``os/src/mm/address.rs`` 中为 ``VirtPageNum`` 实现的 ``StepOne`` Trait,感兴趣的读者可以阅读 + 方法来自于 ``os/src/mm/address.rs`` 中为 ``VirtPageNum`` 实现的 ``StepOne`` Trait,感兴趣的同学可以阅读 代码确认其实现。 每个页面的数据拷贝需要确定源 ``src`` 和目标 ``dst`` 两个切片并直接使用 ``copy_from_slice`` 完成复制。当确定 diff --git a/source/chapter5/0intro.rst b/source/chapter5/0intro.rst index 4ac5289a..70f0b1cf 100644 --- a/source/chapter5/0intro.rst +++ b/source/chapter5/0intro.rst @@ -4,7 +4,7 @@ 本章导读 ------------------------------------------- -在正式开始这一章的介绍之前,我们很高兴告诉读者:在前面的章节中基本涵盖了一个功能相对完善的操作系统内核所需的核心硬件机制:中断、特权级、页表,而且我们的应用程序在开发方面也越来越便捷了。但开发者的需求是无穷的,开发者希望能够在操作系统启动后,能灵活选择执行某个程序,但我们目前实现的操作系统还无法支持这样的功能。这说明操作系统还缺少对应用程序动态执行的灵活性和交互性的支持! +在正式开始这一章的介绍之前,我们很高兴告诉同学:在前面的章节中基本涵盖了一个功能相对完善的操作系统内核所需的核心硬件机制:中断、特权级、页表,而且我们的应用程序在开发方面也越来越便捷了。但开发者的需求是无穷的,开发者希望能够在操作系统启动后,能灵活选择执行某个程序,但我们目前实现的操作系统还无法支持这样的功能。这说明操作系统还缺少对应用程序动态执行的灵活性和交互性的支持! 到目前为止,操作系统启动后,能运行完它管理所有的应用程序。但在整个执行过程中,应用程序是被动地被操作系统加载运行,开发者与操作系统之间没有交互,开发者与应用程序之间没有交互,应用程序不能控制其它应用的执行。这使得开发者不能灵活地选择执行某个程序。为了方便开发者灵活执行程序,本章要完成的操作系统的核心目标是: **让开发者能够控制程序的运行** 。 @@ -234,7 +234,7 @@ 首先我们修改运行在应用态的应用软件,它们均放置在 ``user`` 目录下。在新增系统调用的时候,需要在 ``user/src/lib.rs`` 中新增一个 ``sys_*`` 的函数,它的作用是将对应的系统调用按照与内核约定的 ABI 在 ``syscall`` 中转化为一条用于触发系统调用的 ``ecall`` 的指令;还需要在用户库 ``user_lib`` 将 ``sys_*`` 进一步封装成一个应用可以直接调用的与系统调用同名的函数。通过这种方式我们新增三个进程模型中核心的系统调用 ``fork/exec/waitpid`` ,一个查看进程 PID 的系统调用 ``getpid`` ,还有一个允许应用程序获取用户键盘输入的 ``read`` 系统调用。 -基于进程模型,我们在 ``user/src/bin`` 目录下重新实现了一组应用程序。其中有两个特殊的应用程序:用户初始程序 ``initproc.rs`` 和 shell 程序 ``user_shell.rs`` ,可以认为它们位于内核和其他应用程序之间的中间层提供一些基础功能,但是它们仍处于应用层。前者会被内核唯一自动加载、也是最早加载并执行,后者则负责从键盘接收用户输入的应用名并执行对应的应用。剩下的应用从不同层面测试了我们内核实现的正确性,读者可以自行参考。值得一提的是, ``usertests`` 可以按照顺序执行绝大部分应用,会在测试的时候为我们提供很多方便。 +基于进程模型,我们在 ``user/src/bin`` 目录下重新实现了一组应用程序。其中有两个特殊的应用程序:用户初始程序 ``initproc.rs`` 和 shell 程序 ``user_shell.rs`` ,可以认为它们位于内核和其他应用程序之间的中间层提供一些基础功能,但是它们仍处于应用层。前者会被内核唯一自动加载、也是最早加载并执行,后者则负责从键盘接收用户输入的应用名并执行对应的应用。剩下的应用从不同层面测试了我们内核实现的正确性,同学可以自行参考。值得一提的是, ``usertests`` 可以按照顺序执行绝大部分应用,会在测试的时候为我们提供很多方便。 接下来就需要在内核中实现简化版的进程管理机制并支持新增的系统调用。在本章第二小节 :doc:`/chapter5/2core-data-structures` 中我们对一些进程管理机制相关的数据结构进行了重构或者修改: diff --git a/source/chapter5/1process.rst b/source/chapter5/1process.rst index 87c24cb3..828d4764 100644 --- a/source/chapter5/1process.rst +++ b/source/chapter5/1process.rst @@ -134,7 +134,7 @@ exec 系统调用 **为何创建进程要通过两个系统调用而不是一个?** - 读者可能会有疑问,对于要达成执行不同应用的目标,我们为什么不设计一个系统调用接口同时实现创建一个新进程并加载给定的可执行文件两种功能? + 同学可能会有疑问,对于要达成执行不同应用的目标,我们为什么不设计一个系统调用接口同时实现创建一个新进程并加载给定的可执行文件两种功能? 如果使用 ``fork`` 和 ``exec`` 的组合,那么 ``fork`` 出来的进程仅仅是为了 ``exec`` 一个新应用提供空间。而执行 ``fork`` 中对父进程的地址空间拷贝没有用处,还浪费了时间,且在后续清空地址空间的时候还会产生一些资源回收的额外开销。 这样的设计来源于早期的MULTICS [#multics]_ 和UNIX操作系统 [#unix]_ ,在当时是经过实践考验的,事实上 ``fork`` 和 ``exec`` 是一种灵活的系统调用组合,在当时内存空间比较小的情况下,可以支持更快的进程创建,且上述的开销能够通过一些结合虚存的技术方法(如 ``copy on write`` 等)来缓解。而且拆分为两个系统调用后,可以灵活地支持 **重定向** (Redirection) 等功能。 上述方法是UNIX类操作系统的典型做法。 @@ -150,7 +150,7 @@ exec 系统调用 系统调用封装 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -读者可以在 ``user/src/syscall.rs`` 中看到以 ``sys_*`` 开头的系统调用的函数原型,它们后续还会在 ``user/src/lib.rs`` 中被封装成方便应用程序使用的形式。如 ``sys_fork`` 被封装成 ``fork`` ,而 ``sys_exec`` 被封装成 ``exec`` 。这里值得一提的是 ``sys_waitpid`` 被封装成两个不同的 API : +同学可以在 ``user/src/syscall.rs`` 中看到以 ``sys_*`` 开头的系统调用的函数原型,它们后续还会在 ``user/src/lib.rs`` 中被封装成方便应用程序使用的形式。如 ``sys_fork`` 被封装成 ``fork`` ,而 ``sys_exec`` 被封装成 ``exec`` 。这里值得一提的是 ``sys_waitpid`` 被封装成两个不同的 API : .. code-block:: rust :linenos: @@ -389,7 +389,7 @@ shell程序-user_shell 当内核初始化完毕之后,它会从可执行文件 ``initproc`` 中加载并执行用户初始程序-initproc,而用户初始程序-initproc中又会 ``fork`` 并 ``exec`` 来运行shell程序- ``user_shell`` 。这两个应用虽然都是在 CPU 的 U 特权级执行的,但是相比其他应用,它们要更加基础。原则上应该将它们作为一个组件打包在操作系统中。但这里为了实现更加简单,我们并不将它们和其他应用进行区分。 -除此之外,我们还从 :math:`\mu\text{core}` 中借鉴了很多应用测例。它们可以做到同一时间 **并发** 多个进程并能够有效检验我们内核实现的正确性。感兴趣的读者可以参考 ``matrix`` 和 ``forktree`` 等应用。 +除此之外,我们还从 :math:`\mu\text{core}` 中借鉴了很多应用测例。它们可以做到同一时间 **并发** 多个进程并能够有效检验我们内核实现的正确性。感兴趣的同学可以参考 ``matrix`` 和 ``forktree`` 等应用。 .. [#multics] 1965年,MIT、通用电气公司、贝尔实验室联合开发MULTICS操作系统,开发不够成功,但产生了很多新的设计思想,并催生了UNIX操作系统。 diff --git a/source/chapter5/2core-data-structures.rst b/source/chapter5/2core-data-structures.rst index e28ce1be..b8bef392 100644 --- a/source/chapter5/2core-data-structures.rst +++ b/source/chapter5/2core-data-structures.rst @@ -281,7 +281,7 @@ } } -这仅需要为 ``KernelStack`` 实现 ``Drop`` Trait,一旦它的生命周期结束则在内核地址空间中将对应的逻辑段删除(为此在 ``MemorySet`` 中新增了一个名为 ``remove_area_with_start_vpn`` 的方法,感兴趣的读者可以参考其实现),由前面章节的介绍我们知道这也就意味着那些物理页帧被同时回收掉了。 +这仅需要为 ``KernelStack`` 实现 ``Drop`` Trait,一旦它的生命周期结束则在内核地址空间中将对应的逻辑段删除(为此在 ``MemorySet`` 中新增了一个名为 ``remove_area_with_start_vpn`` 的方法,感兴趣的同学可以参考其实现),由前面章节的介绍我们知道这也就意味着那些物理页帧被同时回收掉了。 进程控制块 ------------------------------------------------------------------------ diff --git a/source/chapter6/1file-descriptor.rst b/source/chapter6/1file-descriptor.rst index 00aacae4..54c1aebe 100644 --- a/source/chapter6/1file-descriptor.rst +++ b/source/chapter6/1file-descriptor.rst @@ -67,7 +67,7 @@ } } -它只是将我们调用 ``translated_byte_buffer`` 获得的包含多个切片的 ``Vec`` 进一步包装起来,通过 ``len`` 方法可以得到缓冲区的长度。此外,我们还让它作为一个迭代器可以逐字节进行读写。有兴趣的读者可以参考类型 ``UserBufferIterator`` 还有 ``IntoIterator`` 和 ``Iterator`` 两个 Trait 的使用方法。 +它只是将我们调用 ``translated_byte_buffer`` 获得的包含多个切片的 ``Vec`` 进一步包装起来,通过 ``len`` 方法可以得到缓冲区的长度。此外,我们还让它作为一个迭代器可以逐字节进行读写。有兴趣的同学可以参考类型 ``UserBufferIterator`` 还有 ``IntoIterator`` 和 ``Iterator`` 两个 Trait 的使用方法。 标准输入和标准输出 -------------------------------------------- diff --git a/source/chapter6/2pipe.rst b/source/chapter6/2pipe.rst index c8f02739..2e3e47a5 100644 --- a/source/chapter6/2pipe.rst +++ b/source/chapter6/2pipe.rst @@ -120,7 +120,7 @@ 因此,在第 25 和第 34 行,分别第一时间在子进程中关闭管道的写端和在父进程中关闭管道的读端。父进程在第 35 行将字符串 ``STR`` 写入管道的写端,随后在第 37 行关闭管道的写端;子进程在第 27 行从管道的读端读取字符串,并在第 29 行关闭。 -如果想在父子进程之间实现双向通信,我们就必须创建两个管道。有兴趣的读者可以参考测例 ``pipe_large_test`` 。 +如果想在父子进程之间实现双向通信,我们就必须创建两个管道。有兴趣的同学可以参考测例 ``pipe_large_test`` 。 通过 sys_close 关闭文件 -------------------------------------------- @@ -390,7 +390,7 @@ 如果 ``loop_read`` 不为 0 ,在这一轮次中管道中就有 ``loop_read`` 个字节可以读取。我们可以迭代应用缓冲区中的每个字节指针并调用 ``PipeRingBuffer::read_byte`` 方法来从管道中进行读取。如果这 ``loop_read`` 个字节均被读取之后还没有填满应用缓冲区就需要进入循环的下一个轮次,否则就可以直接返回了。 -``Pipe`` 的 ``write`` 方法——即通过管道的写端向管道中写入数据的实现和 ``read`` 的原理类似,篇幅所限在这里不再赘述,感兴趣的读者可自行参考其实现。 +``Pipe`` 的 ``write`` 方法——即通过管道的写端向管道中写入数据的实现和 ``read`` 的原理类似,篇幅所限在这里不再赘述,感兴趣的同学可自行参考其实现。 小结 diff --git a/source/chapter6/3exercise.rst b/source/chapter6/3exercise.rst index 90aeec91..2f135b12 100644 --- a/source/chapter6/3exercise.rst +++ b/source/chapter6/3exercise.rst @@ -77,7 +77,7 @@ challenge: 支持多核。 (2) 假设我们的邮箱现在有了更加强大的功能,容量大幅增加而且记录邮件来源,可以实现“回信”。考虑一个多核场景,有 m 个核为消费者,n 个为生产者,消费者通过邮箱向生产者提出订单,生产者通过邮箱回信给出产品。 - 假设你的邮箱实现没有使用锁等机制进行保护,在多核情景下可能会发生哪些问题?单核一定不会发生问题吗?为什么? - - 请结合你在课堂上学到的内容,描述读者写者问题的经典解决方案,必要时提供伪代码。 + - 请结合你在课堂上学到的内容,描述同学写者问题的经典解决方案,必要时提供伪代码。 - 由于读写是基于报文的,不是随机读写,你有什么点子来优化邮箱的实现吗? diff --git a/source/chapter7/0intro.rst b/source/chapter7/0intro.rst index b96f75bb..2f8ec889 100644 --- a/source/chapter7/0intro.rst +++ b/source/chapter7/0intro.rst @@ -238,7 +238,7 @@ 本章涉及的代码量相对较多,且与进程执行相关的管理还有直接的关系。其实我们是参考经典的UNIX基于索引的文件系统,设计了一个简化的有一级目录并支持创建/打开/读写/关闭文件一系列操作的文件系统。这里简要介绍一下在内核中添加文件系统的大致开发过程。 -第一步是能够写出与文件访问相关的应用。这里是参考了Linux的创建/打开/读写/关闭文件的系统调用接口,力图实现一个 :ref:`简化版的文件系统模型 ` 。在用户态我们只需要遵从相关系统调用的接口约定,在用户库里完成对应的封装即可。这一过程我们在前面的章节中已经重复过多次,读者应当对其比较熟悉。其中最为关键的是系统调用可以参考 :ref:`sys_open 语义介绍 ` ,此外我们还给出了 :ref:`测例代码解读 ` 。 +第一步是能够写出与文件访问相关的应用。这里是参考了Linux的创建/打开/读写/关闭文件的系统调用接口,力图实现一个 :ref:`简化版的文件系统模型 ` 。在用户态我们只需要遵从相关系统调用的接口约定,在用户库里完成对应的封装即可。这一过程我们在前面的章节中已经重复过多次,同学应当对其比较熟悉。其中最为关键的是系统调用可以参考 :ref:`sys_open 语义介绍 ` ,此外我们还给出了 :ref:`测例代码解读 ` 。 第二步就是要实现 easyfs 文件系统了。由于 Rust 语言的特点,我们可以在用户态实现 easyfs 文件系统,并在用户态完成文件系统功能的基本测试并基本验证其实现正确性之后,就可以放心的将该模块嵌入到操作系统内核中。当然,有了文件系统的具体实现,还需要对上一章的操作系统内核进行扩展,实现与 easyfs 文件系统对接的接口,这样才可以让操作系统拥有一个简单可用的文件系统。这样内核就可以支持具有文件读写功能的复杂应用。当内核进一步支持应用的命令行参数后,就可以进一步提升应用程序的灵活性,让应用的开发和调试变得更为轻松。 diff --git a/source/chapter7/1fs-interface.rst b/source/chapter7/1fs-interface.rst index da850a04..320422bd 100644 --- a/source/chapter7/1fs-interface.rst +++ b/source/chapter7/1fs-interface.rst @@ -12,7 +12,7 @@ 常规文件 +++++++++++++++++++++++++++++++++++++++++++++++++ -在操作系统的用户看来,常规文件是保存在持久存储设备上的一个字节序列,每个常规文件都有一个 **文件名** (Filename) ,用户需要通过它来区分不同的常规文件。方便起见,在下面的描述中,“文件”有可能指的是常规文件、目录,也可能是之前提到的若干种进程可以读写的 标准输出、标准输入、管道等I/O 资源,请读者自行根据上下文判断取哪种含义。 +在操作系统的用户看来,常规文件是保存在持久存储设备上的一个字节序列,每个常规文件都有一个 **文件名** (Filename) ,用户需要通过它来区分不同的常规文件。方便起见,在下面的描述中,“文件”有可能指的是常规文件、目录,也可能是之前提到的若干种进程可以读写的 标准输出、标准输入、管道等I/O 资源,请同学自行根据上下文判断取哪种含义。 在 Linux 系统上, ``stat`` 工具可以获取文件的一些信息。下面以我们项目中的一个源代码文件 ``os/src/main.rs`` 为例: diff --git a/source/chapter7/3using-easy-fs-in-kernel.rst b/source/chapter7/3using-easy-fs-in-kernel.rst index 2d0fe351..fe43b161 100644 --- a/source/chapter7/3using-easy-fs-in-kernel.rst +++ b/source/chapter7/3using-easy-fs-in-kernel.rst @@ -195,7 +195,7 @@ K210 真实硬件平台 在 K210 开发板上,我们可以插入 microSD 卡并将其作为块设备。相比 VirtIO 块设备来说,想要将 microSD 驱动起来是一件比较困难的事情。microSD 自身的通信规范比较复杂,且还需考虑在 K210 中microSD挂在 **串行外设接口** (SPI, Serial Peripheral Interface) 总线上的情况。此外还需要正确设置 GPIO 的管脚映射并调整各锁相环的频率。实际上,在一块小小的芯片中除了 K210 CPU 之外,还集成了很多不同种类的外设和控制模块,它们内在的关联比较紧密,不能像 VirtIO 设备那样容易地从系统中独立出来。 -好在目前 Rust 嵌入式的生态正高速发展,针对 K210 平台也有比较成熟的封装了各类外设接口的库可以用来开发上层应用。但是其功能往往分散为多个 crate ,在使用的时候需要开发者根据需求自行进行组装。这属于 Rust 的特点之一,和 C 语言提供一个一站式的板级开发包风格有很大的不同。在开发的时候,笔者就从社区中选择了一些 crate 并进行了微量修改最终变成 ``k210-hal/k210-pac/k210-soc`` 三个能够运行在 S 特权级(它们的原身仅支持运行在 M 特权级)的 crate ,它们可以更加便捷的实现 microSD 的驱动。关于 microSD 的驱动 ``SDCardWrapper`` 的实现,有兴趣的读者可以参考 ``os/src/drivers/block/sdcard.rs`` 。 +好在目前 Rust 嵌入式的生态正高速发展,针对 K210 平台也有比较成熟的封装了各类外设接口的库可以用来开发上层应用。但是其功能往往分散为多个 crate ,在使用的时候需要开发者根据需求自行进行组装。这属于 Rust 的特点之一,和 C 语言提供一个一站式的板级开发包风格有很大的不同。在开发的时候,笔者就从社区中选择了一些 crate 并进行了微量修改最终变成 ``k210-hal/k210-pac/k210-soc`` 三个能够运行在 S 特权级(它们的原身仅支持运行在 M 特权级)的 crate ,它们可以更加便捷的实现 microSD 的驱动。关于 microSD 的驱动 ``SDCardWrapper`` 的实现,有兴趣的同学可以参考 ``os/src/drivers/block/sdcard.rs`` 。 .. note:: diff --git a/source/chapter7/4cmdargs-and-redirection.rst b/source/chapter7/4cmdargs-and-redirection.rst index 9a8b8af8..cb631093 100644 --- a/source/chapter7/4cmdargs-and-redirection.rst +++ b/source/chapter7/4cmdargs-and-redirection.rst @@ -482,4 +482,4 @@ sys_exec 将命令行参数压入用户栈 虽然 ``fork/exec/waitpid`` 三个经典的系统调用自它们于古老的 UNIX 时代诞生以来已经过去了太长时间,从某种程度上来讲已经不太适合新的内核环境了。人们也已经提出了若干种替代品并已经在进行实践,比如POSIX标准中的 ``posix_spawn`` 或者 Linux 上的 ``clone`` 系统调用。但是它们迄今为止仍然存在就证明在它们的设计中还能够找到可取之处。从本节介绍的重定向就可以看出它们的灵活性以及强大的功能性:我们能够进行重定向恰恰是因为创建新应用进程分为 ``fork`` 和 ``exec`` 两个系统调用,那么在这两个系统调用之间我们就能够进行一些类似重定向的处理。在实现的过程中,我们还用到了 ``fork`` 出来的子进程会和父进程共享文件描述符表的性质。 -至此,我们基本上完成了“霸王龙”操作系统,它具有UNIX的很多核心特征,比如进程管理、虚存管理、文件系统、管道、I/O重定向等,是一个典型的宏内核操作系统。虽然它还缺少很多优化的算法、机制和策略,但我们已经一步一步地建立了一个相对完整的操作系统框架和核心模块实现。在这个过程中,我们经历了从简单到复杂的LibOS、批处理、多道程序、分时多任务、虚存支持、进程支持、文件系统支持等各种操作系统的设计过程,相信读者对操作系统的总体设计也有了一个连贯的多层次的理解。而且我们可以在这个操作系统的框架下,进一步扩展和改进它的设计实现,支持更多的功能并提高性能,这将是我们后续会进一步讲解的内容。 \ No newline at end of file +至此,我们基本上完成了“霸王龙”操作系统,它具有UNIX的很多核心特征,比如进程管理、虚存管理、文件系统、管道、I/O重定向等,是一个典型的宏内核操作系统。虽然它还缺少很多优化的算法、机制和策略,但我们已经一步一步地建立了一个相对完整的操作系统框架和核心模块实现。在这个过程中,我们经历了从简单到复杂的LibOS、批处理、多道程序、分时多任务、虚存支持、进程支持、文件系统支持等各种操作系统的设计过程,相信同学对操作系统的总体设计也有了一个连贯的多层次的理解。而且我们可以在这个操作系统的框架下,进一步扩展和改进它的设计实现,支持更多的功能并提高性能,这将是我们后续会进一步讲解的内容。 \ No newline at end of file