提交 7dafce30 编写于 作者: chyyuu1972's avatar chyyuu1972

update ch1:sec1,sec3

上级 e2d23f4d
......@@ -218,8 +218,14 @@ Rust 标准库与核心库
Rust 标准库 std。我们之前曾经提到过,编程语言的标准库或三方库的某些功能会直接或间接的用到操作系统提供的系统调用。但目前我们所选的目标平台不存在
任何操作系统支持,于是 Rust 并没有为这个目标平台支持完整的标准库 std。类似这样的平台通常被我们称为 **裸机平台** (bare-metal)。
.. note::
**Rust语言标准库**
Rust语言标准库是让Rust语言开发的软件具备可移植性的基础,类似于C语言的libc标准库。它是一组最小的、经过实战检验的共享抽象,适用于更广泛的Rust生态系统开发。它提供了核心类型,如Vec和Option、类库定义的语言原语操作、标准宏、I/O和多线程等。默认情况下,所有Rust crate都可以使用std来支持Rust应用程序的开发。但Rust语言标准库的一个限制是,它需要有操作系统的支持。所以,如果你要实现的软件是运行在裸机上的操作系统,就不能直接用Rust语言标准库了。
幸运的是,Rust 有一个对 std 裁剪过后的核心库 core,这个库是不需要任何操作系统支持的,相对的它的功能也比较受限,但是也包含了 Rust 语言
相当一部分的核心机制,可以满足我们的大部分需求。Rust 语言是一种面向系统(包括操作系统)开发的语言,所以在 Rust 语言生态中,有很多三方库也不依赖标准库 std 而仅仅依赖核心库 core,它们也可以很大
程度上减轻我们的编程负担。它们是我们能够在裸机平台挣扎求生的最主要倚仗。
程度上减轻我们的编程负担。它们是我们能够在裸机平台挣扎求生的最主要倚仗,也是大部分运行在没有操作系统支持的Rust嵌入式软件的必备
于是,我们知道在裸机平台上我们要将对于标准库 std 的引用换成核心库 core。但是做起来其实并没有那么容易。
\ No newline at end of file
于是,我们知道在裸机平台上我们要将对于标准库 std 的引用换成核心库 core。但是做起来其实还要有一些事情需要解决。
\ No newline at end of file
......@@ -10,13 +10,14 @@
我们在上一小节提到过,一个应用程序的运行离不开下面多层执行环境栈的支撑。以 ``Hello, world!`` 程序为例,在目前广泛使用的操作系统上,
它就至少需要经历以下层层递进的初始化过程:
- 一段汇编代码对硬件进行初始化,让上层包括内核在内的软件得以运行;
- 要运行该程序的时候,内核分配相应资源,将程序代码和数据载入内存,并赋予 CPU 使用权,由此应用程序可以运行;
- 程序员编写的代码是应用程序的一部分,它需要标准库进行一些初始化工作后才能运行。
- 启动OS:硬件启动后,会有一段代码(一般统称为bootloader)对硬件进行初始化,让包括内核在内的系统软件得以运行;
- OS准备好应用程序执行的环境:要运行该应用程序的时候,内核分配相应资源,将程序代码和数据载入内存,并赋予 CPU 使用权,由此应用程序可以运行;
- 应用程序开始执行:程序员编写的代码是应用程序的一部分,它需要标准库/核心库进行一些初始化工作后才能运行。
但在上一小节中,由于目标平台 ``riscv64gc-unknown-none-elf`` 没有任何操作系统支持,我们只能禁用标准库并移除默认的 main 函数
入口。但是最终我们还是要将 main 恢复回来并且在里面输出 ``Hello, world!`` 的。因此,我们需要知道具体需要做哪些初始化工作才能支持
main 的运行。
不过我们的目标是实现在裸机上执行的应用。
在上一小节中,由于目标平台 ``riscv64gc-unknown-none-elf`` 没有任何操作系统支持,我们只能禁用标准库并移除默认的 main 函数
入口。但是最终我们还是要将 main 函数恢复回来并且输出 ``Hello, world!`` 的。因此,我们需要知道具体需要做哪些初始化工作才能支持
应用程序在裸机上的运行。
而这又需要明确两点:首先是系统在做这些初始化工作之前处于什么状态,在做完初始化工作也就是即将执行 main 函数之前又处于什么状态。比较二者
即可得出答案。
......@@ -24,8 +25,7 @@ main 的运行。
.. _term-physical-address:
.. _term-physical-memory:
让我们从 CPU 加电后第一条指令开始讲起。对于裸机平台 ``riscv64gc-unknown-none-elf`` 而言,它的 pc 寄存器会被设置为 ``0x80000000`` ,
也就是说它会从这个 **物理地址** (Physical Address) 开始一条条取指并执行放置于 **物理内存** (Physical Memory) 中的指令。
.. note::
......@@ -49,6 +49,18 @@ main 的运行。
但实际上不止如此,我们还需要考虑栈的设置。
.. note::
**CPU 加电后在做啥?**
CPU加电后的执行细节与具体硬件相关,我们这里以QEMU模拟器为具体例子简单介绍一下。
这需要从 CPU 加电后如何初始化,如何执行第一条指令开始讲起。对于我们采用的QEMU模拟器而言,它模拟了一台标准的RISC-V64计算机。我们启动QEMU时,可设置一些参数,在RISC-V64计算机启动执行前,先在其模拟的内存中放置好BootLoader程序和操作系统的二进制代码。这可以通过查看 ``os/Makefile`` 文件中包含 ``qemu-system-riscv64`` 的相关内容来了解。
- ``-bios $(BOOTLOADER)`` 这个参数意味着硬件内存中的特定位置 ``0x80000000`` 处放置了一个BootLoader程序--RustSBI(戳 :doc:`../appendix-c/index` 可以进一步了解RustSBI。)。
- ``-device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA)`` 这个参数表示硬件内存中的特定位置 ``$(KERNEL_ENTRY_PA)`` 放置了操作系统的二进制代码 ``$(KERNEL_BIN)`` 。 ``$(KERNEL_ENTRY_PA)`` 的值是 ``0x80020000`` 。
当我们执行包含上次参数的qemu-system-riscv64软件,就意味给这台虚拟的RISC-V64计算机加电了。此时,CPU的PC寄存器会指向 ``0x1000`` 的位置,这个位置上是CPU加电后执行的第一条指令,它会很快跳转到 ``0x80000000`` 处,即RustSBI的第一条指令。RustSBI完成基本的硬件初始化后,会跳转操作系统的二进制代码 ``$(KERNEL_BIN)`` 所在内存位置 ``0x80020000`` ,执行操作系统的第一条指令。这时我们的编写的操作系统才开始正式工作。
.. _function-call-and-stack:
函数调用与栈
......@@ -110,7 +122,9 @@ main 的运行。
在 RISC-V 架构中,
通常使用 ra(x1) 寄存器作为其中的 rd ,因此在函数返回的时候,只需跳转回 ra 所保存的地址即可。事实上在函数返回的时候我们常常使用一条
**伪指令** (Pseudo Instruction) 跳转回调用之前的位置: ``ret`` 。它会被汇编器翻译为 ``jalr x0, 0(x1)``,含义为跳转到寄存器
ra 保存的物理地址,由于 x0 是一个恒为 0 的寄存器,在 rd 中保存这一步被省略。总结一下,在进行函数调用的时候,我们通过 jalr 指令
ra 保存的物理地址,由于 x0 是一个恒为 0 的寄存器,在 rd 中保存这一步被省略。
总结一下,在进行函数调用的时候,我们通过 jalr 指令
保存返回地址并实现跳转;而在函数即将返回的时候,则通过 ret 指令跳转之前的下一条指令继续执行。这两条指令实现了函数调用流程的核心机制。
由于我们是在 ra 寄存器中保存返回地址的,我们要保证它在函数执行的全程不发生变化,不然在 ret 之后就会跳转到错误的位置。事实上编译器
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册