Files
rust-based-os-comp2022/guide/source/chapter2/3batch-system.rst

168 lines
6.9 KiB
ReStructuredText
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
.. _term-batchos:
实现批处理操作系统
==============================
.. toctree::
:hidden:
:maxdepth: 5
将应用程序链接到内核
--------------------------------------------
在本章中,我们要把应用程序的二进制镜像文件作为数据段链接到内核里,
内核需要知道应用程序的数量和它们的位置。
``os/src/main.rs`` 中能够找到这样一行:
.. code-block:: rust
core::arch::global_asm!(include_str!("link_app.S"));
这里我们引入了一段汇编代码 ``link_app.S`` ,它是在 ``make run`` 构建操作系统时自动生成的,里面的内容大致如下:
.. code-block:: asm
:linenos:
# os/src/link_app.S
.align 3
.section .data
.global _num_app
_num_app:
.quad 3
.quad app_0_start
.quad app_1_start
.quad app_2_start
.quad app_2_end
.section .data
.global app_0_start
.global app_0_end
app_0_start:
.incbin "../user/target/riscv64gc-unknown-none-elf/release/hello_world.bin"
app_0_end:
.section .data
.global app_1_start
.global app_1_end
app_1_start:
.incbin "../user/target/riscv64gc-unknown-none-elf/release/bad_address.bin"
app_1_end:
.section .data
.global app_2_start
.global app_2_end
app_2_start:
.incbin "../user/target/riscv64gc-unknown-none-elf/release/power.bin"
app_2_end:
第 13 行开始的三个数据段分别插入了三个应用程序的二进制镜像,
并且各自有一对全局符号 ``app_*_start, app_*_end`` 指示它们的开始和结束位置。
而第 3 行开始的另一个数据段相当于一个 64 位整数数组。
数组中的第一个元素表示应用程序的数量,后面则按照顺序放置每个应用程序的起始地址,
最后一个元素放置最后一个应用程序的结束位置。这样数组中相邻两个元素记录了每个应用程序的始末位置,
这个数组所在的位置由全局符号 ``_num_app`` 所指示。
这个文件是在 ``cargo build`` 时,由脚本 ``os/build.rs`` 控制生成的。
找到并加载应用程序二进制码
-----------------------------------------------
我们在 ``os````batch`` 子模块中实现一个应用管理器 ``AppManager`` ,结构体定义如下:
.. code-block:: rust
struct AppManager {
num_app: usize,
current_app: usize,
app_start: [usize; MAX_APP_NUM + 1],
}
初始化 ``AppManager`` 的全局实例:
.. code-block:: rust
lazy_static! {
static ref APP_MANAGER: UPSafeCell<AppManager> = unsafe {
UPSafeCell::new({
extern "C" {
fn _num_app();
}
let num_app_ptr = _num_app as usize as *const usize;
let num_app = num_app_ptr.read_volatile();
let mut app_start: [usize; MAX_APP_NUM + 1] = [0; MAX_APP_NUM + 1];
let app_start_raw: &[usize] =
core::slice::from_raw_parts(num_app_ptr.add(1), num_app + 1);
app_start[..=num_app].copy_from_slice(app_start_raw);
AppManager {
num_app,
current_app: 0,
app_start,
}
})
};
}
初始化的逻辑很简单,就是找到 ``link_app.S`` 中提供的符号 ``_num_app`` ,并从这里开始解析出应用数量以及各个应用的开头地址。
用容器 ``UPSafeCell`` 包裹 ``AppManager`` 是为了防止全局对象 ``APP_MANAGER`` 被重复获取。
.. note::
``UPSafeCell`` 实现在 ``sync`` 模块中,调用 ``exclusive_access`` 方法能获取其内部对象的可变引用,
如果程序运行中同时存在多个这样的引用,会触发 ``already borrowed: BorrowMutError``
``UPSafeCell`` 既提供了内部可变性,又在单核情境下防止了内部对象被重复借用,我们将在后文中多次见到它。
这里使用了外部库 ``lazy_static`` 提供的 ``lazy_static!`` 宏。
``lazy_static!`` 宏提供了全局变量的运行时初始化功能。一般情况下,全局变量必须在编译期设置初始值,
但是有些全局变量的初始化依赖于运行期间才能得到的数据。
如这里我们借助 ``lazy_static!`` 声明了一个 ``AppManager`` 结构的名为 ``APP_MANAGER`` 的全局实例,
只有在它第一次被使用到的时候才会进行实际的初始化工作。
``AppManager`` 的方法中, ``print_app_info/get_current_app/move_to_next_app`` 都相当简单直接,需要说明的是 ``load_app``
.. code-block:: rust
:linenos:
unsafe fn load_app(&self, app_id: usize) {
if app_id >= self.num_app {
panic!("All applications completed!");
}
info!("[kernel] Loading app_{}", app_id);
// clear icache
core::arch::asm!("fence.i");
// clear app area
core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, APP_SIZE_LIMIT).fill(0);
let app_src = core::slice::from_raw_parts(
self.app_start[app_id] as *const u8,
self.app_start[app_id + 1] - self.app_start[app_id],
);
let app_dst = core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, app_src.len());
app_dst.copy_from_slice(app_src);
}
这个方法负责将参数 ``app_id`` 对应的应用程序的二进制镜像加载到物理内存以 ``0x80400000`` 起始的位置,
这个位置是批处理操作系统和应用程序之间约定的常数地址。
我们将从这里开始的一块内存清空,然后找到待加载应用二进制镜像的位置,并将它复制到正确的位置。
清空内存前,我们插入了一条奇怪的汇编指令 ``fence.i`` ,它是用来清理 i-cache 的。
我们知道, 缓存又分成 **数据缓存** (d-cache) 和 **指令缓存** (i-cache) 两部分,分别在 CPU 访存和取指的时候使用。
通常情况下, CPU 会认为程序的代码段不会发生变化,因此 i-cache 是一种只读缓存。
但在这里,我们会修改会被 CPU 取指的内存区域,使得 i-cache 中含有与内存不一致的内容,
必须使用 ``fence.i`` 指令手动清空 i-cache ,让里面所有的内容全部失效,
才能够保证程序执行正确性。
.. warning::
**模拟器与真机的不同之处**
在 Qemu 模拟器上,即使不加刷新 i-cache 的指令,大概率也能正常运行,但在物理计算机上不是这样。
``batch`` 子模块对外暴露出如下接口:
- ``init`` :调用 ``print_app_info`` 的时第一次用到了全局变量 ``APP_MANAGER`` ,它在这时完成初始化;
- ``run_next_app`` :批处理操作系统的核心操作,即加载并运行下一个应用程序。
批处理操作系统完成初始化,或者应用程序运行结束/出错后会调用该函数。下节再介绍其具体实现。