提交 c0d3b248 编写于 作者: Y Yifan Wu

Update 1-1/2

上级 96a76265
......@@ -3,7 +3,7 @@
.. toctree::
:hidden:
:maxdepth: 3
:maxdepth: 4
作为一切的开始,让我们使用 Cargo 工具来创建一个 Rust 项目。它看上去没有任何特别之处:
......@@ -34,7 +34,7 @@
println!("Hello, world!");
}
利用 Cargo 工具即可一条命令实现构建并运行项目:
进入 os 项目根目录下,利用 Cargo 工具即可一条命令实现构建并运行项目:
.. code-block:: console
......@@ -72,7 +72,7 @@
.. note::
``Hello, world!`` 用到了哪些系统调用?
**Hello, world! 用到了哪些系统调用?**
从之前的 ``cargo run`` 的输出可以看出之前构建的可执行文件是在 target/debug 目录下的 os 。
在 Ubuntu 系统上,可以通过 ``strace`` 工具来运行一个程序并输出程序运行过程当中向内核请求的所有的系统调用及其返回值。
......@@ -97,7 +97,7 @@
.. note::
多层执行环境都是必需的吗?
**多层执行环境都是必需的吗?**
除了最上层的应用程序和最下层的硬件平台必须存在之外,作为中间层的函数库和内核并不是必须存在的:它们都是对下层资源进行了 **抽象** (Abstraction),
并为上层提供了一套运行环境。抽象的优点在于它让上层以较小的代价获得所需的功能,并同时可以提供一些保护。但抽象同时也是一种限制,会丧失一些
......@@ -171,5 +171,40 @@ linux-gnu 系统调用支持的版本 ``riscv64gc-unknown-linux-gnu``,是因
.. note::
RISC-V 指令集拓展
**RISC-V 指令集拓展**
由于基于 RISC-V 架构的处理器可能用于嵌入式场景或是通用计算场景,因此指令集规范将指令集划分为最基本的 RV32/64I 以及若干标准指令集拓展。
每款处理器只需按照其实际应用场景按需实现指令集拓展即可。
- RV32/64I:每款处理器都必须实现的基本整数指令集。在 RV32I 中,每个通用寄存器的位宽为 32 位;在 RV64I 中则为 64 位。它可以用来模拟
绝大多数标准指令集拓展中的指令,除了比较特殊的 A 拓展,因为它需要特别的硬件支持。
- M 拓展:提供整数乘除法相关指令。
- A 拓展:提供原子指令和一些相关的内存同步机制,这个后面会展开。
- F/D 拓展:提供单/双精度浮点数运算支持。
- C 拓展:提供压缩指令拓展。
G 拓展是基本整数指令集 I 再加上标准指令集拓展 MAFD 的总称,因此 riscv64gc 也就等同于 riscv64imafdc。我们剩下的内容都基于该处理器
架构完成。除此之外 RISC-V 架构还有很多标准指令集拓展,有一些还在持续更新中尚未稳定,有兴趣的读者可以浏览最新版的 RISC-V 指令集规范。
Rust 标准库与核心库
----------------------------------
我们尝试一下将当前的 ``Hello, world!`` 程序的目标平台换成 riscv64gc-unknown-none-elf 看看会发生什么事情:
.. code-block:: console
$ cargo run --target riscv64gc-unknown-none-elf
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
error[E0463]: can't find crate for `std`
|
= note: the `riscv64gc-unknown-none-elf` target may not be installed
在之前的环境配置中,我们已经在 rustup 工具链中安装了这个目标平台支持,因此并不是该目标平台未安装的问题。因此只是单纯的在这个目标平台上找不到
Rust 标准库 std。我们之前曾经提到过,编程语言的标准库或三方库的某些功能会直接或间接的用到操作系统提供的系统调用。但目前我们所选的目标平台不存在
任何操作系统支持,于是 Rust 并没有为这个目标平台支持完整的标准库 std。类似这样的平台通常被我们称为 **裸机平台** (bare-metal)。
幸运的是,Rust 有一个对 std 裁剪过后的核心库 core,这个库是不需要任何操作系统支持的,相对的它的功能也比较受限,但是也包含了 Rust 语言
相当一部分的核心机制,可以满足我们的大部分需求。在 Rust 语言生态中,有很多三方库也不依赖标准库 std 而仅仅依赖核心库 core,它们也可以很大
程度上减轻我们的编程负担。它们是我们能够在裸机平台挣扎求生的最主要倚仗。
于是,我们知道在裸机平台上我们要将对于标准库 std 的引用换成核心库 core。但是做起来其实并没有那么容易。
\ No newline at end of file
移除标准库依赖
==========================
.. toctree::
:hidden:
:maxdepth: 4
本节我们尝试移除之前的 ``Hello world!`` 程序对于标准库的依赖,使得它能够编译到裸机平台 RV64GC 上。
我们首先在 ``os`` 目录下新建 ``.cargo`` 目录,并在这个目录下创建 ``config`` 文件,并在里面输入如下内容:
.. code-block::
[build]
target = "riscv64gc-unknown-none-elf"
这会对于 Cargo 工具在 os 目录下的行为进行调整:现在默认会使用 riscv64gc 作为目标平台而不是原先的默认 x86_64-unknown-linux-gnu。
事实上,这是一种编译器运行所在的平台与编译器生成可执行文件的目标平台不同(分别是后者和前者)的情况。这是一种 **交叉编译** (Cross Compile)。
当然,这只是使得我们之后在 ``cargo build`` 的时候不必再加上 ``--target`` 参数的一个小 trick。如果我们现在 ``cargo build`` ,还是会和
上一小节一样出现找不到标准库 std 的错误。于是我们开始着手移除标准库。当然,这会产生一些副作用。
移除 println! 宏
----------------------------------
我们在 ``main.rs`` 的开头加上一行 ``#![no_std]`` 来告诉 Rust 编译器不使用 Rust 标准库 std 转而使用核心库 core。编译器报出如下错误:
.. error::
.. code-block:: console
$ cargo build
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
error: cannot find macro `println` in this scope
--> src/main.rs:4:5
|
4 | println!("Hello, world!");
| ^^^^^^^
我们之前提到过, println! 宏是由标准库 std 提供的,且会使用到一个名为 write 的系统调用。现在我们的条件还不足以自己实现一个 println! 宏,由于
使用了系统调用也不能在核心库 core 中找到它。我们目前先通过将它注释掉来绕过它。
提供语义项 panic_handler
----------------------------------------------------
.. error::
.. code-block:: console
$ cargo build
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
error: `#[panic_handler]` function required, but not found
在使用 Rust 编写应用程序的时候,我们常常在遇到了一些无法恢复的致命错误导致程序无法继续向下运行的时候手动或自动调用 panic! 宏来并打印出错的
位置让我们能够意识到它的存在,并进行一些后续处理。panic! 宏最典型的应用场景包括断言宏 assert! 失败或者对 ``Option::None/Result::Err``
进行 ``unwrap`` 操作。
在标准库 std 中提供了 panic 的处理函数 ``#[panic_handler]``,其大致功能是打印出错位置和原因并杀死当前应用。可惜的是在核心库 core 中并没有提供,
因此我们需要自己实现 panic 处理函数。
.. note::
**Rust 语义项 lang_items**
Rust 编译器内部的某些功能的实现并不是硬编码在语言内部的,而是以一种可插入的形式在库中提供。库只需要通过某种方式告诉编译器它的某个方法实现了
编译器内部的哪些功能,编译器就会采用库提供的方法来实现它内部对应的功能。通常只需要在库的方法前面加上一个标记即可。
我们开一个新的子模块 ``lang_items.rs`` 保存这些语义项,在里面提供 panic 处理函数的实现并通过标记通知编译器采用我们的实现:
.. code-block:: rust
// os/src/lang_items.rs
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
注意,panic 处理函数的函数签名需要一个 ``PanicInfo`` 的不可变借用作为输入参数,它在核心库中得以保留,这也是我们第一次与核心库打交道。之后我们
会从 ``PanicInfo`` 解析出错位置并打印出来,然后杀死应用程序。但目前我们什么都不做只是在原地 loop。
移除 main 函数
-----------------------------
.. error::
.. code-block::
$ cargo build
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
error: requires `start` lang_item
编译器提醒我们缺少一个名为 ``start`` 的语义项。我们回忆一下,之前提到语言标准库和三方库作为应用程序的执行环境,需要负责在执行应用程序之前进行
一些初始化工作,然后才跳转到应用程序的入口点(也就是跳转到我们编写的 ``main`` 函数)开始执行。事实上 ``start`` 语义项正代表着标准库 std 在
执行应用程序之前需要进行的一些初始化工作。由于我们禁用了标准库,编译器也就找不到这项功能的实现了。
最简单的解决方案就是压根不让编译器使用这项功能。我们在 ``main.rs`` 的开头加入设置 ``#![no_main]`` 告诉编译器我们没有一般意义上的 ``main`` 函数,
并将原来的 ``main`` 函数删除。在失去了 ``main`` 函数的情况下,编译器也就不需要完成所谓的初始化工作了。
至此,我们成功移除了标准库的依赖并完成裸机平台上的构建。
.. code-block:: console
$ cargo build
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
Finished dev [unoptimized + debuginfo] target(s) in 0.06s
目前的代码如下:
.. code-block:: rust
// os/src/main.rs
#![no_std]
#![no_main]
mod lang_items;
// os/src/lang_items.rs
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
本小节我们固然脱离了标准库,通过了编译器的检验,但也是伤筋动骨,将原有的很多功能弱化甚至直接删除,看起来距离在 RV64GC 平台上打印
``Hello world!`` 相去甚远了(我们甚至连 println! 和 ``main`` 函数都删除了)。不要着急,接下来我们会以自己的方式来重塑这些
功能,并最终完成我们的目标。
\ No newline at end of file
......@@ -6,4 +6,5 @@
:hidden:
:maxdepth: 3
1app-ee-platform
\ No newline at end of file
1app-ee-platform
2remove-std
\ No newline at end of file
......@@ -36,7 +36,7 @@
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<link rel="index" title="Index" href="../genindex.html" />
<link rel="search" title="Search" href="../search.html" />
<link rel="next" title="reStructuredText 基本语法" href="../rest-example.html" />
<link rel="next" title="移除标准库依赖" href="2remove-std.html" />
<link rel="prev" title="第一章:编译运行 RV64 裸机程序" href="index.html" />
</head>
......@@ -88,8 +88,10 @@
<li class="toctree-l2 current"><a class="current reference internal" href="#">应用程序运行环境与平台支持</a><ul>
<li class="toctree-l3"><a class="reference internal" href="#id2">应用程序运行环境</a></li>
<li class="toctree-l3"><a class="reference internal" href="#id3">平台与目标三元组</a></li>
<li class="toctree-l3"><a class="reference internal" href="#rust">Rust 标准库与核心库</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="2remove-std.html">移除标准库依赖</a></li>
</ul>
</li>
</ul>
......@@ -192,7 +194,7 @@
</pre></div>
</td></tr></table></div>
</div>
<p>利用 Cargo 工具即可一条命令实现构建并运行项目:</p>
<p>进入 os 项目根目录下,利用 Cargo 工具即可一条命令实现构建并运行项目:</p>
<div class="highlight-console notranslate"><div class="highlight"><pre><span></span><span class="gp">$</span> cargo run
<span class="go"> Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)</span>
<span class="go"> Finished dev [unoptimized + debuginfo] target(s) in 1.15s</span>
......@@ -220,7 +222,7 @@
的边界。内核作为用户态的运行环境,它不仅要提供系统调用接口,还需要对用户态应用的执行进行监控和管理。</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p><code class="docutils literal notranslate"><span class="pre">Hello,</span> <span class="pre">world!</span></code> 用到了哪些系统调用?</p>
<p><strong>Hello, world! 用到了哪些系统调用?</strong></p>
<p>从之前的 <code class="docutils literal notranslate"><span class="pre">cargo</span> <span class="pre">run</span></code> 的输出可以看出之前构建的可执行文件是在 target/debug 目录下的 os 。
在 Ubuntu 系统上,可以通过 <code class="docutils literal notranslate"><span class="pre">strace</span></code> 工具来运行一个程序并输出程序运行过程当中向内核请求的所有的系统调用及其返回值。
我们只需输入 <code class="docutils literal notranslate"><span class="pre">strace</span> <span class="pre">target/debug/os</span></code> 即可看到一长串的系统调用。</p>
......@@ -239,7 +241,7 @@
三者之间流动。</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>多层执行环境都是必需的吗?</p>
<p><strong>多层执行环境都是必需的吗?</strong></p>
<p>除了最上层的应用程序和最下层的硬件平台必须存在之外,作为中间层的函数库和内核并不是必须存在的:它们都是对下层资源进行了 <strong>抽象</strong> (Abstraction),
并为上层提供了一套运行环境。抽象的优点在于它让上层以较小的代价获得所需的功能,并同时可以提供一些保护。但抽象同时也是一种限制,会丧失一些
应有的灵活性。比如,当你在考虑在项目中应该使用哪个函数库的时候,就常常需要这方面的权衡:过多的抽象和过少的抽象自然都是不合适的。</p>
......@@ -303,8 +305,38 @@ linux-gnu 系统调用支持的版本 <code class="docutils literal notranslate"
直面底层硬件来解锁更大的抽象能力上限。</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>RISC-V 指令集拓展</p>
<p><strong>RISC-V 指令集拓展</strong></p>
<p>由于基于 RISC-V 架构的处理器可能用于嵌入式场景或是通用计算场景,因此指令集规范将指令集划分为最基本的 RV32/64I 以及若干标准指令集拓展。
每款处理器只需按照其实际应用场景按需实现指令集拓展即可。</p>
<ul class="simple">
<li><p>RV32/64I:每款处理器都必须实现的基本整数指令集。在 RV32I 中,每个通用寄存器的位宽为 32 位;在 RV64I 中则为 64 位。它可以用来模拟
绝大多数标准指令集拓展中的指令,除了比较特殊的 A 拓展,因为它需要特别的硬件支持。</p></li>
<li><p>M 拓展:提供整数乘除法相关指令。</p></li>
<li><p>A 拓展:提供原子指令和一些相关的内存同步机制,这个后面会展开。</p></li>
<li><p>F/D 拓展:提供单/双精度浮点数运算支持。</p></li>
<li><p>C 拓展:提供压缩指令拓展。</p></li>
</ul>
<p>G 拓展是基本整数指令集 I 再加上标准指令集拓展 MAFD 的总称,因此 riscv64gc 也就等同于 riscv64imafdc。我们剩下的内容都基于该处理器
架构完成。除此之外 RISC-V 架构还有很多标准指令集拓展,有一些还在持续更新中尚未稳定,有兴趣的读者可以浏览最新版的 RISC-V 指令集规范。</p>
</div>
</div>
<div class="section" id="rust">
<h2>Rust 标准库与核心库<a class="headerlink" href="#rust" title="Permalink to this headline"></a></h2>
<p>我们尝试一下将当前的 <code class="docutils literal notranslate"><span class="pre">Hello,</span> <span class="pre">world!</span></code> 程序的目标平台换成 riscv64gc-unknown-none-elf 看看会发生什么事情:</p>
<div class="highlight-console notranslate"><div class="highlight"><pre><span></span><span class="gp">$</span> cargo run --target riscv64gc-unknown-none-elf
<span class="go"> Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)</span>
<span class="go">error[E0463]: can&#39;t find crate for `std`</span>
<span class="go"> |</span>
<span class="go"> = note: the `riscv64gc-unknown-none-elf` target may not be installed</span>
</pre></div>
</div>
<p>在之前的环境配置中,我们已经在 rustup 工具链中安装了这个目标平台支持,因此并不是该目标平台未安装的问题。因此只是单纯的在这个目标平台上找不到
Rust 标准库 std。我们之前曾经提到过,编程语言的标准库或三方库的某些功能会直接或间接的用到操作系统提供的系统调用。但目前我们所选的目标平台不存在
任何操作系统支持,于是 Rust 并没有为这个目标平台支持完整的标准库 std。类似这样的平台通常被我们称为 <strong>裸机平台</strong> (bare-metal)。</p>
<p>幸运的是,Rust 有一个对 std 裁剪过后的核心库 core,这个库是不需要任何操作系统支持的,相对的它的功能也比较受限,但是也包含了 Rust 语言
相当一部分的核心机制,可以满足我们的大部分需求。在 Rust 语言生态中,有很多三方库也不依赖标准库 std 而仅仅依赖核心库 core,它们也可以很大
程度上减轻我们的编程负担。它们是我们能够在裸机平台挣扎求生的最主要倚仗。</p>
<p>于是,我们知道在裸机平台上我们要将对于标准库 std 的引用换成核心库 core。但是做起来其实并没有那么容易。</p>
</div>
</div>
......@@ -316,7 +348,7 @@ linux-gnu 系统调用支持的版本 <code class="docutils literal notranslate"
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
<a href="../rest-example.html" class="btn btn-neutral float-right" title="reStructuredText 基本语法" accesskey="n" rel="next">Next <span class="fa fa-arrow-circle-right"></span></a>
<a href="2remove-std.html" class="btn btn-neutral float-right" title="移除标准库依赖" accesskey="n" rel="next">Next <span class="fa fa-arrow-circle-right"></span></a>
<a href="index.html" class="btn btn-neutral float-left" title="第一章:编译运行 RV64 裸机程序" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left"></span> Previous</a>
......
<!DOCTYPE html>
<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>移除标准库依赖 &mdash; rCore-Tutorial-Book-v3 0.1 documentation</title>
<script type="text/javascript" src="../_static/js/modernizr.min.js"></script>
<script type="text/javascript" id="documentation_options" data-url_root="../" src="../_static/documentation_options.js"></script>
<script src="../_static/jquery.js"></script>
<script src="../_static/underscore.js"></script>
<script src="../_static/doctools.js"></script>
<script src="../_static/language_data.js"></script>
<script async="async" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/latest.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script type="text/javascript" src="../_static/js/theme.js"></script>
<link rel="stylesheet" href="../_static/css/theme.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<link rel="index" title="Index" href="../genindex.html" />
<link rel="search" title="Search" href="../search.html" />
<link rel="next" title="reStructuredText 基本语法" href="../rest-example.html" />
<link rel="prev" title="应用程序运行环境与平台支持" href="1app-ee-platform.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="../index.html" class="icon icon-home"> rCore-Tutorial-Book-v3
</a>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="../search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<p class="caption"><span class="caption-text">正文</span></p>
<ul class="current">
<li class="toctree-l1"><a class="reference internal" href="../quickstart.html">快速上手</a></li>
<li class="toctree-l1 current"><a class="reference internal" href="index.html">第一章:编译运行 RV64 裸机程序</a><ul class="current">
<li class="toctree-l2"><a class="reference internal" href="1app-ee-platform.html">应用程序运行环境与平台支持</a></li>
<li class="toctree-l2 current"><a class="current reference internal" href="#">移除标准库依赖</a><ul>
<li class="toctree-l3"><a class="reference internal" href="#println">移除 println! 宏</a></li>
<li class="toctree-l3"><a class="reference internal" href="#panic-handler">提供语义项 panic_handler</a></li>
<li class="toctree-l3"><a class="reference internal" href="#main">移除 main 函数</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<p class="caption"><span class="caption-text">开发注记</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../rest-example.html">reStructuredText 基本语法</a></li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" aria-label="top navigation">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="../index.html">rCore-Tutorial-Book-v3</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
<li><a href="../index.html">Docs</a> &raquo;</li>
<li><a href="index.html">第一章:编译运行 RV64 裸机程序</a> &raquo;</li>
<li>移除标准库依赖</li>
<li class="wy-breadcrumbs-aside">
<a href="../_sources/chapter1/2remove-std.rst.txt" rel="nofollow"> View page source</a>
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<div class="section" id="id1">
<h1>移除标准库依赖<a class="headerlink" href="#id1" title="Permalink to this headline"></a></h1>
<div class="toctree-wrapper compound">
</div>
<p>本节我们尝试移除之前的 <code class="docutils literal notranslate"><span class="pre">Hello</span> <span class="pre">world!</span></code> 程序对于标准库的依赖,使得它能够编译到裸机平台 RV64GC 上。</p>
<p>我们首先在 <code class="docutils literal notranslate"><span class="pre">os</span></code> 目录下新建 <code class="docutils literal notranslate"><span class="pre">.cargo</span></code> 目录,并在这个目录下创建 <code class="docutils literal notranslate"><span class="pre">config</span></code> 文件,并在里面输入如下内容:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[</span><span class="n">build</span><span class="p">]</span>
<span class="n">target</span> <span class="o">=</span> <span class="s2">&quot;riscv64gc-unknown-none-elf&quot;</span>
</pre></div>
</div>
<p>这会对于 Cargo 工具在 os 目录下的行为进行调整:现在默认会使用 riscv64gc 作为目标平台而不是原先的默认 x86_64-unknown-linux-gnu。
事实上,这是一种编译器运行所在的平台与编译器生成可执行文件的目标平台不同(分别是后者和前者)的情况。这是一种 <strong>交叉编译</strong> (Cross Compile)。</p>
<p>当然,这只是使得我们之后在 <code class="docutils literal notranslate"><span class="pre">cargo</span> <span class="pre">build</span></code> 的时候不必再加上 <code class="docutils literal notranslate"><span class="pre">--target</span></code> 参数的一个小 trick。如果我们现在 <code class="docutils literal notranslate"><span class="pre">cargo</span> <span class="pre">build</span></code> ,还是会和
上一小节一样出现找不到标准库 std 的错误。于是我们开始着手移除标准库。当然,这会产生一些副作用。</p>
<div class="section" id="println">
<h2>移除 println! 宏<a class="headerlink" href="#println" title="Permalink to this headline"></a></h2>
<p>我们在 <code class="docutils literal notranslate"><span class="pre">main.rs</span></code> 的开头加上一行 <code class="docutils literal notranslate"><span class="pre">#![no_std]</span></code> 来告诉 Rust 编译器不使用 Rust 标准库 std 转而使用核心库 core。编译器报出如下错误:</p>
<div class="admonition error">
<p class="admonition-title">Error</p>
<div class="highlight-console notranslate"><div class="highlight"><pre><span></span><span class="gp">$</span> cargo build
<span class="go"> Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)</span>
<span class="go">error: cannot find macro `println` in this scope</span>
<span class="go">--&gt; src/main.rs:4:5</span>
<span class="go"> |</span>
<span class="go">4 | println!(&quot;Hello, world!&quot;);</span>
<span class="go"> | ^^^^^^^</span>
</pre></div>
</div>
</div>
<p>我们之前提到过, println! 宏是由标准库 std 提供的,且会使用到一个名为 write 的系统调用。现在我们的条件还不足以自己实现一个 println! 宏,由于
使用了系统调用也不能在核心库 core 中找到它。我们目前先通过将它注释掉来绕过它。</p>
</div>
<div class="section" id="panic-handler">
<h2>提供语义项 panic_handler<a class="headerlink" href="#panic-handler" title="Permalink to this headline"></a></h2>
<div class="admonition error">
<p class="admonition-title">Error</p>
<div class="highlight-console notranslate"><div class="highlight"><pre><span></span><span class="gp">$</span> cargo build
<span class="go"> Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)</span>
<span class="go">error: `#[panic_handler]` function required, but not found</span>
</pre></div>
</div>
</div>
<p>在使用 Rust 编写应用程序的时候,我们常常在遇到了一些无法恢复的致命错误导致程序无法继续向下运行的时候手动或自动调用 panic! 宏来并打印出错的
位置让我们能够意识到它的存在,并进行一些后续处理。panic! 宏最典型的应用场景包括断言宏 assert! 失败或者对 <code class="docutils literal notranslate"><span class="pre">Option::None/Result::Err</span></code>
进行 <code class="docutils literal notranslate"><span class="pre">unwrap</span></code> 操作。</p>
<p>在标准库 std 中提供了 panic 的处理函数 <code class="docutils literal notranslate"><span class="pre">#[panic_handler]</span></code>,其大致功能是打印出错位置和原因并杀死当前应用。可惜的是在核心库 core 中并没有提供,
因此我们需要自己实现 panic 处理函数。</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p><strong>Rust 语义项 lang_items</strong></p>
<p>Rust 编译器内部的某些功能的实现并不是硬编码在语言内部的,而是以一种可插入的形式在库中提供。库只需要通过某种方式告诉编译器它的某个方法实现了
编译器内部的哪些功能,编译器就会采用库提供的方法来实现它内部对应的功能。通常只需要在库的方法前面加上一个标记即可。</p>
</div>
<p>我们开一个新的子模块 <code class="docutils literal notranslate"><span class="pre">lang_items.rs</span></code> 保存这些语义项,在里面提供 panic 处理函数的实现并通过标记通知编译器采用我们的实现:</p>
<div class="highlight-rust notranslate"><div class="highlight"><pre><span></span><span class="c1">// os/src/lang_items.rs</span>
<span class="k">use</span><span class="w"> </span><span class="n">core</span>::<span class="n">panic</span>::<span class="n">PanicInfo</span><span class="p">;</span><span class="w"></span>
<span class="cp">#[panic_handler]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">panic</span><span class="p">(</span><span class="n">_info</span>: <span class="kp">&amp;</span><span class="nc">PanicInfo</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="o">!</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">loop</span><span class="w"> </span><span class="p">{}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
</div>
<p>注意,panic 处理函数的函数签名需要一个 <code class="docutils literal notranslate"><span class="pre">PanicInfo</span></code> 的不可变借用作为输入参数,它在核心库中得以保留,这也是我们第一次与核心库打交道。之后我们
会从 <code class="docutils literal notranslate"><span class="pre">PanicInfo</span></code> 解析出错位置并打印出来,然后杀死应用程序。但目前我们什么都不做只是在原地 loop。</p>
</div>
<div class="section" id="main">
<h2>移除 main 函数<a class="headerlink" href="#main" title="Permalink to this headline"></a></h2>
<div class="admonition error">
<p class="admonition-title">Error</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ cargo build
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
error: requires `start` lang_item
</pre></div>
</div>
</div>
<p>编译器提醒我们缺少一个名为 <code class="docutils literal notranslate"><span class="pre">start</span></code> 的语义项。我们回忆一下,之前提到语言标准库和三方库作为应用程序的执行环境,需要负责在执行应用程序之前进行
一些初始化工作,然后才跳转到应用程序的入口点(也就是跳转到我们编写的 <code class="docutils literal notranslate"><span class="pre">main</span></code> 函数)开始执行。事实上 <code class="docutils literal notranslate"><span class="pre">start</span></code> 语义项正代表着标准库 std 在
执行应用程序之前需要进行的一些初始化工作。由于我们禁用了标准库,编译器也就找不到这项功能的实现了。</p>
<p>最简单的解决方案就是压根不让编译器使用这项功能。我们在 <code class="docutils literal notranslate"><span class="pre">main.rs</span></code> 的开头加入设置 <code class="docutils literal notranslate"><span class="pre">#![no_main]</span></code> 告诉编译器我们没有一般意义上的 <code class="docutils literal notranslate"><span class="pre">main</span></code> 函数,
并将原来的 <code class="docutils literal notranslate"><span class="pre">main</span></code> 函数删除。在失去了 <code class="docutils literal notranslate"><span class="pre">main</span></code> 函数的情况下,编译器也就不需要完成所谓的初始化工作了。</p>
<p>至此,我们成功移除了标准库的依赖并完成裸机平台上的构建。</p>
<div class="highlight-console notranslate"><div class="highlight"><pre><span></span><span class="gp">$</span> cargo build
<span class="go"> Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)</span>
<span class="go"> Finished dev [unoptimized + debuginfo] target(s) in 0.06s</span>
</pre></div>
</div>
<p>目前的代码如下:</p>
<div class="highlight-rust notranslate"><div class="highlight"><pre><span></span><span class="c1">// os/src/main.rs</span>
<span class="cp">#![no_std]</span><span class="w"></span>
<span class="cp">#![no_main]</span><span class="w"></span>
<span class="k">mod</span> <span class="nn">lang_items</span><span class="p">;</span><span class="w"></span>
<span class="c1">// os/src/lang_items.rs</span>
<span class="k">use</span><span class="w"> </span><span class="n">core</span>::<span class="n">panic</span>::<span class="n">PanicInfo</span><span class="p">;</span><span class="w"></span>
<span class="cp">#[panic_handler]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">panic</span><span class="p">(</span><span class="n">_info</span>: <span class="kp">&amp;</span><span class="nc">PanicInfo</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="o">!</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">loop</span><span class="w"> </span><span class="p">{}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
</div>
<p>本小节我们固然脱离了标准库,通过了编译器的检验,但也是伤筋动骨,将原有的很多功能弱化甚至直接删除,看起来距离在 RV64GC 平台上打印
<code class="docutils literal notranslate"><span class="pre">Hello</span> <span class="pre">world!</span></code> 相去甚远了(我们甚至连 println! 和 <code class="docutils literal notranslate"><span class="pre">main</span></code> 函数都删除了)。不要着急,接下来我们会以自己的方式来重塑这些
功能,并最终完成我们的目标。</p>
</div>
</div>
</div>
</div>
<footer>
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
<a href="../rest-example.html" class="btn btn-neutral float-right" title="reStructuredText 基本语法" accesskey="n" rel="next">Next <span class="fa fa-arrow-circle-right"></span></a>
<a href="1app-ee-platform.html" class="btn btn-neutral float-left" title="应用程序运行环境与平台支持" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left"></span> Previous</a>
</div>
<hr/>
<div role="contentinfo">
<p>
&copy; Copyright 2020, Yifan Wu
</p>
</div>
Built with <a href="http://sphinx-doc.org/">Sphinx</a> using a <a href="https://github.com/rtfd/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
</div>
</section>
</div>
<script type="text/javascript">
jQuery(function () {
SphinxRtdTheme.Navigation.enable(true);
});
</script>
</body>
</html>
\ No newline at end of file
......@@ -86,6 +86,7 @@
<li class="toctree-l1"><a class="reference internal" href="../quickstart.html">快速上手</a></li>
<li class="toctree-l1 current"><a class="current reference internal" href="#">第一章:编译运行 RV64 裸机程序</a><ul>
<li class="toctree-l2"><a class="reference internal" href="1app-ee-platform.html">应用程序运行环境与平台支持</a></li>
<li class="toctree-l2"><a class="reference internal" href="2remove-std.html">移除标准库依赖</a></li>
</ul>
</li>
</ul>
......
无法预览此类型文件
此差异已折叠。
......@@ -3,7 +3,7 @@
.. toctree::
:hidden:
:maxdepth: 3
:maxdepth: 4
作为一切的开始,让我们使用 Cargo 工具来创建一个 Rust 项目。它看上去没有任何特别之处:
......@@ -34,7 +34,7 @@
println!("Hello, world!");
}
利用 Cargo 工具即可一条命令实现构建并运行项目:
进入 os 项目根目录下,利用 Cargo 工具即可一条命令实现构建并运行项目:
.. code-block:: console
......@@ -72,7 +72,7 @@
.. note::
``Hello, world!`` 用到了哪些系统调用?
**Hello, world! 用到了哪些系统调用?**
从之前的 ``cargo run`` 的输出可以看出之前构建的可执行文件是在 target/debug 目录下的 os 。
在 Ubuntu 系统上,可以通过 ``strace`` 工具来运行一个程序并输出程序运行过程当中向内核请求的所有的系统调用及其返回值。
......@@ -97,7 +97,7 @@
.. note::
多层执行环境都是必需的吗?
**多层执行环境都是必需的吗?**
除了最上层的应用程序和最下层的硬件平台必须存在之外,作为中间层的函数库和内核并不是必须存在的:它们都是对下层资源进行了 **抽象** (Abstraction),
并为上层提供了一套运行环境。抽象的优点在于它让上层以较小的代价获得所需的功能,并同时可以提供一些保护。但抽象同时也是一种限制,会丧失一些
......@@ -171,5 +171,40 @@ linux-gnu 系统调用支持的版本 ``riscv64gc-unknown-linux-gnu``,是因
.. note::
RISC-V 指令集拓展
**RISC-V 指令集拓展**
由于基于 RISC-V 架构的处理器可能用于嵌入式场景或是通用计算场景,因此指令集规范将指令集划分为最基本的 RV32/64I 以及若干标准指令集拓展。
每款处理器只需按照其实际应用场景按需实现指令集拓展即可。
- RV32/64I:每款处理器都必须实现的基本整数指令集。在 RV32I 中,每个通用寄存器的位宽为 32 位;在 RV64I 中则为 64 位。它可以用来模拟
绝大多数标准指令集拓展中的指令,除了比较特殊的 A 拓展,因为它需要特别的硬件支持。
- M 拓展:提供整数乘除法相关指令。
- A 拓展:提供原子指令和一些相关的内存同步机制,这个后面会展开。
- F/D 拓展:提供单/双精度浮点数运算支持。
- C 拓展:提供压缩指令拓展。
G 拓展是基本整数指令集 I 再加上标准指令集拓展 MAFD 的总称,因此 riscv64gc 也就等同于 riscv64imafdc。我们剩下的内容都基于该处理器
架构完成。除此之外 RISC-V 架构还有很多标准指令集拓展,有一些还在持续更新中尚未稳定,有兴趣的读者可以浏览最新版的 RISC-V 指令集规范。
Rust 标准库与核心库
----------------------------------
我们尝试一下将当前的 ``Hello, world!`` 程序的目标平台换成 riscv64gc-unknown-none-elf 看看会发生什么事情:
.. code-block:: console
$ cargo run --target riscv64gc-unknown-none-elf
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
error[E0463]: can't find crate for `std`
|
= note: the `riscv64gc-unknown-none-elf` target may not be installed
在之前的环境配置中,我们已经在 rustup 工具链中安装了这个目标平台支持,因此并不是该目标平台未安装的问题。因此只是单纯的在这个目标平台上找不到
Rust 标准库 std。我们之前曾经提到过,编程语言的标准库或三方库的某些功能会直接或间接的用到操作系统提供的系统调用。但目前我们所选的目标平台不存在
任何操作系统支持,于是 Rust 并没有为这个目标平台支持完整的标准库 std。类似这样的平台通常被我们称为 **裸机平台** (bare-metal)。
幸运的是,Rust 有一个对 std 裁剪过后的核心库 core,这个库是不需要任何操作系统支持的,相对的它的功能也比较受限,但是也包含了 Rust 语言
相当一部分的核心机制,可以满足我们的大部分需求。在 Rust 语言生态中,有很多三方库也不依赖标准库 std 而仅仅依赖核心库 core,它们也可以很大
程度上减轻我们的编程负担。它们是我们能够在裸机平台挣扎求生的最主要倚仗。
于是,我们知道在裸机平台上我们要将对于标准库 std 的引用换成核心库 core。但是做起来其实并没有那么容易。
\ No newline at end of file
移除标准库依赖
==========================
.. toctree::
:hidden:
:maxdepth: 4
本节我们尝试移除之前的 ``Hello world!`` 程序对于标准库的依赖,使得它能够编译到裸机平台 RV64GC 上。
我们首先在 ``os`` 目录下新建 ``.cargo`` 目录,并在这个目录下创建 ``config`` 文件,并在里面输入如下内容:
.. code-block::
[build]
target = "riscv64gc-unknown-none-elf"
这会对于 Cargo 工具在 os 目录下的行为进行调整:现在默认会使用 riscv64gc 作为目标平台而不是原先的默认 x86_64-unknown-linux-gnu。
事实上,这是一种编译器运行所在的平台与编译器生成可执行文件的目标平台不同(分别是后者和前者)的情况。这是一种 **交叉编译** (Cross Compile)。
当然,这只是使得我们之后在 ``cargo build`` 的时候不必再加上 ``--target`` 参数的一个小 trick。如果我们现在 ``cargo build`` ,还是会和
上一小节一样出现找不到标准库 std 的错误。于是我们开始着手移除标准库。当然,这会产生一些副作用。
移除 println! 宏
----------------------------------
我们在 ``main.rs`` 的开头加上一行 ``#![no_std]`` 来告诉 Rust 编译器不使用 Rust 标准库 std 转而使用核心库 core。编译器报出如下错误:
.. error::
.. code-block:: console
$ cargo build
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
error: cannot find macro `println` in this scope
--> src/main.rs:4:5
|
4 | println!("Hello, world!");
| ^^^^^^^
我们之前提到过, println! 宏是由标准库 std 提供的,且会使用到一个名为 write 的系统调用。现在我们的条件还不足以自己实现一个 println! 宏,由于
使用了系统调用也不能在核心库 core 中找到它。我们目前先通过将它注释掉来绕过它。
提供语义项 panic_handler
----------------------------------------------------
.. error::
.. code-block:: console
$ cargo build
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
error: `#[panic_handler]` function required, but not found
在使用 Rust 编写应用程序的时候,我们常常在遇到了一些无法恢复的致命错误导致程序无法继续向下运行的时候手动或自动调用 panic! 宏来并打印出错的
位置让我们能够意识到它的存在,并进行一些后续处理。panic! 宏最典型的应用场景包括断言宏 assert! 失败或者对 ``Option::None/Result::Err``
进行 ``unwrap`` 操作。
在标准库 std 中提供了 panic 的处理函数 ``#[panic_handler]``,其大致功能是打印出错位置和原因并杀死当前应用。可惜的是在核心库 core 中并没有提供,
因此我们需要自己实现 panic 处理函数。
.. note::
**Rust 语义项 lang_items**
Rust 编译器内部的某些功能的实现并不是硬编码在语言内部的,而是以一种可插入的形式在库中提供。库只需要通过某种方式告诉编译器它的某个方法实现了
编译器内部的哪些功能,编译器就会采用库提供的方法来实现它内部对应的功能。通常只需要在库的方法前面加上一个标记即可。
我们开一个新的子模块 ``lang_items.rs`` 保存这些语义项,在里面提供 panic 处理函数的实现并通过标记通知编译器采用我们的实现:
.. code-block:: rust
// os/src/lang_items.rs
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
注意,panic 处理函数的函数签名需要一个 ``PanicInfo`` 的不可变借用作为输入参数,它在核心库中得以保留,这也是我们第一次与核心库打交道。之后我们
会从 ``PanicInfo`` 解析出错位置并打印出来,然后杀死应用程序。但目前我们什么都不做只是在原地 loop。
移除 main 函数
-----------------------------
.. error::
.. code-block::
$ cargo build
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
error: requires `start` lang_item
编译器提醒我们缺少一个名为 ``start`` 的语义项。我们回忆一下,之前提到语言标准库和三方库作为应用程序的执行环境,需要负责在执行应用程序之前进行
一些初始化工作,然后才跳转到应用程序的入口点(也就是跳转到我们编写的 ``main`` 函数)开始执行。事实上 ``start`` 语义项正代表着标准库 std 在
执行应用程序之前需要进行的一些初始化工作。由于我们禁用了标准库,编译器也就找不到这项功能的实现了。
最简单的解决方案就是压根不让编译器使用这项功能。我们在 ``main.rs`` 的开头加入设置 ``#![no_main]`` 告诉编译器我们没有一般意义上的 ``main`` 函数,
并将原来的 ``main`` 函数删除。在失去了 ``main`` 函数的情况下,编译器也就不需要完成所谓的初始化工作了。
至此,我们成功移除了标准库的依赖并完成裸机平台上的构建。
.. code-block:: console
$ cargo build
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
Finished dev [unoptimized + debuginfo] target(s) in 0.06s
目前的代码如下:
.. code-block:: rust
// os/src/main.rs
#![no_std]
#![no_main]
mod lang_items;
// os/src/lang_items.rs
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
本小节我们固然脱离了标准库,通过了编译器的检验,但也是伤筋动骨,将原有的很多功能弱化甚至直接删除,看起来距离在 RV64GC 平台上打印
``Hello world!`` 相去甚远了(我们甚至连 println! 和 ``main`` 函数都删除了)。不要着急,接下来我们会以自己的方式来重塑这些
功能,并最终完成我们的目标。
\ No newline at end of file
......@@ -6,4 +6,5 @@
:hidden:
:maxdepth: 3
1app-ee-platform
\ No newline at end of file
1app-ee-platform
2remove-std
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册