0%

2024年开源操作系统训练营总结-winddevil/ArceOS第一节课笔记

解决rust-analyzer的问题

创建:.vscode/settings.json:

1
2
3
{
    "rust-analyzer.cargo.features": ["axstd"]
}

这里一定要注意对rust-analyzer进行Restart Server,这个操作也可以见我的这篇博客.

直接用vscode打开wsl环境下的工程

直接在wslcmd下输入code ..

这里可能会显示在安装VSCode Server,不是在安装VSCode本身,而是在安装一个服务,安装好自己就帮你打开了.

组件化内核

不同的内核要求就有不同的设计.

Linux是宏内核就照着宏内核设计.

那么组件化内核就照着组件化内核设计.

复杂的内核=很多小的内核合起来的

宏内核和Hypervisor与Unikernel没有明显的界限,因为这实际上是一个设计模式.

  • 协作性
  • 定位容易,维护难度低
  • 开发效率高

用组件化内核替代原本的宏内核.但是这个内核是组件化开发的,最终组成一个总的内核.

UniKernel

有一个非常不同的地方应用和内核都在内核态并且在同一个地址空间里,互相是可见的.

RTOS似乎就是一个UniKernel.

那么打包完是同一个image.

那么宏内核是内核是一个image应用是另一个image,用syscall来打通.

因为是同一特权级,所以安全性不太行.

那么如果UniKernel需要保证安全性,那么就需要Hypervisor来帮你解决这个问题.

不同的组件可以用axruntime来组织.

裸机->逻辑程序

裸机->unikernel->OS程序

用OS去实现RTOS.

用裸机+虚拟化实现类似于docker的功能.

实验支撑的开发

每一节课是一个针对每个需求的内核.

那么新的内核是从旧的内核组建而成的.

宏内核和Unikernel的区别是加了用户特权级和用户的地址空间.看起来是增加了两个组件形成的,实际上到了很远的一条路.

让Unikernel实现Hypervisor,这样就可以实现虚拟化.从Host升级到Guest.

宏内核的东西比较复杂

实验环境的建立

使用WSL2+Ubuntu 22.04.2 LTS环境.

安装 WSL | Microsoft Learn

Windows Terminal - Windows官方下载 | 微软应用商店 | Microsoft Store

框架

引导过程

是通过axhal.

实际上使用的是_start这个指针.

通过一系列的asm操作来进行完成页表和函数调用和MMU的启动的支持.

日志级别控制与features

使用Cargo.toml来控制features.

使用环境变量:

  1. 具体环境变量
  2. 使用通用环境变量

三个部分汇集起来到axfeat

课后练习-支持带颜色的打印输出

[print_with_color]: 支持带颜色的打印输出。

要求:

  1. 修改一个组件的实现
  2. 执行make run A=exercises/print_with_color

这一点非常重要

预期:字符串输出带颜色。(具体颜色不做要求)
提示:在不同层次的组件上修改,影响的输出范围不同。
例如,修改axstd可能只影响println!的输出;修改axhal则可能一并影响ArceOS启动信息的颜色。

通过修改APP层实现

修改exercises\print_with_color\src\main.rs:

1
2
3
4
... ...
fn main() {
println!("\x1b[31m[WithColor]: Hello, Arceos!\x1b[0m");
}

分支名称:print_with_color_app

通过修改ulib:axstd来实现

ulib\axstd\src\macros.rs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#[macro_export]
macro_rules! print {
($($arg:tt)*) => {
$crate::io::__print_impl(format_args!("\x1b[31m{}\x1b[0m", format_args!($($arg)*)));
}
}

/// Prints to the standard output, with a newline.
#[macro_export]
macro_rules! println {
() => { $crate::print!("\n") };
($($arg:tt)*) => {
$crate::io::__print_impl(format_args!("\x1b[31m{}\n\x1b[0m", format_args!($($arg)*)));
}
}

分支名称:print_with_color_axstd

通过修改axhal:write_bytes来实现

修改modules\axhal\src\lib.rs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
... ...
pub mod console {
pub use super::platform::console::*;

/// Write a slice of bytes to the console.
pub fn write_bytes(bytes: &[u8]) {
let color_begin = "\x1b[31m";
let color_end = "\x1b[0m";
for c in color_begin.bytes() {
putchar(c);
}
for c in bytes {
putchar(*c);
}
for c in color_end.bytes() {
putchar(c);
}
}
}
... ...

分支名称:print_with_color_axhal

通过修改axlog:ax_println来实现(不了)

可以看到:modules\axruntime\src\lib.rs里调用了这个宏,

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
... ...
pub extern "C" fn rust_main(cpu_id: usize, dtb: usize) -> ! {
ax_println!("{}", LOGO);
ax_println!(
"\
arch = {}\n\
platform = {}\n\
target = {}\n\
smp = {}\n\
build_mode = {}\n\
log_level = {}\n\
",
option_env!("AX_ARCH").unwrap_or(""),
option_env!("AX_PLATFORM").unwrap_or(""),
option_env!("AX_TARGET").unwrap_or(""),
option_env!("AX_SMP").unwrap_or(""),
option_env!("AX_MODE").unwrap_or(""),
option_env!("AX_LOG").unwrap_or(""),
);
#[cfg(feature = "rtc")]
ax_println!(
"Boot at {}\n",
chrono::DateTime::from_timestamp_nanos(axhal::time::wall_time_nanos() as _),
);
... ...
}

并且这个宏的位置在modules\axlog\src\lib.rs,我们修改它:

1
2
3
4
5
6
7
8
... ...
macro_rules! ax_println {
() => { $crate::ax_print!("\n") };
($($arg:tt)*) => {
$crate::__print_impl(format_args!("\x1b[31m{}\x1b[0m\n", format_args!($($arg)*)));
}
}
... ...

这里只能使得如下部分变成红色,而不能满足题意:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
       d8888                            .d88888b.   .d8888b.
d88888 d88P" "Y88b d88P Y88b
d88P888 888 888 Y88b.
d88P 888 888d888 .d8888b .d88b. 888 888 "Y888b.
d88P 888 888P" d88P" d8P Y8b 888 888 "Y88b.
d88P 888 888 888 88888888 888 888 "888
d8888888888 888 Y88b. Y8b. Y88b. .d88P Y88b d88P
d88P 888 888 "Y8888P "Y8888 "Y88888P" "Y8888P"

arch = riscv64
platform = riscv64-qemu-virt
target = riscv64gc-unknown-none-elf
smp = 1
build_mode = release
log_level = warn

分支名称:print_with_color_axlog

问题和疑问

  1. make是怎么编译的?
  2. axruntime是怎么加载的编译好的APP并且运行的?
  3. sbi用的什么?

axstd支持Collections

具体可以看这里的介绍:集合类型 - Rust语言圣经(Rust Course)

最开始我学Rust的时候没有内化这个概念.

实际上集合类型就是VectorHashMap以及String这样的类型.

其实到这里我们自己就可以总结出了:

类型长度可变->指针地址和大小变量存在栈上,具体内容存在堆上->需要堆的支持->需要动态内存分配器.

那么实际上要支持Collections就是要支持一个动态内存分配器.

rCore中对动态内存分配器的描述:[rCore学习笔记 028] Rust 中的动态内存分配 - winddevil - 博客园

rCore中引用的动态内存分配器:[rCore学习笔记 029] 动态内存分配器实现-以buddy_system_allocator源码为例 - winddevil - 博客园

这里注意这张图,在Unikernel中,内存管理器也是和APP放在一起的,主要的思想还是APP和Kernel在同一特权级.

alloc实现接口

这个是需要实现两个功能:

  1. 实现byteAllocatorpageAllocatir.
  2. byteAllocator只需要实现rust自带的一个Trait#[global_alloctor]即可
  3. pageAllocator的实现方式是用了一个全局变量来实现相应的功能的

这里的源代码在modules/axalloc/src/lib.rs这里看.

这里是实现了一个GlobalAllocator类,然后实例化一个

  1. 实现GlobalAllocTrait给它,有allocdealloc这两个方法,即可实现内存分配.用#[global_alloctor]标注这个实例即可.
  2. GlobalAllocator实现了alloc_pagesdealloc_pages,通过直接获取这个实例

这里实现的时候traitfnstruct本身的fn重合,这是允许的.

调用时,MyTrait::do_something(&struct)struct.do_something两种调用形式是调用的不同的方法.

实现方法:

  1. 最开始的时候页分配器是先分配一部分内存给字节分配器的
  2. 先找字节分配器分配,任何如果不够了找页分配器追加

在当前为了给HashMap提供分配器的是core库里allocator里自带的TlsfByteAllocator作为字节分配器,BitmapPageAllocator作为页分配器.

那么为了实现自己的字节分配器就需要要给它实现两个Trait,即BaseAllocatorByteAllocator.

这个下一节可能会讲到.

课后练习-支持HashMap类型

这里要搞懂关于库的文件夹的互相依赖关系.

为什么core里没HashMap?

因为HashMap需要的随机数生成器涉及到体系结构.

那么这里的random()是来自于axhal就非常的合理了.

有关于Cargo.toml

两个Cargo.toml:

  1. .\Cargo.toml
  2. ulib\axstd\Cargo.toml

使用cargo tree -p axstd -e features可以很好地看到features结构.

有关于结构问题

这里注意一点,在ulib/axstd/src/lib.rs里已经引用了collections,

1
pub use alloc::{boxed, collections, format, string, vec};

而在exercises/support_hashmap/src/main.rs里引用的是:

1
use std::collections::HashMap;

rust这个项目里发现,library\alloccollection里并没有HashMap,HashMap是在library\stdcollection里.

所以其实我们是要把这两个collection的内容合并.

直接使用hashbrown

创建ulib/axstd/src/collections/mod.rs:

1
2
3
4
// 合并alloc::collections里的内容
pub use alloc::collections::*;
// 引用hashbrown
pub use hashbrown::HashMap;

ulib/axstd/Cargo.toml加入hashbrown:

1
2
3
4
5
... ...
[dependencies]
... ...
hashbrown = "0.15.1"
... ...

完全仿写std

自己实现以hashbrown::HashMapbaseHashMap.

通过题中给出的random实现RandomState.

想在axstd中调用axhal/random,需要在ulib/axstd/Cargo.toml里加入:

1
2
[dependencies]
axhal = { workspace = true }

#TODO

拉链法实现

#TODO