提交 4b1a68c3 编写于 作者: W wyfcyx

deploy: 0de80a9c

上级 647f9ea6
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
.. _term-physical-address: .. _term-physical-address:
计算机主要由处理器(Processor,也即中央处理器,CPU,Central Processing Unit),物理内存和 I/O 外设三部分组成。在前八章我们主要用到 CPU 和物理内存。处理器的主要功能是从物理内存中读取指令、译码并执行,在此过程中还要与物理内存和 I/O 外设打交道。物理内存则是计算机体系结构中一个重要的组成部分。在存储方面,CPU 唯一能够直接访问的只有物理内存中的数据,它可以通过访存指令来达到这一目的。从 CPU 的视角看来,可以将物理内存看成一个大字节数组,而物理地址则对应于一个能够用来访问数组中某个元素的下标。与我们日常编程习惯不同的是,该下标通常不以 0 开头,而通常以一个常数,如 ``0x80000000`` 开头。简言之,CPU 可以通过物理地址来寻址,并 **逐字节** 地访问物理内存中保存的数据。 计算机主要由处理器(Processor,也即中央处理器,CPU,Central Processing Unit),物理内存和 I/O 外设三部分组成。在前八章我们主要用到 CPU 和物理内存,第九章则开始与丰富多彩的外设打交道。处理器的主要功能是从物理内存中读取指令、译码并执行,在此过程中还要与物理内存和 I/O 外设打交道。物理内存则是计算机体系结构中一个重要的组成部分。在存储方面,CPU 唯一能够直接访问的只有物理内存中的数据,它可以通过访存指令来达到这一目的。从 CPU 的视角看来,可以将物理内存看成一个大字节数组,而物理地址则对应于一个能够用来访问数组中某个元素的下标。与我们日常编程习惯不同的是,该下标通常不以 0 开头,而通常以一个常数,如 ``0x80000000`` 开头。简言之,CPU 可以通过物理地址来寻址,并 **逐字节** 地访问物理内存中保存的数据。
值得一提的是,当 CPU 以多个字节(比如 2/4/8 或更多)为单位访问物理内存(事实上并不局限于物理内存,也包括I/O外设的数据空间)中的数据时,就有可能会引入端序(也称字节顺序)和内存地址对齐的问题。由于这并不是重点,我们在这里不展开说明,如读者有兴趣可以参考下面的补充说明。 值得一提的是,当 CPU 以多个字节(比如 2/4/8 或更多)为单位访问物理内存(事实上并不局限于物理内存,也包括I/O外设的数据空间)中的数据时,就有可能会引入端序(也称字节顺序)和内存地址对齐的问题。由于这并不是重点,我们在这里不展开说明,如读者有兴趣可以参考下面的补充说明。
...@@ -68,7 +68,7 @@ Qemu 启动流程 ...@@ -68,7 +68,7 @@ Qemu 启动流程
.. _term-physical-memory: .. _term-physical-memory:
在Qemu模拟的 ``virt`` 硬件平台上,物理内存的起始物理地址为 ``0x80000000`` ,物理内存的默认大小为 128MiB ,它可以通过 ``-m`` 选项进行配置。在本书中,我们只会用到最低的 8MiB 物理内存,对应的物理地址区间为 ``[0x80000000,0x80800000)`` 。如果使用上面给出的命令启动 Qemu ,那么在 Qemu 开始执行任何指令之前,首先把两个文件加载到 Qemu 的物理内存中:即作把作为 bootloader 的 ``rustsbi-qemu.bin`` 加载到物理内存以物理地址 ``0x80000000`` 开头的区域上,同时把内核镜像 ``os.bin`` 加载到以物理地址 ``0x80200000`` 开头的区域上。 在Qemu模拟的 ``virt`` 硬件平台上,物理内存的起始物理地址为 ``0x80000000`` ,物理内存的默认大小为 128MiB ,它可以通过 ``-m`` 选项进行配置。如果使用默认配置的 128MiB 物理内存则对应的物理地址区间为 ``[0x80000000,0x88000000)`` 。如果使用上面给出的命令启动 Qemu ,那么在 Qemu 开始执行任何指令之前,首先把两个文件加载到 Qemu 的物理内存中:即作把作为 bootloader 的 ``rustsbi-qemu.bin`` 加载到物理内存以物理地址 ``0x80000000`` 开头的区域上,同时把内核镜像 ``os.bin`` 加载到以物理地址 ``0x80200000`` 开头的区域上。
为什么加载到这两个位置呢?这与 Qemu 模拟计算机加电启动后的运行流程有关。一般来说,计算机加电之后的启动流程可以分成若干个阶段,每个阶段均由一层软件或 :ref:`固件 <term-firmware>` 负责,每一层软件或固件的功能是进行它应当承担的初始化工作,并在此之后跳转到下一层软件或固件的入口地址,也就是将计算机的控制权移交给了下一层软件或固件。Qemu 模拟的启动流程则可以分为三个阶段:第一个阶段由固化在 Qemu 内的一小段汇编程序负责;第二个阶段由 bootloader 负责;第三个阶段则由内核镜像负责。 为什么加载到这两个位置呢?这与 Qemu 模拟计算机加电启动后的运行流程有关。一般来说,计算机加电之后的启动流程可以分成若干个阶段,每个阶段均由一层软件或 :ref:`固件 <term-firmware>` 负责,每一层软件或固件的功能是进行它应当承担的初始化工作,并在此之后跳转到下一层软件或固件的入口地址,也就是将计算机的控制权移交给了下一层软件或固件。Qemu 模拟的启动流程则可以分为三个阶段:第一个阶段由固化在 Qemu 内的一小段汇编程序负责;第二个阶段由 bootloader 负责;第三个阶段则由内核镜像负责。
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
如果把应用程序执行的整个过程进行进一步分析,可以看到,当程序访问 I/O 外设或睡眠时,其实是不需要占用处理器的,于是我们可以把应用程序在不同时间段的执行过程分为两类,占用处理器执行有效任务的计算阶段和不必占用处理器的等待阶段。这些阶段就形成了一个我们熟悉的“暂停-继续...”组合的控制流或执行历史。从应用程序开始执行到结束的整个控制流就是应用程序的整个执行过程。 如果把应用程序执行的整个过程进行进一步分析,可以看到,当程序访问 I/O 外设或睡眠时,其实是不需要占用处理器的,于是我们可以把应用程序在不同时间段的执行过程分为两类,占用处理器执行有效任务的计算阶段和不必占用处理器的等待阶段。这些阶段就形成了一个我们熟悉的“暂停-继续...”组合的控制流或执行历史。从应用程序开始执行到结束的整个控制流就是应用程序的整个执行过程。
本节的重点是操作系统的核心机制—— **任务切换** 。 任务切换支持的场景是:一个应用在运行途中便会主动或被动交出 CPU 的使用权,此时它只能暂停执行,等到内核重新给它分配处理器资源之后才能恢复并继续执行。有了任务切换的能力,“螈”级的操作系统才能跳出水坑,进入陆地,才有能力进化到“恐龙”级的操作系统。 本节的重点是操作系统的核心机制—— **任务切换** ,在内核中这种机制是在 ``__switch`` 函数中实现的。 任务切换支持的场景是:一个应用在运行途中便会主动或被动交出 CPU 的使用权,此时它只能暂停执行,等到内核重新给它分配处理器资源之后才能恢复并继续执行。有了任务切换的能力,“螈”级的操作系统才能跳出水坑,进入陆地,才有能力进化到“恐龙”级的操作系统。
任务的概念形成 任务的概念形成
--------------------------------- ---------------------------------
......
...@@ -396,7 +396,7 @@ ...@@ -396,7 +396,7 @@
<div class="section" id="id3"> <div class="section" id="id3">
<h2>计算机组成基础<a class="headerlink" href="#id3" title="永久链接至标题">#</a></h2> <h2>计算机组成基础<a class="headerlink" href="#id3" title="永久链接至标题">#</a></h2>
<p>当编写应用程序的时候,大多数情况下我们只需调用库函数即可在操作系统的支持下实现各项功能,而无需关心操作系统如何调度管理各类软硬件资源。操作系统提供了一些监控工具(如 Windows 上的任务管理器或 Linux 上的 <code class="docutils literal notranslate"><span class="pre">ps</span></code> 工具),这些工具可以帮助我们统计 CPU、内存、硬盘、网络等资源的占用情况,从而让我们大致上了解这些资源的使用情况,并帮助我们更好地开发或部署应用程序。然而,在实际编写操作系统的时候,我们就必须直面这些硬件资源,将它们管理起来并为应用程序提供高效易用的抽象。为此,我们必须增进对于这些硬件的了解。</p> <p>当编写应用程序的时候,大多数情况下我们只需调用库函数即可在操作系统的支持下实现各项功能,而无需关心操作系统如何调度管理各类软硬件资源。操作系统提供了一些监控工具(如 Windows 上的任务管理器或 Linux 上的 <code class="docutils literal notranslate"><span class="pre">ps</span></code> 工具),这些工具可以帮助我们统计 CPU、内存、硬盘、网络等资源的占用情况,从而让我们大致上了解这些资源的使用情况,并帮助我们更好地开发或部署应用程序。然而,在实际编写操作系统的时候,我们就必须直面这些硬件资源,将它们管理起来并为应用程序提供高效易用的抽象。为此,我们必须增进对于这些硬件的了解。</p>
<p id="term-physical-address">计算机主要由处理器(Processor,也即中央处理器,CPU,Central Processing Unit),物理内存和 I/O 外设三部分组成。在前八章我们主要用到 CPU 和物理内存。处理器的主要功能是从物理内存中读取指令、译码并执行,在此过程中还要与物理内存和 I/O 外设打交道。物理内存则是计算机体系结构中一个重要的组成部分。在存储方面,CPU 唯一能够直接访问的只有物理内存中的数据,它可以通过访存指令来达到这一目的。从 CPU 的视角看来,可以将物理内存看成一个大字节数组,而物理地址则对应于一个能够用来访问数组中某个元素的下标。与我们日常编程习惯不同的是,该下标通常不以 0 开头,而通常以一个常数,如 <code class="docutils literal notranslate"><span class="pre">0x80000000</span></code> 开头。简言之,CPU 可以通过物理地址来寻址,并 <strong>逐字节</strong> 地访问物理内存中保存的数据。</p> <p id="term-physical-address">计算机主要由处理器(Processor,也即中央处理器,CPU,Central Processing Unit),物理内存和 I/O 外设三部分组成。在前八章我们主要用到 CPU 和物理内存,第九章则开始与丰富多彩的外设打交道。处理器的主要功能是从物理内存中读取指令、译码并执行,在此过程中还要与物理内存和 I/O 外设打交道。物理内存则是计算机体系结构中一个重要的组成部分。在存储方面,CPU 唯一能够直接访问的只有物理内存中的数据,它可以通过访存指令来达到这一目的。从 CPU 的视角看来,可以将物理内存看成一个大字节数组,而物理地址则对应于一个能够用来访问数组中某个元素的下标。与我们日常编程习惯不同的是,该下标通常不以 0 开头,而通常以一个常数,如 <code class="docutils literal notranslate"><span class="pre">0x80000000</span></code> 开头。简言之,CPU 可以通过物理地址来寻址,并 <strong>逐字节</strong> 地访问物理内存中保存的数据。</p>
<p>值得一提的是,当 CPU 以多个字节(比如 2/4/8 或更多)为单位访问物理内存(事实上并不局限于物理内存,也包括I/O外设的数据空间)中的数据时,就有可能会引入端序(也称字节顺序)和内存地址对齐的问题。由于这并不是重点,我们在这里不展开说明,如读者有兴趣可以参考下面的补充说明。</p> <p>值得一提的是,当 CPU 以多个字节(比如 2/4/8 或更多)为单位访问物理内存(事实上并不局限于物理内存,也包括I/O外设的数据空间)中的数据时,就有可能会引入端序(也称字节顺序)和内存地址对齐的问题。由于这并不是重点,我们在这里不展开说明,如读者有兴趣可以参考下面的补充说明。</p>
<div class="admonition note"> <div class="admonition note">
<p class="admonition-title">注解</p> <p class="admonition-title">注解</p>
...@@ -431,7 +431,7 @@ ...@@ -431,7 +431,7 @@
</ul> </ul>
<div class="section" id="id5"> <div class="section" id="id5">
<h3>Qemu 启动流程<a class="headerlink" href="#id5" title="永久链接至标题">#</a></h3> <h3>Qemu 启动流程<a class="headerlink" href="#id5" title="永久链接至标题">#</a></h3>
<p id="term-physical-memory">在Qemu模拟的 <code class="docutils literal notranslate"><span class="pre">virt</span></code> 硬件平台上,物理内存的起始物理地址为 <code class="docutils literal notranslate"><span class="pre">0x80000000</span></code> ,物理内存的默认大小为 128MiB ,它可以通过 <code class="docutils literal notranslate"><span class="pre">-m</span></code> 选项进行配置。在本书中,我们只会用到最低的 8MiB 物理内存,对应的物理地址区间为 <code class="docutils literal notranslate"><span class="pre">[0x80000000,0x80800000)</span></code> 。如果使用上面给出的命令启动 Qemu ,那么在 Qemu 开始执行任何指令之前,首先把两个文件加载到 Qemu 的物理内存中:即作把作为 bootloader 的 <code class="docutils literal notranslate"><span class="pre">rustsbi-qemu.bin</span></code> 加载到物理内存以物理地址 <code class="docutils literal notranslate"><span class="pre">0x80000000</span></code> 开头的区域上,同时把内核镜像 <code class="docutils literal notranslate"><span class="pre">os.bin</span></code> 加载到以物理地址 <code class="docutils literal notranslate"><span class="pre">0x80200000</span></code> 开头的区域上。</p> <p id="term-physical-memory">在Qemu模拟的 <code class="docutils literal notranslate"><span class="pre">virt</span></code> 硬件平台上,物理内存的起始物理地址为 <code class="docutils literal notranslate"><span class="pre">0x80000000</span></code> ,物理内存的默认大小为 128MiB ,它可以通过 <code class="docutils literal notranslate"><span class="pre">-m</span></code> 选项进行配置。如果使用默认配置的 128MiB 物理内存则对应的物理地址区间为 <code class="docutils literal notranslate"><span class="pre">[0x80000000,0x88000000)</span></code> 。如果使用上面给出的命令启动 Qemu ,那么在 Qemu 开始执行任何指令之前,首先把两个文件加载到 Qemu 的物理内存中:即作把作为 bootloader 的 <code class="docutils literal notranslate"><span class="pre">rustsbi-qemu.bin</span></code> 加载到物理内存以物理地址 <code class="docutils literal notranslate"><span class="pre">0x80000000</span></code> 开头的区域上,同时把内核镜像 <code class="docutils literal notranslate"><span class="pre">os.bin</span></code> 加载到以物理地址 <code class="docutils literal notranslate"><span class="pre">0x80200000</span></code> 开头的区域上。</p>
<p>为什么加载到这两个位置呢?这与 Qemu 模拟计算机加电启动后的运行流程有关。一般来说,计算机加电之后的启动流程可以分成若干个阶段,每个阶段均由一层软件或 <a class="reference internal" href="#term-firmware"><span class="std std-ref">固件</span></a> 负责,每一层软件或固件的功能是进行它应当承担的初始化工作,并在此之后跳转到下一层软件或固件的入口地址,也就是将计算机的控制权移交给了下一层软件或固件。Qemu 模拟的启动流程则可以分为三个阶段:第一个阶段由固化在 Qemu 内的一小段汇编程序负责;第二个阶段由 bootloader 负责;第三个阶段则由内核镜像负责。</p> <p>为什么加载到这两个位置呢?这与 Qemu 模拟计算机加电启动后的运行流程有关。一般来说,计算机加电之后的启动流程可以分成若干个阶段,每个阶段均由一层软件或 <a class="reference internal" href="#term-firmware"><span class="std std-ref">固件</span></a> 负责,每一层软件或固件的功能是进行它应当承担的初始化工作,并在此之后跳转到下一层软件或固件的入口地址,也就是将计算机的控制权移交给了下一层软件或固件。Qemu 模拟的启动流程则可以分为三个阶段:第一个阶段由固化在 Qemu 内的一小段汇编程序负责;第二个阶段由 bootloader 负责;第三个阶段则由内核镜像负责。</p>
<ul class="simple"> <ul class="simple">
<li><p>第一阶段:将必要的文件载入到 Qemu 物理内存之后,Qemu CPU 的程序计数器(PC, Program Counter)会被初始化为 <code class="docutils literal notranslate"><span class="pre">0x1000</span></code> ,因此 Qemu 实际执行的第一条指令位于物理地址 <code class="docutils literal notranslate"><span class="pre">0x1000</span></code> ,接下来它将执行寥寥数条指令并跳转到物理地址 <code class="docutils literal notranslate"><span class="pre">0x80000000</span></code> 对应的指令处并进入第二阶段。从后面的调试过程可以看出,该地址 <code class="docutils literal notranslate"><span class="pre">0x80000000</span></code> 被固化在 Qemu 中,作为 Qemu 的使用者,我们在不触及 Qemu 源代码的情况下无法进行更改。</p></li> <li><p>第一阶段:将必要的文件载入到 Qemu 物理内存之后,Qemu CPU 的程序计数器(PC, Program Counter)会被初始化为 <code class="docutils literal notranslate"><span class="pre">0x1000</span></code> ,因此 Qemu 实际执行的第一条指令位于物理地址 <code class="docutils literal notranslate"><span class="pre">0x1000</span></code> ,接下来它将执行寥寥数条指令并跳转到物理地址 <code class="docutils literal notranslate"><span class="pre">0x80000000</span></code> 对应的指令处并进入第二阶段。从后面的调试过程可以看出,该地址 <code class="docutils literal notranslate"><span class="pre">0x80000000</span></code> 被固化在 Qemu 中,作为 Qemu 的使用者,我们在不触及 Qemu 源代码的情况下无法进行更改。</p></li>
......
...@@ -391,7 +391,7 @@ ...@@ -391,7 +391,7 @@
<h2>本节导读<a class="headerlink" href="#id2" title="永久链接至标题">#</a></h2> <h2>本节导读<a class="headerlink" href="#id2" title="永久链接至标题">#</a></h2>
<p>在上一节实现的二叠纪“锯齿螈”操作系统还是比较原始,一个应用会独占 CPU 直到它出错或主动退出。操作系统还是以程序的一次执行过程(从开始到结束)作为处理器切换程序的时间段。为了提高效率,我们需要引入新的操作系统概念 <strong>任务</strong><strong>任务切换</strong><strong>任务上下文</strong> 。为此,我们需要实现从“螈”到“恐龙”的进化,实现“始初龙”操作系统。</p> <p>在上一节实现的二叠纪“锯齿螈”操作系统还是比较原始,一个应用会独占 CPU 直到它出错或主动退出。操作系统还是以程序的一次执行过程(从开始到结束)作为处理器切换程序的时间段。为了提高效率,我们需要引入新的操作系统概念 <strong>任务</strong><strong>任务切换</strong><strong>任务上下文</strong> 。为此,我们需要实现从“螈”到“恐龙”的进化,实现“始初龙”操作系统。</p>
<p>如果把应用程序执行的整个过程进行进一步分析,可以看到,当程序访问 I/O 外设或睡眠时,其实是不需要占用处理器的,于是我们可以把应用程序在不同时间段的执行过程分为两类,占用处理器执行有效任务的计算阶段和不必占用处理器的等待阶段。这些阶段就形成了一个我们熟悉的“暂停-继续…”组合的控制流或执行历史。从应用程序开始执行到结束的整个控制流就是应用程序的整个执行过程。</p> <p>如果把应用程序执行的整个过程进行进一步分析,可以看到,当程序访问 I/O 外设或睡眠时,其实是不需要占用处理器的,于是我们可以把应用程序在不同时间段的执行过程分为两类,占用处理器执行有效任务的计算阶段和不必占用处理器的等待阶段。这些阶段就形成了一个我们熟悉的“暂停-继续…”组合的控制流或执行历史。从应用程序开始执行到结束的整个控制流就是应用程序的整个执行过程。</p>
<p>本节的重点是操作系统的核心机制—— <strong>任务切换</strong> 。 任务切换支持的场景是:一个应用在运行途中便会主动或被动交出 CPU 的使用权,此时它只能暂停执行,等到内核重新给它分配处理器资源之后才能恢复并继续执行。有了任务切换的能力,“螈”级的操作系统才能跳出水坑,进入陆地,才有能力进化到“恐龙”级的操作系统。</p> <p>本节的重点是操作系统的核心机制—— <strong>任务切换</strong> ,在内核中这种机制是在 <code class="docutils literal notranslate"><span class="pre">__switch</span></code> 函数中实现的。 任务切换支持的场景是:一个应用在运行途中便会主动或被动交出 CPU 的使用权,此时它只能暂停执行,等到内核重新给它分配处理器资源之后才能恢复并继续执行。有了任务切换的能力,“螈”级的操作系统才能跳出水坑,进入陆地,才有能力进化到“恐龙”级的操作系统。</p>
</div> </div>
<div class="section" id="id3"> <div class="section" id="id3">
<h2>任务的概念形成<a class="headerlink" href="#id3" title="永久链接至标题">#</a></h2> <h2>任务的概念形成<a class="headerlink" href="#id3" title="永久链接至标题">#</a></h2>
......
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册