Skip to content

Commit 3591e65

Browse files
committed
fix typos and update description
1 parent 2a6700b commit 3591e65

File tree

9 files changed

+13
-13
lines changed

9 files changed

+13
-13
lines changed

source/chapter1/3first-instruction-in-kernel1.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
- ``-machine virt`` 表示将模拟的 64 位 RISC-V 计算机设置为名为 ``virt`` 的虚拟计算机。我们知道,即使同属同一种指令集架构,也会有很多种不同的计算机配置,比如 CPU 的生产厂商和型号不同,支持的 I/O 外设种类也不同。关于 ``virt`` 平台的更多信息可以参考 [#virt_platform]_ 。Qemu 还支持模拟其他 RISC-V 计算机,其中包括由 SiFive 公司生产的著名的 HiFive Unleashed 开发板。
6262
- ``-nographic`` 表示模拟器不需要提供图形界面,而只需要对外输出字符流。
6363
- 通过 ``-bios`` 可以设置 Qemu 模拟器开机时用来初始化的引导加载程序(bootloader),这里我们使用预编译好的 ``rustsbi-qemu.bin`` ,它需要被放在与 ``os`` 同级的 ``bootloader`` 目录下,该目录可以从每一章的代码分支中获得。
64-
- 通过虚拟设备 ``-device`` 中的 ``loader`` 属性可以在 Qemu 模拟器开机之前将一个宿主机上的文件载入到 Qemu 的物理内存的指定位置中, ``file`` 和 ``addr`` 属性分别可以设置待载入文件的路径以及将文件载入到的 Qemu 物理内存上的物理地址。注意这里我们载入的文件带有 ``.bin`` 后缀,它并不是上一节中我们移除标准库依赖后构建得到的内核可执行文件,而是还要进行加工处理得到内核镜像。我们后面再进行深入讨论
64+
- 通过虚拟设备 ``-device`` 中的 ``loader`` 属性可以在 Qemu 模拟器开机之前将一个宿主机上的文件载入到 Qemu 的物理内存的指定位置中, ``file`` 和 ``addr`` 属性分别可以设置待载入文件的路径以及将文件载入到的 Qemu 物理内存上的物理地址。这里我们载入的 ``os.bin`` 被称为 **内核镜像** ,它会被载入到 Qemu 模拟器内存的 ``0x80200000`` 地址处。 那么内核镜像 ``os.bin`` 是怎么来的呢?上一节中我们移除标准库依赖后会得到一个内核可执行文件 ``os`` ,将其进一步处理就能得到 ``os.bin`` ,具体处理流程我们会在后面深入讨论
6565

6666
Qemu 启动流程
6767
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

source/chapter4/9answer.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434
不同之处:覆盖是程序级的,需要程序员自行处理。交换则不同,由OS控制交换程序段。虚拟内存也由OS和CPU来负责处理,可以实现内存交换到外存的过程。
3535

36-
虚拟存储的优势:1.与段/页式存储完美契合,方便非连续内存分配。2.粒度合适,比较灵活。兼顾了覆盖和交换的好处:可以在较小粒度上置换;自动化程度高,编程简单,受程序本身影响很小。(覆盖的粒度受限于程序模块的大小,对编程技巧要求很高。交换粒度较大,受限于程序所需内存。尤其页式虚拟存储,几乎不受程序影响,一般情况下,只要置换算法合适,表现稳定、高效)3.页式虚拟存储还可以同时解决内存外碎片。提高空间利用率。
36+
虚拟存储的优势:1.与段/页式存储完美契合,方便非连续内存分配。2.粒度合适,比较灵活。兼顾了覆盖和交换的好处:可以在较小粒度上置换;自动化程度高,编程简单,受程序本身影响很小。(覆盖的粒度受限于程序模块的大小,对编程技巧要求很高。交换粒度较大,受限于程序所需内存。尤其页式虚拟存储,几乎不受程序影响,一般情况下,只要置换算法合适,表现稳定、高效)3.页式虚拟存储还可以同时消除内存外碎片并将内碎片限制在一个页面大小以内,提高空间利用率。
3737

3838
虚拟存储的挑战: 1.依赖于置换算法的性能。2.相比于覆盖和交换,需要比较高的硬件支持。3.较小的粒度在面临大规模的置换时会发生多次较小规模置换,降低效率。典型情况是程序第一次执行时的大量page fault,可配合预取技术缓解这一问题。
3939

source/chapter5/4scheduling.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ Corbato教授的思路很巧妙,用四个字来总结,就是 **以史为鉴*
262262
固定优先级的多级无反馈队列
263263
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
264264

265-
MLFQ调度策略的关键在于如何设置优先级。一旦设置进程的好优先级,MLFQ总是优先执行唯有高优先级就绪队列中的进程。对于挂在同一优先级就绪队列中的进程,采用轮转调度策略。
265+
MLFQ调度策略的关键在于如何设置优先级。一旦设置好进程的优先级,MLFQ总是优先执行位于高优先级就绪队列中的进程。对于挂在同一优先级就绪队列中的进程,采用轮转调度策略。
266266

267267
先考虑简单情况下,如果我们提前知道某些进程是I/O密集型的,某些进程是CPU密集型的,那么我们可以给I/O密集型设置高优先级,而CPU密集型进程设置低优先级。这样就绪队列就变成了两个,一个包含I/O密集型进程的高优先级队列,一个是处理器密集型的低优先级队列。
268268

@@ -582,7 +582,7 @@ t1: CurID = CurID+1 CurID = CurID +1
582582
处理器1: A C A A C A ...
583583
处理器2: C D D C D D ...
584584

585-
当然,这个例子是一种简单的理想情况,实际的多处理器计算机系统中运行的进程行为会很复杂,除了并行执行,还有同步互斥执行、各种/O操作等,这些都会对调度策略产生影响。
585+
当然,这个例子是一种简单的理想情况,实际的多处理器计算机系统中运行的进程行为会很复杂,除了并行执行,还有同步互斥执行、各种I/O操作等,这些都会对调度策略产生影响。
586586

587587
小结
588588
----------------------------------

source/chapter8/2lock.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ Rust 在标准库中提供了互斥锁 ``std::sync::Mutex<T>`` ,它可以包
385385

386386
- 忙则等待:意思是当一个线程持有了共享资源的锁,此时资源处于繁忙状态,这个时候其他线程必须等待拿着锁的线程将锁释放后才有进入临界区的机会。这其实就是互斥访问的另一种说法。这种互斥性是锁实现中最重要的也是必须做到的目标,不然共享资源访问的正确性会受到影响。
387387
- **空闲则入** (在《操作系统概念》一书中也被称为 **前进** Progress):若资源处于空闲状态且有若干线程尝试进入临界区,那么一定能够在有限时间内从这些线程中选出一个进入临界区。如果不满足空闲则入的话,可能导致即使资源空闲也没有线程能够进入临界区,对于锁来说是不可接受的。
388-
- **有界等待** (Bounded Waiting):当线程获取锁失败的时候首先需要等待锁被释放,但这并不意味着此后它能够立即抢到被释放的锁,因此此时可能还有其他的线程也处于等待状态。于是它可能需要等待一轮、二轮、多轮才能拿到锁,甚至在极端情况下永远拿不到锁。 **有界等待** 要求每个线程在等待有限长时间后最终总能够拿到锁。相对的,线程可能永远无法拿到锁的情况被称之为 **饥饿** (Starvation) 。这体现了锁实现分配共享资源给线程的 **公平性** (Fairness) 。
388+
- **有界等待** (Bounded Waiting):当线程获取锁失败的时候首先需要等待锁被释放,但这并不意味着此后它能够立即抢到被释放的锁,因为此时可能还有其他的线程也处于等待状态。于是它可能需要等待一轮、二轮、多轮才能拿到锁,甚至在极端情况下永远拿不到锁。 **有界等待** 要求每个线程在等待有限长时间后最终总能够拿到锁。相对的,线程可能永远无法拿到锁的情况被称之为 **饥饿** (Starvation) 。这体现了锁实现分配共享资源给线程的 **公平性** (Fairness) 。
389389
- 让权等待(可选):线程如何进行等待实际上也大有学问。这里所说的让权等待是指需要等待的线程暂时主动或被动交出 CPU 使用权来让 CPU 做一些有意义的事情,这通常需要操作系统的支持。这样可以提升系统的总体效率。
390390

391391
总的来说,忙则等待、空闲则入和有界等待是一个合格的锁实现必须满足的要求,而让权等待则关系到锁机制的效率,是可选的。

source/chapter8/5concurrency-problem.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
println!("thr2: A is One --> {}", A);
3131
}
3232
33-
A是共享变量。粗略地看,可以估计执行流程为:第一个线程thr1检查A的值,如果为0,则显示“"thr1: A is Zero --> 0”;第二个线程thr2设置A的值为2,并显示"thr2: A is One --> 1”。但如果线程thr1执行完第4行代码,准备执行第5行代码前发生了线程切换,开始执行线程th2;当线程thr2完成第10行后,操作系统有切换回线程thr1继续执行,那么线程thr1就会输出“thr1: A is Zero --> 1” 这样的奇怪结果。
33+
A是共享变量。粗略地看,可以估计执行流程为:第一个线程thr1检查A的值,如果为0,则显示“"thr1: A is Zero --> 0”;第二个线程thr2将A的值由0设置为1(因为除了初始化之外没有其他地方修改了A的值),并显示"thr2: A is One --> 1”。但如果线程thr1执行完第4行代码,准备执行第5行代码前发生了线程切换,开始执行线程th2;当线程thr2完成第10行后,操作系统又切换回线程thr1继续执行,那么线程thr1就会输出“thr1: A is Zero --> 1” 这样的奇怪结果。
3434

3535
这里出现问题的根源是线程在对共享变量进行访问时,违反了临界区的互斥性(原子性)原则。解决这样的问题需要给共享变量的访问加锁,确保每个线程访问共享变量时,都持有锁,修改后的代码如下:
3636

@@ -256,7 +256,7 @@ Need[i,j] = Max[i,j] - allocation[i, j]
256256
257257
跳转回步骤2
258258

259-
4. 如果Finish[0..n-1] 都为true,则表示系统处于安全状态;否则表示系统处于不安全状态。
259+
4. 如果Finish[0..=n-1] 都为true,则表示系统处于安全状态;否则表示系统处于不安全状态。
260260

261261

262262
通过操作系统调度,如银行家算法来避免死锁不是广泛使用的通用方案。因为从线程执行的一般情况上看,银行家算法需要提前获知线程总的资源申请量,以及未来的每一次请求,而这些请求对于一般线程而言在运行前是不可知或随机的。另外,即使在某些特殊情况下,可以提前知道线程的资源申请量等信息,多重循环的银行家算法开销也是很大的,不适合于对性能要求很高的操作系统中。

source/chapter9/0intro.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@
136136
:alt: 侏罗盗龙操作系统 -- DeviceOS总体结构
137137

138138

139-
我们先分析一下图的上下两部分。从上图的右上角可以看到为应用程序增加了GUI相关的新系统调用。应用程序可以通过 ``sys_framebuffer`` 和 ``sys_framebuffer_flush`` 来显示图形界面,通过 ``sys_event_get`` 和 ``sys_key_pressed`` 来接收来自串口/键盘/鼠标的输入事件。这其实就形成了基本的GUI应用支持框架。在上图的中上部,添加了三个GUI应用的图形显示,从左到右分别是: ``gui_simple`` 、 ``gui_snake`` 和 ``gui_rect`` 。
139+
我们先分析一下图的上下两部分。从上图的左上角可以看到为应用程序增加了GUI相关的新系统调用。应用程序可以通过 ``sys_framebuffer`` 和 ``sys_framebuffer_flush`` 来显示图形界面,通过 ``sys_event_get`` 和 ``sys_key_pressed`` 来接收来自串口/键盘/鼠标的输入事件。这其实就形成了基本的GUI应用支持框架。在上图的中上部,添加了三个GUI应用的图形显示,从左到右分别是: ``gui_simple`` 、 ``gui_snake`` 和 ``gui_rect`` 。
140140

141141
在上图的最下面展示的硬件组成中,可以看到由Qemu模拟器仿真的 ``Virt Machine`` ,它包含了我们要管理的各种硬件组件,包括在前面章节中重点涉及的 ``CPU`` 和 ``Main Memory`` ,还包括新引入的外设, ``ns16500`` UART串口外设、 ``virtio-gpu`` 图形显示外设、 ``virtio-input`` 键盘鼠标外设、 ``vritio-blk`` 硬盘存储设备。为了与这些硬件交互,系统软件还需了解有关这些外设的硬件参数模型,如各个外设的控制寄存器的内存起始地址和范围等,这就是Qemu模拟器中的 ``Virt Machine`` 硬件参数模型。硬件参数的具体内容可以在Qemu源码 ``qemu/include/hw/riscv/virt.h`` 和 ``qemu/hw/riscv/virt.c`` 中找到。
142142

source/chapter9/1io-interface.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ I/O接口的交互协议
236236

237237
在最接近进程的流的末端是一组例程,它们为操作系统的其余部分提供接口。用户进程的写操作请求和输入/输出控制请求被转换成发送到流的消息,而读请求将从流中获取数据并将其传递给用户进程。流的另一端是设备驱动程序模块。对字符或网络传输而言,从用户进程以流的方式传递数据将被发送到设备;设备检测到的字符、网络包和状态转换被合成为消息,并被发送到流向用户进程的流中。整个过程会经过多个中间模块,这些模块会以各种方式处理或过滤消息。
238238

239-
在具体实现上,当设备打开时,流中的两个末端管理的内核模块自动连接;中间模块是根据用户程序的请求动态附加的。为了能够方便动态地插入不同的流处理模块,这些中间模块的读写接口被设定为相同
239+
在具体实现上,当设备打开时,流中的两个末端管理的内核模块自动连接;中间模块是根据用户程序的请求动态附加的。为了能够方便动态地插入不同的流处理模块,这些中间模块的读写接口遵从相同的语义约束并互相兼容
240240

241241
每个流处理模块由一对队列(queue)组成,每个方向一个队列。队列不仅包括数据队列本身,还包括两个例程和一些状态信息。一个是put例程,它由邻居模块调用以将消息放入数据队列中。另一个是服务(service)例程,被安排在有工作要做的时候执行。状态信息包括指向下游下一个队列的指针、各种标志以及指向队列实例化所需的附加状态信息的指针。
242242

source/chapter9/2device-driver-0.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@
261261

262262
计算机中的中断控制器是一种硬件,可帮助处理器处理来自多个不同I/O设备的中断请求(Interrupt Request,简称IRQ)。这些中断请求可能同时发生,并首先经过中断控制器的处理,即中断控制器根据 IRQ 的优先级对同时发生的中断进行排序,然后把优先级最高的IRQ传给处理器,让操作系统执行相应的中断处理例程 (Interrupt Service Routine,简称ISR)。
263263

264-
CPU可以通过MMIO方式来对PLIC进行管理,下面是一下与PLIC相关的寄存器
264+
CPU可以通过MMIO方式来对PLIC进行管理,下面是一些与PLIC相关的寄存器
265265

266266
.. code-block:: console
267267

source/chapter9/2device-driver-4.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ virtio-gpu驱动程序与virtio-gpu设备之间通过两个 virtqueue 来进行
9696
初始化virtio-gpu设备
9797
------------------------------------------
9898

99-
在 ``virtio-drivers`` crate的 ``examples\riscv\src\main.rs`` 文件中的 ``virtio_probe`` 函数识别出virtio-gpu设备后,会调用 ``virtio_gpu(header)`` 函数来完成对virtio-gpu设备的初始化过程。virtio-gpu设备初始化的工作主要是查询显示设备的信息(如分辨率等),并将该信息用于初始显示扫描(scanout)设置。下面的命令可以看到虚拟GPU的创建和识别过程:
99+
在 ``virtio-drivers`` crate的 ``examples/riscv/src/main.rs`` 文件中的 ``virtio_probe`` 函数识别出virtio-gpu设备后,会调用 ``virtio_gpu(header)`` 函数来完成对virtio-gpu设备的初始化过程。virtio-gpu设备初始化的工作主要是查询显示设备的信息(如分辨率等),并将该信息用于初始显示扫描(scanout)设置。下面的命令可以看到虚拟GPU的创建和识别过程:
100100

101101
.. code-block:: shell
102102
:linenos:
@@ -377,7 +377,7 @@ virtio-gpu设备的I/O操作
377377
4. (可选)设置virtio-gpu设备的光标图像
378378
5. 返回VirtIOGpuWrapper结构类型
379379
380-
上述步骤的第一步 :ref:`"virto-gpu基本初始化"<term-virtio-driver-gpu-new>` 和第二步 :ref:` 设置显存<term-virtio-driver-gpu-setupfb>`是核心内容,都由 virtio-drivers中与具体操作系统无关的virtio-gpu裸机驱动实现,极大降低本章从操作系统的代码复杂性。至此,我们已经完成了操作系统对 virtio-gpu设备的初始化过程,接下来,我们看一下操作系统对virtio-gpu设备的I/O处理过程。
380+
上述步骤的第一步 :ref:`"virto-gpu基本初始化"<term-virtio-driver-gpu-new>` 和第二步 :ref:`设置显存<term-virtio-driver-gpu-setupfb>` 是核心内容,都由 virtio-drivers中与具体操作系统无关的virtio-gpu裸机驱动实现,极大降低本章从操作系统的代码复杂性。至此,我们已经完成了操作系统对 virtio-gpu设备的初始化过程,接下来,我们看一下操作系统对virtio-gpu设备的I/O处理过程。
381381
382382
操作系统对接virtio-gpu设备I/O处理
383383
------------------------------------------
@@ -462,7 +462,7 @@ virtio-gpu设备的I/O操作
462462
}
463463
464464
465-
到目前为止,看到的操作系统支持工作还是比较简单的,但其实我们还没分析如何给应用程序提供显存虚拟地址空间的。以前章节的操作系统支持应用程序的 :ref: `用户态地址空间<term-vm-app-addr-space>` ,都是在创建应用程序对应进程的初始化过程中建立,涉及不少工作,具体包括:
465+
到目前为止,看到的操作系统支持工作还是比较简单的,但其实我们还没分析如何给应用程序提供显存虚拟地址空间的。以前章节的操作系统支持应用程序的 :ref:`用户态地址空间<term-vm-app-addr-space>` ,都是在创建应用程序对应进程的初始化过程中建立,涉及不少工作,具体包括:
466466
467467
- 分配空闲 :ref:`物理页帧<term-manage-phys-frame>`
468468
- 建立 :ref:`进程地址空间(Address Space)<term-vm-memory-set>` 中的 :ref:`逻辑段(MemArea)<term-vm-map-area>`

0 commit comments

Comments
 (0)