diff --git a/source/appendix-b/index.rst b/source/appendix-b/index.rst index 1c3d3f8d..18cea5ac 100644 --- a/source/appendix-b/index.rst +++ b/source/appendix-b/index.rst @@ -118,8 +118,8 @@ rust-readobj 每个 section header 则描述一个段的元数据。 -其中,我们看到了代码段 ``.text`` 需要被加载到地址 ``0x5070`` ,大小 208067 字节,。 -它们分别由元数据的字段 Offset、 Size 和 Address 给出。。 +其中,我们看到了代码段 ``.text`` 需要被加载到地址 ``0x5070`` ,大小 208067 字节。 +它们分别由元数据的字段 Offset、 Size 和 Address 给出。 我们还能够看到程序中的符号表: diff --git a/source/chapter4/0intro.rst b/source/chapter4/0intro.rst index 2e90f75c..9451f242 100644 --- a/source/chapter4/0intro.rst +++ b/source/chapter4/0intro.rst @@ -233,7 +233,7 @@ 为了能够在内核中动态分配内存,我们的第二步需要在内核增加连续内存分配的功能,具体实现主要集中在 ``os/src/mm/heap_allocator.rs`` 中。完成这一步后,我们就可以在内核中用到Rust的堆数据结构了,如 ``Vec`` 、 ``Box`` 等,这样内核编程就更加灵活了。 -操作系统如果要建立页表(构建虚实地址映射关系),首先要能管理整个系统的物理内存,这就需要知道整个计算机系统的物理内存空间的范围,物理内存中哪些区域是空闲可用的,哪些区域放置内核/应用的代码和数据。操作系统内核能够以物理页帧为单位分配和回收物理内存,具体实现主要集中在 ``os/src/mm/frame_allocator.rs`` 中;也能在虚拟内存中以各种粒度大小来动态分配内存资源,具体实现主要集中在 ``os/src/mm/heap_allocator.rs`` 中。。 +操作系统如果要建立页表(构建虚实地址映射关系),首先要能管理整个系统的物理内存,这就需要知道整个计算机系统的物理内存空间的范围,物理内存中哪些区域是空闲可用的,哪些区域放置内核/应用的代码和数据。操作系统内核能够以物理页帧为单位分配和回收物理内存,具体实现主要集中在 ``os/src/mm/frame_allocator.rs`` 中;也能在虚拟内存中以各种粒度大小来动态分配内存资源,具体实现主要集中在 ``os/src/mm/heap_allocator.rs`` 中。 页表中的页表项的索引其实是虚拟地址中的虚拟页号,页表项的重要内容是物理地址的物理页帧号。为了能够灵活地在虚拟地址、物理地址、虚拟页号、物理页号之间进行各种转换,在 ``os/src/mm/address.rs`` 中实现了各种转换函数。 diff --git a/source/chapter4/2address-space.rst b/source/chapter4/2address-space.rst index 07166424..395aebbf 100644 --- a/source/chapter4/2address-space.rst +++ b/source/chapter4/2address-space.rst @@ -110,7 +110,7 @@ .. _term-page: .. _term-frame: -如上图所示,内核以页为单位进行物理内存管理。每个应用的地址空间可以被分成若干个(虚拟) **页面** (Page) ,而可用的物理内存也同样可以被分成若干个(物理) **页帧** (Frame) ,虚拟页面和物理页帧的大小相同。每个虚拟页面中的数据实际上都存储在某个物理页帧上。相比分段内存管理,分页内存管理的粒度更小且大小固定,应用地址空间中的每个逻辑段都由多个虚拟页面组成。而且每个虚拟页面在地址转换的过程中都使用与运行的应用绑定的不同的线性映射,而不象分段内存管理那样每个逻辑段都使用一个相同的线性映射。 +如上图所示,内核以页为单位进行物理内存管理。每个应用的地址空间可以被分成若干个(虚拟) **页面** (Page) ,而可用的物理内存也同样可以被分成若干个(物理) **页帧** (Frame) ,虚拟页面和物理页帧的大小相同。每个虚拟页面中的数据实际上都存储在某个物理页帧上。相比分段内存管理,分页内存管理的粒度更小且大小固定,应用地址空间中的每个逻辑段都由多个虚拟页面组成。而且每个虚拟页面在地址转换的过程中都使用与运行的应用绑定的不同的线性映射,而不像分段内存管理那样每个逻辑段都使用一个相同的线性映射。 .. _term-virtual-page-number: .. _term-physical-page-number: diff --git a/source/chapter4/3sv39-implementation-1.rst b/source/chapter4/3sv39-implementation-1.rst index 6b04f153..7d9ce3db 100644 --- a/source/chapter4/3sv39-implementation-1.rst +++ b/source/chapter4/3sv39-implementation-1.rst @@ -307,7 +307,7 @@ SV39 多级页表的硬件机制 - 当 ``V`` 为 0 的时候,代表当前指针是一个空指针,无法走向下一级节点,即该页表项对应的虚拟地址范围是无效的; - 只有当 ``V`` 为1 且 ``R/W/X`` 均为 0 时,表示是一个合法的页目录表项,其包含的指针会指向下一级的页表; -- 注意: ``当V`` 为1 且 ``R/W/X`` 不全为 0 时,表示是一个合法的页表项,其包含了虚地址对应的物理页号。 +- 注意: 当``V`` 为1 且 ``R/W/X`` 不全为 0 时,表示是一个合法的页表项,其包含了虚地址对应的物理页号。 在这里我们给出 SV39 中的 ``R/W/X`` 组合的含义: @@ -357,7 +357,7 @@ SV39 地址转换过程 :height: 600 :align: center -在 Sv39 模式中我们采用三级页表,即将 27 位的虚拟页号分为三个等长的部分,第 26-18 位为三级索引 :math:`\text{VPN}_2` ,第17-9 位为二级索引 :math:`\text{VPN}_1` ,第 8-0 位为一级索引 :math:`\text{VPN}_0` 。 +在 SV39 模式中我们采用三级页表,即将 27 位的虚拟页号分为三个等长的部分,第 26-18 位为三级索引 :math:`\text{VPN}_2` ,第17-9 位为二级索引 :math:`\text{VPN}_1` ,第 8-0 位为一级索引 :math:`\text{VPN}_0` 。 我们也将页表分为三级页表,二级页表,一级页表。每个页表都用 9 位索引的,因此有 :math:`2^{9}=512` 个页表项,而每个页表项都是 8 字节,因此每个页表大小都为 :math:`512\times 8=4\text{KiB}` 。正好是一个物理页的大小。我们可以把一个页表放到一个物理页中,并用一个物理页号来描述它。事实上,三级页表的每个页表项中的物理页号可描述一个二级页表;二级页表的每个页表项中的物理页号可描述一个一级页表;一级页表中的页表项内容则和我们刚才提到的页表项一样,其内容包含物理页号,即描述一个要映射到的物理页。 diff --git a/source/chapter4/4sv39-implementation-2.rst b/source/chapter4/4sv39-implementation-2.rst index d4d37821..2f3c0cdd 100644 --- a/source/chapter4/4sv39-implementation-2.rst +++ b/source/chapter4/4sv39-implementation-2.rst @@ -321,7 +321,7 @@ } } -每个应用的地址空间都对应一个不同的多级页表,这也就意味这不同页表的起始地址(即页表根节点的地址)是不一样的。因此 ``PageTable``要保存它根节点的物理页号 ``root_ppn`` 作为页表唯一的区分标志。此外,向量 ``frames`` 以 ``FrameTracker`` 的形式保存了页表所有的节点(包括根节点)所在的物理页帧。这与物理页帧管理模块的测试程序是一个思路,即将这些 ``FrameTracker`` 的生命周期进一步绑定到 ``PageTable`` 下面。当 ``PageTable`` 生命周期结束后,向量 ``frames`` 里面的那些 ``FrameTracker`` 也会被回收,也就意味着存放多级页表节点的那些物理页帧被回收了。 +每个应用的地址空间都对应一个不同的多级页表,这也就意味这不同页表的起始地址(即页表根节点的地址)是不一样的。因此 ``PageTable`` 要保存它根节点的物理页号 ``root_ppn`` 作为页表唯一的区分标志。此外,向量 ``frames`` 以 ``FrameTracker`` 的形式保存了页表所有的节点(包括根节点)所在的物理页帧。这与物理页帧管理模块的测试程序是一个思路,即将这些 ``FrameTracker`` 的生命周期进一步绑定到 ``PageTable`` 下面。当 ``PageTable`` 生命周期结束后,向量 ``frames`` 里面的那些 ``FrameTracker`` 也会被回收,也就意味着存放多级页表节点的那些物理页帧被回收了。 当我们通过 ``new`` 方法新建一个 ``PageTable`` 的时候,它只需有一个根节点。为此我们需要分配一个物理页帧 ``FrameTracker`` 并挂在向量 ``frames`` 下,然后更新根节点的物理页号 ``root_ppn`` 。 diff --git a/source/chapter4/7more-as.rst b/source/chapter4/7more-as.rst index 163a4b9f..edab66af 100644 --- a/source/chapter4/7more-as.rst +++ b/source/chapter4/7more-as.rst @@ -256,7 +256,7 @@ Clock置换策略 这种近似LRU策略类似时钟旋转的过程,所以也称为Clock(时钟,也称 Second-Chance 二次机会 )置换策略。虽然Clock置换策略不如LRU置换策略的效果好,但它比不考虑历史访问的方法要好,且在一般情况下,与LRU策略的结果对比相差不大。 -时钟置换策略的一个小改进,是进一步额外关注内存中的页的修改情况。这样做的原因是:如果页已被修改(modified,也称 dirty),称为脏页,则在释放它之前须将它的更新内容写回交换区,这又增加了一次甚至多次缓慢的I/O写回操作。但如果它没有被修改(clean),就称为干净页,可以直接释放它,没有额外的I/O写回操作。。因此,操作系统更倾向于先处理干净页,而不是脏页。 +时钟置换策略的一个小改进,是进一步额外关注内存中的页的修改情况。这样做的原因是:如果页已被修改(modified,也称 dirty),称为脏页,则在释放它之前须将它的更新内容写回交换区,这又增加了一次甚至多次缓慢的I/O写回操作。但如果它没有被修改(clean),就称为干净页,可以直接释放它,没有额外的I/O写回操作。因此,操作系统更倾向于先处理干净页,而不是脏页。 为了支持这种改进,页表项还应该扩展一个修改位(modified bit,又名脏位,dirty bit)。处理器在写入页时,会设置对应页表项的此位为1,操作系统会在合适的时机清除该位(即将其设置为0)。因此可以将该修改位作为该页近期是否被写的信息源。这样,改进的时钟置换策略,制定出新的优先级,即优先查找并清除未使用且干净的页(第一类);如无法找到第一类页,再查找并清除已使用且干净的页或未使用且脏的页(第二类);如果无法找到第二类的页,再查找并清除已使用且脏的页(第三类)。 diff --git a/source/chapter5/4scheduling.rst b/source/chapter5/4scheduling.rst index e8f99970..3fe4679c 100644 --- a/source/chapter5/4scheduling.rst +++ b/source/chapter5/4scheduling.rst @@ -254,7 +254,7 @@ MLFQ调度策略的关键在于如何设置优先级。一旦设置进程的好 那如何动态调整进程的优先级呢?首先,我们假设新创建的进程是I/O密集型的,可以把它设置为最高优先级。接下来根据它的执行表现来调整其优先级。如果在分配给它的时间配额内,它睡眠或等待I/O事件完成而主动放弃了处理器,操作系统预测它接下来的时间配额阶段很大可能还是具有I/O密集型特征,所以就保持其优先级不变。如果进程用完了分配给它的时间配额,操作系统预测它接下来有很大可能还是具有CPU密集型特征,就会降低其优先级。 这里的时间配额的具体值是一个经验值,一般是时间片的整数倍。 -这样,如果一个进程的执行时间小于分配给它的一个或几个时间配额,我们把这样的进程称为短进程。那么这个短进程会以比较高的优先级迅速地结束。而如果一个进程有大量的I/O操作,那么一般情况下,它会在时间配额结束前主动放弃处理器,进入等待状态,一旦被唤醒,会以原有的高优先级继续执行。。如果一个进程的执行时间远大于几个时间配额,我们把这样的进程称为长进程。那么这个常进程经过一段时间后,会处于优先级最底部的队列,只有在没有高优先级进程就绪的情况下,它才会继续执行,从而不会影响交互式进程的响应时间。 +这样,如果一个进程的执行时间小于分配给它的一个或几个时间配额,我们把这样的进程称为短进程。那么这个短进程会以比较高的优先级迅速地结束。而如果一个进程有大量的I/O操作,那么一般情况下,它会在时间配额结束前主动放弃处理器,进入等待状态,一旦被唤醒,会以原有的高优先级继续执行。如果一个进程的执行时间远大于几个时间配额,我们把这样的进程称为长进程。那么这个常进程经过一段时间后,会处于优先级最底部的队列,只有在没有高优先级进程就绪的情况下,它才会继续执行,从而不会影响交互式进程的响应时间。 这样,我们进一步扩展了MLFQ的基本规则: