1remove-std.rst.txt 5.6 KB
Newer Older
Y
Yifan Wu 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
移除标准库依赖
===========================

.. toctree::
   :hidden:
   :maxdepth: 3

作为一切的开始,让我们使用 Cargo 工具来创建一个 Rust 项目。它看上去没有任何特别之处:

.. code-block:: console

   $ cargo new os --bin

我们加上了 ``--bin`` 选项来告诉 Cargo 我们创建一个可执行项目而不是库项目。此时,项目的文件结构如下:

.. code-block:: console
   
   $ tree os
   os
   ├── Cargo.toml
   └── src
       └── main.rs

   1 directory, 2 files

其中 ``Cargo.toml`` 中保存着项目的配置,包括作者的信息、联系方式以及库依赖等等。显而易见源代码保存在 ``src`` 目录下,目前为止只有 ``main.rs``
一个文件,让我们看一下里面的内容:

.. code-block:: rust
   :linenos:
   :caption: 最简单的 Rust 应用

   fn main() {
       println!("Hello, world!");
   }

利用 Cargo 工具即可一条命令实现构建并运行项目:

.. code-block:: console
   
   $ cargo run
      Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
       Finished dev [unoptimized + debuginfo] target(s) in 1.15s
        Running `target/debug/os`
   Hello, world!
 
如我们预想的一样,我们在屏幕上看到了一行 ``Hello, world!`` 。但是,需要注意到我们所享受到的编程的方便并不是理所当然的,背后有着从硬件
到软件的多种机制的支持。

应用程序运行环境
-------------------------------

如下图所示,应用程序的运行需要下面一套运行环境栈的支持:

.. image:: app-software-stack.png

图中的白色块自上而下(越往下则越靠近底层,下层作为上层的执行环境支持上层代码的运行)表示各级运行环境,黑色块则表示相邻两层运行环境之间
的接口。

我们的应用位于最上层,它可以通过调用编程语言提供的标准库或者其他三方库对外提供的功能强大的函数接口,使得仅需少量的源代码就能完成复杂的
功能。但是这些库的功能不仅限于此,事实上它们属于应用程序的 **执行环境** (Execution Environment),在我们通常不会注意到的地方,它
们还会在执行应用之前完成一些初始化工作,并在应用程序执行的时候对它进行监控。我们在打印 ``Hello, world!`` 时使用的 ``println!`` 
宏正是由 Rust 标准库 std 提供的。

从内核/操作系统的角度看来,它上面的一切都属于用户态,而它自身属于内核态。无论用户态应用如何编写,是手写汇编代码,还是基于某种编程语言利用
其标准库或三方库,某些功能总要直接或间接的通过内核/操作系统提供的 **系统调用** (System Call) 来实现。因此系统调用充当了用户和内核之间
的边界。内核作为用户态的运行环境,它不仅要提供系统调用接口,还需要对用户态应用的执行进行监控和管理。

.. note::

   ``Hello, world!`` 用到了哪些系统调用?
   

从硬件的角度来看,它上面的一切都属于软件。硬件可以分为三种: 处理器 (Processor) ——它更常见的名字是中央处理单元 (CPU, Central Processing Unit),
内存 (Memory) 还有 I/O 设备。其中处理器无疑是其中最复杂同时也最关键的一个。它与软件约定一套 **指令集架构** (ISA, Instruction Set Architecture),
使得软件可以通过 ISA 中提供的汇编指令来访问各种硬件资源。软件当然也需要知道处理器会如何执行这些指令:最简单的话就是一条一条执行位于内存
中的指令。当然,实际的情况远比这个要复杂得多,为了适应现代应用程序的场景,处理器还需要提供很多额外的机制,而不仅仅是让数据在 CPU 寄存器、内存和 I/O 设备
三者之间流动。

.. note::

   多层执行环境都是必需的吗?

   除了最上层的应用程序和最下层的硬件平台必须存在之外,作为中间层的函数库和内核并不是必须存在的:它们都是对下层资源进行了 **抽象** (Abstraction),
   并为上层提供了一套运行环境。抽象的优点在于它让上层以较小的代价获得所需的功能,并同时可以提供一些保护。但抽象同时也是一种限制,会丧失一些
   应有的灵活性。比如,当你在考虑在项目中应该使用哪个函数库的时候,就常常需要这方面的权衡:过多的抽象和过少的抽象自然都是不合适的。

   实际上,我们通过应用程序的特征来判断它需要什么程度的抽象。

   - 如果函数库和内核都不存在,那么我们就是在手写汇编代码,这种方式具有最高的灵活性,抽象能力则最低,基本等同于硬件。我们通常用这种方式来
     实现一些架构相关且仅通过编程语言无法描述的小模块或者代码片段。
   - 如果仅存在函数库而不存在内核,意味着我们不需要内核提供的抽象。在嵌入式场景就常常会出现这种情况。嵌入式设备虽然也包含 CPU、内存和 I/O
     设备,但是它上面通常只会同时运行一个或几个功能非常简单的小应用程序,其定位就是那种功能单一的场景,比如人脸识别打卡系统等。我们常用的
     操作系统如 Windows/Linux/macOS 等的抽象都支持同时运行很多应用程序,在嵌入式场景是过抽象的。因此,常见的解决方案是仅使用函数库构建
     单独的应用程序或是用专为应用场景特别裁减过的轻量级内核管理少数应用程序。

目标三元组
--------------------