0%

阶段一-Rustlings

这个阶段主要是需要进行rust相关的学习.

其实早就有接触过rust,但是语法,尤其是所有权和生命周期这块,一直还比较生疏.

有关于所有权相关的内容主要是看了这个视频:如何一步一步推导出Rust所有权、借用规则、引用类型以及秒懂生命周期标注_哔哩哔哩_bilibili

其中关于rust作为无gc语言,大部分内存都是保存在栈上的,只有少部分是保存在栈里边.

‘a指出返回的引用与输入参数x、y之间有关联.

这个非常非常重要.

阶段二-OS内核实验

ch0-2

可以参见我之前复现rCore-Tutorial-Book-v3 3.6.0-alpha.1 文档的笔记,被公开在博客园上.

[winddevil的笔记 - 博客园

这次学习主要是从ch3~ch8的学习,重点换做了解决通过测例,和一些自己的问题.

ch3

主要是讲的一个任务切换的流程,有了任务切换之后又通过定时器中断实现了抢占式调度.

替代文本|800

ch4

这一章主要解决的是一个虚拟地址转换为物理地址的过程,说是虚拟地址,我原来以为真的改变了地址,实际上每一次调用资源都还是使用了物理地址的,利用地址空间对所有的需要访问具体物理地址的对象进行操作.

替代文本|800

ch5

这一章同样是承接了上一章的知识,讲的主要是一个进程的概念,加入了很多新的结构体,后边我应该会有时间的时候更新一下图片.

进程中最巧妙的就是使用了fork这个复制一个任务的操作,有了进程,那么就可以实现编程的简洁性,倭只需要编写一个小任务,然后再进行组合,而不是调用fn,然后自己设计各种分支结构.

有了进程,相当于把调度的工作委托给了os.

ch6

在上一章的基础上,引入了块和文件系统.

这一部分的知识学的非常的不牢靠.

但是让我印象深刻的地方是,这一章基本实现了我对之前学习的时候发现windows是可以直接”点开”应用这个操作的好奇.

那么应用保存在哪里,为什么我用U盘拷贝了还是可以继续运行.

之前学习单片机的时候很少想到我可以通过什么东西对”可执行”的东西进行操作.

通过二进制文件进行加载然后运行的操作属实惊艳到了我.

ch7

这个和ch4更加相关.之前运行rtos的时候总是想着,那么这个变量可以直接以全局变量的方式进行传输为什么我要使用各种比如信号量比如邮箱的方式,现在就一目了然了.

因为地址空间的不同所以进程之间的通信需要通过管道,也就是需要经由操作系统这一层.

ch8

这一部分让我想起了之前进行单片机编程的时候的临界段保护操作,那时候是通过非常暴力的方法关掉了所有的中断以保证这次读取不会出现问题.

或者使用原子操作保证中断无法打断单一时钟下的操作.

这里并没有和硬件和中断打交道,而是选用了三种方式,加锁\条件变量\信号量的方式.

使用银行家算法进行了调度,算法不难,但是调用本身很麻烦,需要在每一次加锁的时候对题中的变量进行操作.并且每一次上锁的时候都需要detect,那么对上锁的程序也必须进行改造.

本次体验

越到后边越忙,如果有幸进入下一个阶段一定不能好高骛远多线程操作,一定要留足时间给自己.

详细编写过程

附在ArceOS igb网卡驱动编写上

体会

前边很多时间用在流控、过滤器上边。由于igb-drive和ixgbe-drive的抽象化不一样。尤其是对ring和dma内存的结构的抽象。

比如把ring初始化的时候的操作,需要适配Tx和Rx。看文档的时间花了很久,具体怎么抽象化反而做的很差。

最后igb-drive卡在Descriptor构成之后想要写入Tail发送,但是想不出怎么进行发送上边。

很难解决具体的问题。

后来看到群友有人魔改ixgbe-dirve,得到成功。进而参考学习,自己也可以通过修改本地的ixgbe驱动来实现成功的httpserver。

并且看到有的人参考的驱动是旧版本的linux驱动,而我参考的是新版本的驱动,这就体现了我策略性的问题。

后续

不管这次结果如何,仍然想要参加下一期的学习,并且尝试自己完成igb驱动的编写,并且放到自己的blog中去。

qemu 模拟 igb设备

Make方式

在linux系统下可使用make方式运行,需修改arceos/scripts/make目录下qemu.mk文件,更改 Qemu 模拟网卡为 igb。

qemu_args-$(NET)的参数,由-device virtio-net-$(vdev-suffix),netdev=net0修改为-device igb,netdev=net0

在arceos目录下运行make命令:

1
make A=examples/httpclient PLATFORM=aarch64-qemu-virt LOG=debug SMP=2 NET=y NET_DEV=user run

最终生成的运行命令:

1
qemu-system-aarch64 -m 128M -smp 2 -cpu cortex-a72 -machine virt -kernel examples/httpclient/httpclient_aarch64-qemu-virt.bin -device virtio-net-pci,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:5555,hostfwd=udp::5555-:5555 -nographic

这里其实我们可以自己进行一个理解,先从qemu的命令开始:

  1. -m 设置分配的内存
  2. -smp 设置对称多处理(SMP)配置,这里指定的是创建一个具有两个 CPU 核心的虚拟机
  3. -cpu 指定要模拟的 CPU 类型,在这里是 Cortex-A72
  4. -machine 选择平台,这里使用 virt 类型的机器模型,这是一个通用的,不与任何特定硬件绑定的虚拟平台
  5. -kernel 指定内核映像文件,这个文件是在虚拟机启动时加载的程序或操作系统内核
  6. -device 添加一个 PCI 设备到虚拟机中,这个设备是一个VirtIO 网络适配器,并且它连接到了 IDnet0 的网络后端
  7. -netdev 定义了一个用户模式网络后端,其 IDnet0。同时设置了主机端口转发规则,将主机的 TCPUDP5555 端口转发到虚拟机的相同端口
  8. -nographic 禁用图形输出

那么我们可以得到如下的问题.

如何编译得来.bin文件?

看log的时候很不仔细,其实在log里是有的:

1
cargo -C examples/httpclient build -Z unstable-options --target aarch64-unknown-none-softfloat --target-dir /home/winddevil/workspace/arceos/target --release  --features "axstd/log-level-debug axstd/smp"

然后我们直接运行这个编译过程是发现会报错的,这是什么原因呢?输出log是:

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
warning: `/home/winddevil/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
warning: `/home/winddevil/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
warning: `/home/winddevil/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
warning: `/home/winddevil/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
Compiling log v0.4.21
Compiling cfg-if v1.0.0
Compiling tock-registers v0.8.1
Compiling bitflags v2.6.0
Compiling axerrno v0.1.0
Compiling byteorder v1.4.3
Compiling const-default v1.0.0
Compiling memory_addr v0.3.1
Compiling bit_field v0.10.2
Compiling percpu v0.1.3
Compiling lock_api v0.4.10
Compiling lazyinit v0.2.1
Compiling axconfig v0.1.0 (/home/winddevil/workspace/arceos/modules/axconfig)
Compiling int_ratio v0.1.0
Compiling static_assertions v1.1.0
Compiling linkme v0.3.27
Compiling scopeguard v1.2.0
Compiling handler_table v0.1.1
Compiling kernel_guard v0.1.1
Compiling axdriver_base v0.1.0 (https://github.com/arceos-org/axdriver_crates.git?tag=v0.1.0#78686a7e)
Compiling aarch64-cpu v9.4.0
Compiling rlsf v0.2.1
Compiling dw_apb_uart v0.1.0
Compiling arm_gicv2 v0.1.0
Compiling arm_pl011 v0.1.0
Compiling bitmap-allocator v0.1.0
Compiling heapless v0.7.16
Compiling kspin v0.1.0
Compiling zerocopy v0.7.35
Compiling hash32 v0.2.1
Compiling stable_deref_trait v1.2.0
Compiling smoltcp v0.10.0 (https://github.com/rcore-os/smoltcp.git?rev=2ade274#2ade2747)
Compiling axdriver v0.1.0 (/home/winddevil/workspace/arceos/modules/axdriver)
Compiling num-traits v0.2.16
Compiling managed v0.8.0
Compiling axlog v0.1.0 (/home/winddevil/workspace/arceos/modules/axlog)
Compiling bitflags v1.3.2
Compiling axio v0.1.0
Compiling spin v0.9.8
Compiling allocator v0.1.0 (https://github.com/arceos-org/allocator.git?tag=v0.1.0#16496d88)
Compiling axdriver_net v0.1.0 (https://github.com/arceos-org/axdriver_crates.git?tag=v0.1.0#78686a7e)
Compiling axalloc v0.1.0 (/home/winddevil/workspace/arceos/modules/axalloc)
Compiling virtio-drivers v0.7.4
Compiling axhal v0.1.0 (/home/winddevil/workspace/arceos/modules/axhal)
Compiling chrono v0.4.38
Compiling page_table_entry v0.4.0
Compiling axdriver_virtio v0.1.0 (https://github.com/arceos-org/axdriver_crates.git?tag=v0.1.0#78686a7e)
Compiling axdriver_pci v0.1.0 (https://github.com/arceos-org/axdriver_crates.git?tag=v0.1.0#78686a7e)
Compiling page_table_multiarch v0.4.0
error[E0425]: cannot find function `init_boot_page_table` in module `crate::platform::mem`
--> modules/axhal/src/platform/aarch64_common/boot.rs:100:27
|
100 | crate::platform::mem::init_boot_page_table(addr_of_mut!(BOOT_PT_L0), addr_of_mut!(BOOT_PT_L1));
| ^^^^^^^^^^^^^^^^^^^^ not found in `crate::platform::mem`

error[E0425]: cannot find value `rust_entry` in module `crate::platform`
--> modules/axhal/src/platform/aarch64_common/boot.rs:139:38
|
139 | entry = sym crate::platform::rust_entry,
| ^^^^^^^^^^ not found in `crate::platform`

error[E0425]: cannot find value `rust_entry_secondary` in module `crate::platform`
--> modules/axhal/src/platform/aarch64_common/boot.rs:170:38
|
170 | entry = sym crate::platform::rust_entry_secondary,
| ^^^^^^^^^^^^^^^^^^^^ not found in `crate::platform`

error[E0425]: cannot find value `PSCI_METHOD` in crate `axconfig`
--> modules/axhal/src/platform/aarch64_common/psci.rs:82:31
|
82 | let ret = match axconfig::PSCI_METHOD {
| ^^^^^^^^^^^ not found in `axconfig`

error[E0425]: cannot find value `PSCI_METHOD` in crate `axconfig`
--> modules/axhal/src/platform/aarch64_common/psci.rs:85:58
|
85 | _ => panic!("Unknown PSCI method: {}", axconfig::PSCI_METHOD),
| ^^^^^^^^^^^ not found in `axconfig`

error[E0425]: cannot find value `UART_PADDR` in crate `axconfig`
--> modules/axhal/src/platform/aarch64_common/pl011.rs:9:43
|
9 | const UART_BASE: PhysAddr = pa!(axconfig::UART_PADDR);
| ^^^^^^^^^^ not found in `axconfig`

For more information about this error, try `rustc --explain E0425`.
error: could not compile `axhal` (lib) due to 6 previous errors
warning: build failed, waiting for other jobs to finish...

这里有个非常重要的细节,我们报告的错都是modules/axhal/src/platform/aarch64_common这个文件夹里的.

那么我们发现这个paltform不对啊,它怎么不给我编译aarch64-qemu-virt呢?

这里要提到一个我们经常会使用到的东西,即查看make过程的V=1,在make指令后边加上这句,就可以看到make的过程

然后我们看到:

1
2
3
4
5
6
7
APP: "examples/httpclient"
APP_TYPE: "rust"
FEATURES: ""
arceos features: "axstd/log-level-debug"
lib features: "axstd/smp"
app features: ""
RUSTFLAGS: "-C link-arg=-T/home/winddevil/workspace/arceos/target/aarch64-unknown-none-softfloat/release/linker_aarch64-qemu-virt.lds -C link-arg=-no-pie -C link-arg=-znostart-stop-gc"

这里是来自scripts/make/build.mk:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ifneq ($(filter $(MAKECMDGOALS),doc doc_check_missing),)  # run `cargo doc`
$(if $(V), $(info RUSTDOCFLAGS: "$(RUSTDOCFLAGS)"))
export RUSTDOCFLAGS
else ifeq ($(filter $(MAKECMDGOALS),clippy unittest unittest_no_fail_fast),) # not run `cargo test` or `cargo clippy`
ifneq ($(V),)
$(info APP: "$(APP)")
$(info APP_TYPE: "$(APP_TYPE)")
$(info FEATURES: "$(FEATURES)")
$(info arceos features: "$(AX_FEAT)")
$(info lib features: "$(LIB_FEAT)")
$(info app features: "$(APP_FEAT)")
endif
ifeq ($(APP_TYPE), c)
$(if $(V), $(info CFLAGS: "$(CFLAGS)") $(info LDFLAGS: "$(LDFLAGS)"))
else
$(if $(V), $(info RUSTFLAGS: "$(RUSTFLAGS)"))
export RUSTFLAGS
endif
endif

这里后来发现两个问题,一个是虽然输出了几个关键的环境变量还是没有输出全部的环境变量还是会导致不能编译

这里介绍一个关键字export:

1
2
3
4
5
6
7
export [-fnp][变量名称]=[变量设置值]

参数说明:

-f  代表[变量名称]中为函数名称。
-n  删除指定的变量。变量实际上并未删除,只是不会输出到后续指令的执行环境中。
-p  列出所有的shell赋予程序的环境变量。

上述是通过export的环境变量.

那么其中有一个比较可疑的,就是RUSTFLAGS,在这里:

自定义参数的空格分隔列表,用来传递给 Cargo 执行的所有编译器调用。与cargo rustc不同,这对于传递一个标志 全部的 编译实例是有用的。

经过多次尝试,设置这两个环境变量即可编译:

1
2
3
export RUSTFLAGS="-C link-arg=-T/home/winddevil/workspace/arceos/target/aarch64-unknown-none-softfloat/release/linker_aarch64-qemu-virt.lds -C link-arg=-no-pie -C link-arg=-znostart-stop-gc"
export AX_PLATFORM="aarch64-qemu-virt"
cargo -C examples/httpclient build -Z unstable-options --target aarch64-unknown-none-softfloat --target-dir /home/winddevil/workspace/arceos/target --release --features "axstd/log-level-debug axstd/smp"

这说明了编译流程里是有设置很多环境变量的.

编译过程很好理解:

但是仍然没有办法解决在cargo build的时候到底有哪些环境变量被export了.

我们搜索export,其实也不多:

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
// Makefile

export AX_ARCH=$(ARCH)
export AX_PLATFORM=$(PLATFORM_NAME)
export AX_SMP=$(SMP)
export AX_MODE=$(MODE)
export AX_LOG=$(LOG)
export AX_TARGET=$(TARGET)
export AX_IP=$(IP)
export AX_GW=$(GW)

// scripts/make/build.mk

ifneq ($(filter $(MAKECMDGOALS),doc doc_check_missing),) # run `cargo doc`
$(if $(V), $(info RUSTDOCFLAGS: "$(RUSTDOCFLAGS)"))
export RUSTDOCFLAGS
else ifeq ($(filter $(MAKECMDGOALS),clippy unittest unittest_no_fail_fast),) # not run `cargo test` or `cargo clippy`
ifneq ($(V),)
$(info APP: "$(APP)")
$(info APP_TYPE: "$(APP_TYPE)")
$(info FEATURES: "$(FEATURES)")
$(info arceos features: "$(AX_FEAT)")
$(info lib features: "$(LIB_FEAT)")
$(info app features: "$(APP_FEAT)")
endif
ifeq ($(APP_TYPE), c)
$(if $(V), $(info CFLAGS: "$(CFLAGS)") $(info LDFLAGS: "$(LDFLAGS)"))
else
$(if $(V), $(info RUSTFLAGS: "$(RUSTFLAGS)"))
export RUSTFLAGS
endif
endif

我们需要看看各个包在编译的时候,build.rs是怎么运行的,会加入什么环境变量进去.

依赖图:

这里边最重要的就是config.rs,它是由axconfig进行编译的时候build.rs生成的.

build.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
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
use std::io::{Result, Write};
use std::path::{Path, PathBuf};
use toml_edit::{Decor, DocumentMut, Item, Table, Value};

fn resolve_config_path(platform: Option<&str>) -> Result<PathBuf> {
let mut root_dir = PathBuf::from(std::env!("CARGO_MANIFEST_DIR"));
root_dir.extend(["..", ".."]);
let config_dir = root_dir.join("platforms");

let builtin_platforms = std::fs::read_dir(&config_dir)?
.filter_map(|e| {
e.unwrap()
.file_name()
.to_str()?
.strip_suffix(".toml")
.map(String::from)
})
.collect::<Vec<_>>();

let path = match platform {
None | Some("") => "defconfig.toml".into(),
Some(plat) if builtin_platforms.contains(&plat.to_string()) => {
config_dir.join(format!("{plat}.toml"))
}
Some(plat) => {
let path = PathBuf::from(&plat);
if path.is_absolute() {
path
} else {
root_dir.join(plat)
}
}
};

Ok(path)
}

fn get_comments<'a>(config: &'a Table, key: &str) -> Option<&'a str> {
config
.key(key)
.and_then(|k| k.leaf_decor().prefix())
.and_then(|s| s.as_str())
.map(|s| s.trim())
}

fn add_config(config: &mut Table, key: &str, item: Item, comments: Option<&str>) {
config.insert(key, item);
if let Some(comm) = comments {
if let Some(mut dst) = config.key_mut(key) {
*dst.leaf_decor_mut() = Decor::new(comm, "");
}
}
}

fn load_config_toml(config_path: &Path) -> Result<Table> {
let config_content = std::fs::read_to_string(config_path)?;
let toml = config_content
.parse::<DocumentMut>()
.expect("failed to parse config file")
.as_table()
.clone();
Ok(toml)
}

fn gen_config_rs(config_path: &Path) -> Result<Vec<u8>> {
fn is_num(s: &str) -> bool {
let s = s.replace('_', "");
if s.parse::<usize>().is_ok() {
true
} else if let Some(s) = s.strip_prefix("0x") {
usize::from_str_radix(s, 16).is_ok()
} else {
false
}
}

// Load TOML config file
let mut config = if config_path == Path::new("defconfig.toml") {
load_config_toml(config_path)?
} else {
// Set default values for missing items
let defconfig = load_config_toml(Path::new("defconfig.toml"))?;
let mut config = load_config_toml(config_path)?;

for (key, item) in defconfig.iter() {
if !config.contains_key(key) {
add_config(
&mut config,
key,
item.clone(),
get_comments(&defconfig, key),
);
}
}
config
};

add_config(
&mut config,
"smp",
toml_edit::value(std::env::var("AX_SMP").unwrap_or("1".into())),
Some("# Number of CPUs"),
);

// Generate config.rs
let mut output = Vec::new();
writeln!(
output,
"// Platform constants and parameters for {}.",
config["platform"].as_str().unwrap(),
)?;
writeln!(output, "// Generated by build.rs, DO NOT edit!\n")?;

for (key, item) in config.iter() {
let var_name = key.to_uppercase().replace('-', "_");
if let Item::Value(value) = item {
let comments = get_comments(&config, key)
.unwrap_or_default()
.replace('#', "///");
match value {
Value::String(s) => {
writeln!(output, "{comments}")?;
let s = s.value();
if is_num(s) {
writeln!(output, "pub const {var_name}: usize = {s};")?;
} else {
writeln!(output, "pub const {var_name}: &str = \"{s}\";")?;
}
}
Value::Array(regions) => {
if key != "mmio-regions" && key != "virtio-mmio-regions" && key != "pci-ranges"
{
continue;
}
writeln!(output, "{comments}")?;
writeln!(output, "pub const {var_name}: &[(usize, usize)] = &[")?;
for r in regions.iter() {
let r = r.as_array().unwrap();
writeln!(
output,
" ({}, {}),",
r.get(0).unwrap().as_str().unwrap(),
r.get(1).unwrap().as_str().unwrap()
)?;
}
writeln!(output, "];")?;
}
_ => {}
}
}
}

Ok(output)
}

fn main() -> Result<()> {
let platform = option_env!("AX_PLATFORM");
let config_path = resolve_config_path(platform)?;

println!("Reading config file: {:?}", config_path);
let config_rs = gen_config_rs(&config_path)?;

let out_dir = std::env::var("OUT_DIR").unwrap();
let out_path = Path::new(&out_dir).join("config.rs");
println!("Generating config file: {}", out_path.display());
std::fs::write(out_path, config_rs)?;

println!("cargo:rerun-if-changed={}", config_path.display());
println!("cargo:rerun-if-env-changed=AX_PLATFORM");
println!("cargo:rerun-if-env-changed=AX_SMP");
Ok(())
}

我们通过在编译过程中加入-vv查看更详细的编译过程,即:

1
2
3
export RUSTFLAGS="-C link-arg=-T/home/winddevil/workspace/arceos/target/aarch64-unknown-none-softfloat/release/linker_aarch64-qemu-virt.lds -C link-arg=-no-pie -C link-arg=-znostart-stop-gc"
export AX_PLATFORM="aarch64-qemu-virt"
cargo -C examples/httpclient build -Z unstable-options --target aarch64-unknown-none-softfloat --target-dir /home/winddevil/workspace/arceos/target --release --features "axstd/log-level-debug axstd/smp" -vv

可以看到一个好的信息,

1
[axconfig 0.1.0] Generating config file: /home/winddevil/workspace/arceos/target/release/build/axconfig-f271638000f4f11a/out/config.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
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
102
103
104
105
106
107
108
109
// Platform constants and parameters for aarch64-qemu-virt.
// Generated by build.rs, DO NOT edit!

/// Architecture identifier.
pub const ARCH: &str = "aarch64";
/// Platform identifier.
pub const PLATFORM: &str = "aarch64-qemu-virt";
/// Platform family.
pub const FAMILY: &str = "aarch64-qemu-virt";
/// Base address of the whole physical memory.
pub const PHYS_MEMORY_BASE: usize = 0x4000_0000;
/// Size of the whole physical memory.
pub const PHYS_MEMORY_SIZE: usize = 0x800_0000;
/// Base physical address of the kernel image.
pub const KERNEL_BASE_PADDR: usize = 0x4008_0000;
/// Base virtual address of the kernel image.
pub const KERNEL_BASE_VADDR: usize = 0xffff_0000_4008_0000;
/// Linear mapping offset, for quick conversions between physical and virtual
/// addresses.
pub const PHYS_VIRT_OFFSET: usize = 0xffff_0000_0000_0000;
/// Offset of bus address and phys address. some boards, the bus address is
/// different from the physical address.
pub const PHYS_BUS_OFFSET: usize = 0;
/// Kernel address space base.
pub const KERNEL_ASPACE_BASE: usize = 0xffff_0000_0000_0000;
/// Kernel address space size.
pub const KERNEL_ASPACE_SIZE: usize = 0x0000_ffff_ffff_f000;
/// MMIO regions with format (`base_paddr`, `size`).
pub const MMIO_REGIONS: &[(usize, usize)] = &[
(0x0900_0000, 0x1000),
(0x0910_0000, 0x1000),
(0x0800_0000, 0x2_0000),
(0x0a00_0000, 0x4000),
(0x1000_0000, 0x2eff_0000),
(0x40_1000_0000, 0x1000_0000),
];
/// VirtIO MMIO regions with format (`base_paddr`, `size`).
pub const VIRTIO_MMIO_REGIONS: &[(usize, usize)] = &[
(0x0a00_0000, 0x200),
(0x0a00_0200, 0x200),
(0x0a00_0400, 0x200),
(0x0a00_0600, 0x200),
(0x0a00_0800, 0x200),
(0x0a00_0a00, 0x200),
(0x0a00_0c00, 0x200),
(0x0a00_0e00, 0x200),
(0x0a00_1000, 0x200),
(0x0a00_1200, 0x200),
(0x0a00_1400, 0x200),
(0x0a00_1600, 0x200),
(0x0a00_1800, 0x200),
(0x0a00_1a00, 0x200),
(0x0a00_1c00, 0x200),
(0x0a00_1e00, 0x200),
(0x0a00_3000, 0x200),
(0x0a00_2200, 0x200),
(0x0a00_2400, 0x200),
(0x0a00_2600, 0x200),
(0x0a00_2800, 0x200),
(0x0a00_2a00, 0x200),
(0x0a00_2c00, 0x200),
(0x0a00_2e00, 0x200),
(0x0a00_3000, 0x200),
(0x0a00_3200, 0x200),
(0x0a00_3400, 0x200),
(0x0a00_3600, 0x200),
(0x0a00_3800, 0x200),
(0x0a00_3a00, 0x200),
(0x0a00_3c00, 0x200),
(0x0a00_3e00, 0x200),
];
/// Base physical address of the PCIe ECAM space.
pub const PCI_ECAM_BASE: usize = 0x40_1000_0000;
/// End PCI bus number (`bus-range` property in device tree).
pub const PCI_BUS_END: usize = 0xff;
/// PCI device memory ranges (`ranges` property in device tree).
pub const PCI_RANGES: &[(usize, usize)] = &[
(0x3ef_f0000, 0x1_0000),
(0x1000_0000, 0x2eff_0000),
(0x80_0000_0000, 0x80_0000_0000),
];
/// UART Address
pub const UART_PADDR: usize = 0x0900_0000;

pub const UART_IRQ: usize = 1;
/// GICC Address
pub const GICC_PADDR: usize = 0x0801_0000;

pub const GICD_PADDR: usize = 0x0800_0000;
/// PSCI
pub const PSCI_METHOD: &str = "hvc";
/// pl031@9010000 {
/// clock-names = "apb_pclk";
/// clocks = <0x8000>;
/// interrupts = <0x00 0x02 0x04>;
/// reg = <0x00 0x9010000 0x00 0x1000>;
/// compatible = "arm,pl031\0arm,primecell";
/// };
/// RTC (PL031) Address
pub const RTC_PADDR: usize = 0x901_0000;
/// Timer interrupt frequency in Hz.
pub const TIMER_FREQUENCY: usize = 0;
/// Stack size of each task.
pub const TASK_STACK_SIZE: usize = 0x40000;
/// Number of timer ticks per second (Hz). A timer tick may contain several timer
/// interrupts.
pub const TICKS_PER_SEC: usize = 100;
/// Number of CPUs
pub const SMP: usize = 1;

由于modules/axconfig/src/lib.rs里有:

1
2
3
4
#[rustfmt::skip]
mod config {
include!(concat!(env!("OUT_DIR"), "/config.rs"));
}

那么我们可以看到一些使用了axconfig::xx的引用的来源.

透传到hostnet0网络后端和5555端口是怎么被访问到的?

解析modules/axdriver/src/ixgbe.rs

log

这里是log:

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
qemu-system-aarch64 -m 128M -smp 2 -cpu cortex-a72 -machine virt -kernel examples/httpclient/httpclient_aarch64-qemu-virt.bin -device virtio-net-pci,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:5555,hostfwd=udp::5555-:5555 -nographic

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 = aarch64
platform = aarch64-qemu-virt
target = aarch64-unknown-none-softfloat
smp = 2
build_mode = release
log_level = debug

[ 0.005105 axruntime:130] Logging is enabled.
[ 0.006216 axruntime:131] Primary CPU 0 started, dtb = 0x44000000.
[ 0.007098 axruntime:133] Found physcial memory regions:
[ 0.007682 axruntime:135] [PA:0x40080000, PA:0x400a6000) .text (READ | EXECUTE | RESERVED)
[ 0.008824 axruntime:135] [PA:0x400a6000, PA:0x400af000) .rodata (READ | RESERVED)
[ 0.009895 axruntime:135] [PA:0x400af000, PA:0x400b3000) .data .tdata .tbss .percpu (READ | WRITE | RESERVED)
[ 0.011083 axruntime:135] [PA:0x400b3000, PA:0x40133000) boot stack (READ | WRITE | RESERVED)
[ 0.011895 axruntime:135] [PA:0x40133000, PA:0x40159000) .bss (READ | WRITE | RESERVED)
[ 0.012720 axruntime:135] [PA:0x40159000, PA:0x48000000) free memory (READ | WRITE | FREE)
[ 0.013704 axruntime:135] [PA:0x9000000, PA:0x9001000) mmio (READ | WRITE | DEVICE | RESERVED)
[ 0.014160 axruntime:135] [PA:0x9100000, PA:0x9101000) mmio (READ | WRITE | DEVICE | RESERVED)
[ 0.015061 axruntime:135] [PA:0x8000000, PA:0x8020000) mmio (READ | WRITE | DEVICE | RESERVED)
[ 0.016082 axruntime:135] [PA:0xa000000, PA:0xa004000) mmio (READ | WRITE | DEVICE | RESERVED)
[ 0.017048 axruntime:135] [PA:0x10000000, PA:0x3eff0000) mmio (READ | WRITE | DEVICE | RESERVED)
[ 0.017742 axruntime:135] [PA:0x4010000000, PA:0x4020000000) mmio (READ | WRITE | DEVICE | RESERVED)
[ 0.018484 axruntime:208] Initialize global memory allocator...
[ 0.019127 axruntime:209] use TLSF allocator.
[ 0.019775 axalloc:212] initialize global allocator at: [0xffff000040159000, 0xffff000048000000)
[ 0.020798 axmm:60] Initialize virtual memory management...
[ 0.027013 axmm:63] kernel address space init OK: AddrSpace {
va_range: VA:0xffff000000000000..VA:0xfffffffffffff000,
page_table_root: PA:0x40161000,
}
[ 0.028517 axruntime:150] Initialize platform devices...
[ 0.028923 axdriver:152] Initialize device drivers...
[ 0.029312 axdriver:153] device model: static
[ 0.029799 axdriver::bus::pci:97] PCI 00:00.0: 1b36:0008 (class 06.00, rev 00) Standard
[ 0.031156 axdriver::bus::pci:97] PCI 00:01.0: 1af4:1000 (class 02.00, rev 00) Standard
[ 0.031683 axdriver::bus::pci:54] BAR 1: MEM [0x10000000, 0x10001000)
[ 0.032182 axdriver::bus::pci:54] BAR 4: MEM [0x10004000, 0x10008000) 64bit pref
[ 0.034618 virtio_drivers::transport:78] Device features: Features(CTRL_GUEST_OFFLOADS | MAC | MRG_RXBUF | STATUS | CTRL_VQ | CTRL_RX | CTRL_VLAN | CTRL_RX_EXTRA | GUEST_ANNOUNCE | CTL_MAC_ADDR | RING_INDIRECT_DESC | RING_EVENT_IDX | VERSION_1)
[ 0.036068 virtio_drivers::device::net::dev_raw:30] negotiated_features Features(MAC | STATUS | RING_INDIRECT_DESC | RING_EVENT_IDX)
[ 0.036781 virtio_drivers::device::net::dev_raw:37] Got MAC=[52, 54, 00, 12, 34, 56], status=Status(LINK_UP)
[ 0.038039 axalloc:118] expand heap memory: [0xffff0000403a5000, 0xffff0000403e5000)
[ 0.039351 axdriver::bus::pci:104] registered a new Net device at 00:01.0: "virtio-net"
[ 0.050505 axdriver:160] number of NICs: 1
[ 0.050868 axdriver:163] NIC 0: "virtio-net"
[ 0.051241 axnet:43] Initialize network subsystem...
[ 0.051457 axnet:46] use NIC 0: "virtio-net"
[ 0.051956 axalloc:118] expand heap memory: [0xffff0000403e5000, 0xffff0000404e5000)
[ 0.052391 axalloc:118] expand heap memory: [0xffff0000404e5000, 0xffff0000406e5000)
[ 0.053174 axnet::smoltcp_impl:332] created net interface "eth0":
[ 0.053471 axnet::smoltcp_impl:333] ether: 52-54-00-12-34-56
[ 0.054021 axnet::smoltcp_impl:334] ip: 10.0.2.15/24
[ 0.054368 axnet::smoltcp_impl:335] gateway: 10.0.2.2
[ 0.054664 axruntime::mp:19] starting CPU 1...
[ 0.054937 axhal::platform::aarch64_common::psci:113] Starting CPU 1 ON ...
[ 0.055480 axruntime:186] Primary CPU 0 init OK.
[ 0.056642 axruntime::mp:36] Secondary CPU 1 started.
[ 0.056872 axruntime::mp:46] Secondary CPU 1 init OK.
Hello, simple http client!
dest: 49.12.234.183:80 (49.12.234.183:80)
[ 0.058237 0 axnet::smoltcp_impl:100] socket #0: created
[ 0.059213 0 smoltcp::iface::interface:1473] address 10.0.2.2 not in neighbor cache, sending ARP request
[ 0.386031 0 axnet::smoltcp_impl::tcp:430] TCP socket #0: connected to 49.12.234.183:80
HTTP/1.1 200 OK
Server: nginx
Date: Sat, 07 Dec 2024 07:03:37 GMT
Content-Type: text/plain
Content-Length: 14
Connection: keep-alive
Access-Control-Allow-Origin: *
Cache-Control: no-cache, no-store, must-revalidate

211.83.106.222
[ 0.675625 0 axnet::smoltcp_impl::tcp:247] TCP socket #0: shutting down
[ 0.676139 0 axnet::smoltcp_impl:128] socket #0: destroyed
[ 0.676366 0 axruntime:199] main task exited: exit_code=0
[ 0.676586 0 axhal::platform::aarch64_common::psci:96] Shutting down...

技术交流会议内容

技术文档

igb-driver/doc/tack.md at main · qclic/igb-driver

会议内容

主要看一下设备树和BAR空间的概念。

4.5节讲了怎么进行初始化。

怎么完成这个初始化呢?操作寄存器

说明reset成功

初始化流程

关中断

在发出全局重置后,也需要禁用中断

IMC 寄存器


全局Reset并且进行常规配置

CTRL寄存器

RCTL 寄存器

TCTL寄存器

RXPBS寄存器

TXPBS寄存器

设置PHY和链接

CTRL_EXT

Copper PHY Link设置

以太网——PHY、MAC和 MII基础知识 - 知乎

自动协商

链路的解析流控制行为要放到CTRL.TFCECTRL.RFCE里.

在自动协商结束后,MAC从PHY中识别”链接指示”之前需要设置CTRL.SLU.

MAC速度解决

  1. 根据PHY指示的速度来进行解决
    1. 直接读取PHY寄存器,详见下面一节MDIO的读取
    2. 通过STATUS.SPEED 寄存器来读取PHY的SPD_IND寄存器
  2. 软件要求MAC尝试从PHY到MAC RX_CLK自动检测PHY速度,然后相应地编程MAC速度
  3. MAC通过使用PHY的内部PHY-到MAC速度指示(SPD_IND),基于PHY指示自动检测和设置MAC的链路速度
强制设置MAC速度

设置CTRL.frcspdCTRL.speed

这里有一点非常重要,就是在设置一个位的时候,假如我们不知道其原始状态,我们应该先将其清零,然后再设置

使用PHY的指示值

设置CTRL.ASDECTRL.FRCSPD

MAC双工解决

设置CTRL.frcdplxCTRL.FD

使用PHY寄存器

暂且跳过,这部分是高级功能,直接通过MDIO接口操作PHY设备

MDIO接口

  1. 物理接口
  2. 特殊协议-可以跨连接运行
  3. 一组内部的可寻址寄存器

内部和外扩的接口由:

  1. 数据线MDIO
  2. 时钟线MDC

通过访问MAC的寄存器可以访问这两种接口.

MDC是MDIO的时钟线,这个时钟信号不一定一直要有,只要在有数据的时候有没数据的时候就冻结就行了.最大工作频率为2.5MHz.

MDIO是一种传送PHY和MAC之间命令的双向数据信号,因此MAC可以通过一些指令来读取和写PHY管理寄存器.

直接通过访问MDIC寄存器即可访问这个接口.

MDIC寄存器

The PHY address for MDIO accesses is 00001b.

MAC/PHY 链接设置

这个有4种方法,按照情况

自动设置

只需要设置CTRL.FRCDPLXCTRL.FRCSPD0b.

启动PHY的自动协商

接收寄存器初始化

初始化多播表


然后这个是11位也就是[11:0],

那么[11:5]决定指向哪个寄存器,刚好128个寄存器嘛.

那么[4:0]决定是指向哪个位,刚好32个位嘛.

接收功能

主要是要学一下rings.

Rx队列分配

接收到的包分为三个阶段:

  1. L2 Filters 用于确保包已经收到
  2. Pool Select 用于虚拟化环境,定义Rx包的目标虚拟端口(称之为)一个数据包可以与任意数量的端口/池相关联
  3. Queue Select 这一步Rx包已经成功通过过滤器,并且和一个或者多个接收描述符队列相连接

这是一个类开关结构.

这是一种网络资源虚拟化,把网卡的接口也就是最上边的,虚拟化成很多有粒度的资源

接收数据包的目的地

  • 虚拟化
  • RSS
  • L2以太网过滤器
  • L3/L4 5-元组过滤器
  • TCP SYN筛选器

通常,包接收包括识别线上包的存在、执行地址过滤、将包存储在接收数据FIFO中、将数据传输到主机存储器中的16个接收队列中的一个,以及更新接收描述符的状态。

队列分配.

虚拟化

在虚拟化环境中,DMA资源被多个软件共享.

通过在DMA中分配 接收描述符队列(receive descriptor queues) 来完成虚拟化.

队列分配到虚拟分区是按集合完成的,每个集合都有相同数量的队列,那么这个队列组合叫做.

虚拟化会为每个接收到的数据包分配一个或者多个池索引.

包的分配是根据池索引和一些其它的约束来分配到池中的.

RSS

RSS通过将数据包分配到不同的描述符队列中,在多个处理器核心之间分配数据包处理.

RSS为每个接收到的数据包分配一个RSS索引.

包的分配是根据RSS索引和一些其它的约束(比如上述提到的池索引)来分配到池中的.

L2以太网过滤器

这些过滤器根据其L2以太网类型识别数据包,并将它们分配给接收队列.

L3/L4 5-元组过滤器

识别出指定的L3/L4流,或者L3/L4流的组合.

每个过滤器由一个5个元组组成(协议、源和目标IP地址、源和目标TCP/ UDP端口).

TCP SYN筛选器

把有SYN标志的TCP包专门放到一个独立的队列里监视.

非虚拟化中的队列

队列设置寄存器

每一个队列会有一套配置寄存器,用于控制队列操作。

  • RDBAL 和 RDBAH — 接收描述符基址
  • RDLEN — 接收描述符长度
  • RDH — 接收描述符头
  • RDT — 接收描述符尾
  • RXDCTL — 接收描述符控制
  • RXCTL — 接收 DCA 控制

DCA在网络硬件接口和网络驱动中通常指的是“Direct Cache Access”,即直接缓存访问。DCA是一种硬件技术,旨在通过减少数据在内存和处理器之间的传输延迟来提高网络数据包处理的效率。具体来说,DCA可以将网络数据直接写入处理器的缓存,从而提高数据处理的速度和效率。

一共有16个队列.

定义描述符队列功能的CSR被复制了8个副本给虚拟功能(VF,Virtual Function)索引,以实现虚拟化.

每一套被复制的寄存器对应了一组队列,这些队列的VF index相同.

注意是CSR被复制了8份,而不是队列被复制了8份.

这个虚拟化不是按照队列来划分的,不是说一个队列虚拟出几个队列.而是每个功能都认为自己拥有整个网卡,所以假设你的网卡支持8个虚拟功能(VF),那么每个描述符队列的控制和状态寄存器会被复制8份,每份对应一个VF index(从0到7)。
(Gen by GPT-4o)

SRRCTL(Split and Replication Receive Control) 寄存器用于控制接收数据包的拆分和复制功能。这些功能在处理网络数据包时可以提高灵活性和性能。
主要功能包括:

  1. 数据包拆分(Split)
    • 可以将接收到的网络数据包拆分成多个部分,以便于在不同的内存区域中存储。这样可以提高数据包处理的效率,特别是在数据包非常大的情况下。
    • 通过拆分数据包,能够更高效地利用缓存和内存,从而减少处理延迟。
  2. 数据包复制(Replication)
    • 允许将接收到的数据包复制到多个队列中。这样可以支持多播和广播数据包的高效处理。
    • 数据包复制有助于在多个处理器核心之间分配接收的网络流量,从而提高处理吞吐量。
      SRRCTL寄存器中的各个字段可以配置这些功能的具体行为,例如启用或禁用拆分和复制、设置拆分的阈值等。

PSRTYPE(Packet Split Receive Type) 寄存器用于指定数据包拆分的类型和模式。它定义了如何将接收到的数据包拆分成不同的部分。
主要功能包括:

  1. 拆分模式选择
    • PSRTYPE寄存器允许选择不同的拆分模式,例如基于数据包头部的拆分、基于数据包长度的拆分等。
    • 不同的拆分模式适用于不同的应用场景,例如高效处理TCP/IP头部、优化内存使用等。
  2. 拆分类型配置
    • 可以配置拆分的具体类型,例如将数据包拆分成固定大小的块、将头部和数据部分分开存储等。
    • 通过配置拆分类型,可以更好地优化数据包的处理和存储,提高整体性能。
      PSRTYPE寄存器中的字段用于详细配置这些拆分模式和类型,确保数据包能够按照预期方式进行拆分和处理。

有了上述功能,那么每个虚拟化功能(VF)就可以按照自己的喜好来处理每个数据包,就好像每个数据包都专门发给他一样,实际上是拆分复制处理过的数据包.

通过L2 Filter 来确保包已经收到

L2 Filter被翻译为二级数据包过滤.

MAC地址过滤
单播过滤器
多播过滤器
VLAN 过滤

可管理性过滤

五元组过滤器

#todo

RSS接收侧缩放

RSS是一种将接收到的数据包分发到多个描述符队列中的机制。然后,软件将每个队列分配给不同的处理器,在多个处理器之间共享数据包处理的负载。

VLAN过滤器

#todo

接收数据存储

Host Buffer

其大小可以通过RCTL.BSIZE设置packatbuffer的大小,会影响所有的接收队列.

也可也通过每个队列SRRCTL[n].BSIZEPACKET来设置.在设置为0的时候是采用RCTL.BSIZE 的默认设置.

在使用advanced descriptor的时候需要通过设置SRRCTL.BSIZEHEADER来决定headerbuffer的大小.

片上Rx Buffer

82576包含一个64 KBytes的包缓冲区,可用于存储包,直到它们被转发到主机。
此外,为了支持第7.10.3节所述的本地包的转发,提供了20kbyt的交换机缓冲区。此缓冲区作为所有本地流量的接收缓冲区。

片上descriptor Buffer

82576为每个接收队列包含一个32个描述符缓存,用于减少包处理的延迟,并通过突发中获取和写回描述符来优化PCIe带宽的使用。

传统接收描述符格式

SRRCTL[n],DESCTYPE = 000b

这里ixgbe暂时跳过了,我们也暂时跳过,可以根据Advanced Descriptor来进行后续的补充工作

接收描述符Ring结构

移植到ixgbe代码

寻找igb的id

我们用的是82576,在ethernet-linux-igb,也就是linuxigb驱动项目里的src/e1000_hw.h文件夹里可以查出.

E1000_DEV_ID_82576 0x10C9

解决宏定义问题

ixgbe-driversrc/constants.rs文件里存在大量的宏,是用于规定一些需要的.

我们把ethernet-linux-igb中的src/e1000_hw.hE1000_DEV_ID_82576等宏改为rust形式,并且重新命名.

例如把E1000_DEV_ID_82576的名字改为IGB_DEV_ID_82576.

读取MAC

这里有一点语焉不详的地方,就是RAL0RAH0是从EEPROM加载出来的数据,那么这里有我们需要的数据.

TIPS

git的换行符问题

有时候是从windows clone下来的,所以在wsl里是linux方式读取,因此刚刚clone下来就是”被更改的状态”.

1
git config --global core.autocrlf true

下一步

概念

关键是在于这个虚拟化层,我们用虚拟化层来实现对硬件抽象层的虚拟化.

虚拟化的效率高是因为Hypervisor的体系结构环境是相同的.

这里的II型是KVM这种.1.5型可能更多的是

物理设备只有一个CPU的时候用多任务假装是vcpu.

有点像虚拟内存<->物理内存.用的也是页表的方式来建立对应关系.

vcpu的方式是一样的实现vDev.其实就是怎么把一个设备的划分成很多小的粒度,然后再分配资源给它.

最简Hypervisor执行流程:

  1. 加载Guest OS内核Image到新建地址空间。
  2. 准备虚拟机环境,设置特殊上下文。
  3. 结合特殊上下文和指令sret切换到V模式,即VM-ENTRY。
  4. OS内核只有一条指令,调用sbi-call的关机操作。
  5. 在虚拟机中,sbi-call超出V模式权限,导致VM-EXIT退出虚拟机,切换回Hypervisor。
  6. Hypervisor响应VM-EXIT的函数检查退出原因和参数,进行处理,由于是请求关机,清理虚拟机后,退出。

这里这个V模式一定要注意,之前我们只涉及了US模式.

S变成了HS.
HS是关键,作为联通真实世界和虚拟世界的通道.体系结构设计了双向变迁机制.
后边实现的很多东西都来自于HS这个特权级的寄存器.

H扩展后,S模式发送明显变化:原有s[xxx]寄存器组作用不变,新增hs[xxx]和vs[xxx]
hs[xxx]寄存器组的作用:面向Guest进行路径控制,例如异常/中断委托等
vs[xxx]寄存器组的作用:直接操纵Guest域中的VS,为其准备或设置状态

意思好像是要用HS里的H去假冒VS?

根据spv的不同决定sret会进入虚拟化还是进入user.

这里还多一个spvp,指示HS对V模式下地址空间是否有操作权限,1表示有权限操作,0无权限.

总结下来感觉虚拟机更像是一个进程,但是它又比进程多很多东西,需要运行自己的一套寄存器.

实验

运行之后是触发了一个IllegalInstructiontrap.

所以确实是会报错的.

这里要注意是QEMU的版本问题,如果是默认的6.x会导致无法正常进入vmexit_handler.
我这里安装的QEMU9.1.0版本,可以参见我自己的博客[rCore学习笔记 03]配置rCore开发环境 - winddevil - 博客园,下载的时候把版本改成9.1.0即可.

这里知道li指令访问了a7寄存器违反了不能访问csr的规定.

由于li的长度为4,我们做出如下修改tf.sepc += 4;:

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
// modules/axhal/src/arch/riscv/trap.rs
#[no_mangle]
fn riscv_trap_handler(tf: &mut TrapFrame, from_user: bool) {
let scause: scause::Scause = scause::read();
match scause.cause() {
#[cfg(feature = "uspace")]
Trap::Exception(E::UserEnvCall) => {
tf.regs.a0 = crate::trap::handle_syscall(tf, tf.regs.a7) as usize;
tf.sepc += 4;
}
Trap::Exception(E::LoadPageFault) => handle_page_fault(tf, MappingFlags::READ, from_user),
Trap::Exception(E::StorePageFault) => handle_page_fault(tf, MappingFlags::WRITE, from_user),
Trap::Exception(E::InstructionPageFault) => {
handle_page_fault(tf, MappingFlags::EXECUTE, from_user)
}
Trap::Exception(E::Breakpoint) => handle_breakpoint(&mut tf.sepc),
Trap::Interrupt(_) => {
handle_trap!(IRQ, scause.bits());
}
Trap::Exception(E::IllegalInstruction) => {
tf.sepc += 4;
info!("Illegal Instruction");
}
_ => {
panic!(
"Unhandled trap {:?} @ {:#x}:\n{:#x?}",
scause.cause(),
tf.sepc,
tf
);
}
}
}

课后作业

注意触发缺页异常之后要调整sepc的值,防止再回去再触发缺页异常然后无限循环了.

因为未知原因会卡住,这题的解题思路应该和h_1_0是一样的.

解决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

抢占式调度

两个实验第二个比第一个多一些关于wait_queue的内容.

CFS公平调度策略.

抢占式调度的基本保障是定时器.

ArceOS的抢占不是无条件的:

  1. 内部条件:时间片耗尽
  2. 外部条件
    1. 通过关抢占(锁)确定一段执行的过程中不会出现抢占,形成关抢占的临界区
    2. 只有的特定的执行点上才会发生抢占(一个反向的临界区?)

要内外部条件都满足.

preempt_disable_count是多个结合的,因此计数会大于1.只有是0的情况下才可以被抢占.

CFS比之前的那种抢占式调度不同,加了一种调度算法.

vruntime = init_vruntime + (delta / weight(nice)).

系统初始化时,init_vruntime, delta, nice三者都是0.但是我们可以人为根据偏好设置init_vruntime,但是随着系统运行时间的增加,init_vruntime的作用越来越小.

vruntime最小的任务就是优先权最高任务,即当前任务.

每次始终中断的时候都会递增delta(但是不会直接切换任务,而会运行优先级最高的任务),随着delta的递增,导致这个任务的优先级不够,然后就会被换掉.

即使下次还是运行自己,还是会发生一次无用切换.

实验验证

第一个实验

make run A=tour/u_6_0

出现的log如下:

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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
OpenSBI v0.9
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
| |__| | |_) | __/ | | |____) | |_) || |_
\____/| .__/ \___|_| |_|_____/|____/_____|
| |
|_|

Platform Name : riscv-virtio,qemu
Platform Features : timer,mfdeleg
Platform HART Count : 1
Firmware Base : 0x80000000
Firmware Size : 100 KB
Runtime SBI Version : 0.2

Domain0 Name : root
Domain0 Boot HART : 0
Domain0 HARTs : 0*
Domain0 Region00 : 0x0000000080000000-0x000000008001ffff ()
Domain0 Region01 : 0x0000000000000000-0xffffffffffffffff (R,W,X)
Domain0 Next Address : 0x0000000080200000
Domain0 Next Arg1 : 0x0000000087000000
Domain0 Next Mode : S-mode
Domain0 SysReset : yes

Boot HART ID : 0
Boot HART Domain : root
Boot HART ISA : rv64imafdcsu
Boot HART Features : scounteren,mcounteren,time
Boot HART PMP Count : 16
Boot HART PMP Granularity : 4
Boot HART PMP Address Bits: 54
Boot HART MHPM Count : 0
Boot HART MHPM Count : 0
Boot HART MIDELEG : 0x0000000000000222
Boot HART MEDELEG : 0x000000000000b109

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

Multi-task(Preemptible) is starting ...
worker1 ... ThreadId(4)
worker1 [0]
worker1 [1]
worker1 [2]
worker1 [3]
worker1 [4]
worker1 [5]
worker1 [6]
worker1 [7]
worker1 [8]
worker1 [9]
Wait for workers to exit ...
worker2 ... ThreadId(5)
worker2 [0]
worker2 [1]
worker2 [2]
worker2 [3]
worker2 [4]
worker2 [5]
worker2 [6]
worker2 [7]
worker2 [8]
worker2: nothing to do!
worker2: nothing to do!
worker2: nothing to do!
worker1 [10]
worker2 [9]
worker2: nothing to do!
worker2: nothing to do!
worker1 [11]
worker2 [10]
worker2: nothing to do!
worker1 [12]
worker1 [13]
worker2 [11]
worker2 [12]
worker1 [14]
worker1 [15]
worker2 [13]
worker2 [14]
worker2: nothing to do!
worker1 [16]
worker2 [15]
worker2: nothing to do!
worker1 [17]
worker1 [18]
worker2 [16]
worker2 [17]
worker1 [19]
worker1 [20]
worker2 [18]
worker2 [19]
worker2: nothing to do!
worker1 [21]
worker2 [20]
worker2: nothing to do!
worker1 [22]
worker1 [23]
worker2 [21]
worker2 [22]
worker1 [24]
worker1 [25]
worker2 [23]
worker2 [24]
worker1 [26]
worker1 [27]
worker1 [28]
worker2 [25]
worker2 [26]
worker1 [29]
worker1 [30]
worker2 [27]
worker2 [28]
worker1 [31]
worker1 [32]
worker1 [33]
worker2 [29]
worker2 [30]
worker1 [34]
worker1 [35]
worker2 [31]
worker2 [32]
worker1 [36]
worker1 [37]
worker1 [38]
worker2 [33]
worker2 [34]
worker1 [39]
worker1 [40]
worker2 [35]
worker2 [36]
worker2 [37]
worker1 [41]
worker1 [42]
worker2 [38]
worker2 [39]
worker1 [43]
worker1 [44]
worker2 [40]
worker2 [41]
worker2 [42]
worker1 [45]
worker1 [46]
worker2 [43]
worker2 [44]
worker1 [47]
worker1 [48]
worker2 [45]
worker2 [46]
worker2 [47]
worker1 [49]
worker1 [50]
worker2 [48]
worker2 [49]
worker1 [51]
worker1 [52]
worker2 [50]
worker2 [51]
worker2: nothing to do!
worker1 [53]
worker2 [52]
worker2: nothing to do!
worker1 [54]
worker1 [55]
worker2 [53]
worker2 [54]
worker1 [56]
worker1 [57]
worker2 [55]
worker2 [56]
worker1 [58]
worker1 [59]
worker1 [60]
worker2 [57]
worker2 [58]
worker1 [61]
worker1 [62]
worker2 [59]
worker2 [60]
worker2 [61]
worker1 [63]
worker1 [64]
worker2 [62]
worker2 [63]
worker1 [65]
worker1 [66]
worker2 [64]
worker2 [65]
worker2: nothing to do!
worker1 [67]
worker2 [66]
worker2: nothing to do!
worker1 [68]
worker1 [69]
worker2 [67]
worker2 [68]
worker1 [70]
worker1 [71]
worker2 [69]
worker2 [70]
worker2: nothing to do!
worker1 [72]
worker2 [71]
worker2: nothing to do!
worker1 [73]
worker2 [72]
worker2: nothing to do!
worker1 [74]
worker1 [75]
worker2 [73]
worker2 [74]
worker1 [76]
worker1 [77]
worker1 [78]
worker2 [75]
worker2 [76]
worker1 [79]
worker1 [80]
worker2 [77]
worker2 [78]
worker1 [81]
worker1 [82]
worker1 [83]
worker2 [79]
worker2 [80]
worker1 [84]
worker1 [85]
worker2 [81]
worker2 [82]
worker1 [86]
worker1 [87]
worker1 [88]
worker2 [83]
worker2 [84]
worker1 [89]
worker1 [90]
worker2 [85]
worker2 [86]
worker1 [91]
worker1 [92]
worker1 [93]
worker2 [87]
worker2 [88]
worker1 [94]
worker1 [95]
worker2 [89]
worker2 [90]
worker1 [96]
worker1 [97]
worker1 [98]
worker2 [91]
worker2 [92]
worker1 [99]
worker1 [100]
worker2 [93]
worker2 [94]
worker1 [101]
worker1 [102]
worker2 [95]
worker2 [96]
worker1 [103]
worker1 [104]
worker2 [97]
worker2 [98]
worker2 [99]
worker1 [105]
worker1 [106]
worker2 [100]
worker2 [101]
worker1 [107]
worker1 [108]
worker2 [102]
worker2 [103]
worker1 [109]
worker1 [110]
worker2 [104]
worker2 [105]
worker1 [111]
worker1 [112]
worker2 [106]
worker2 [107]
worker1 [113]
worker1 [114]
worker1 [115]
worker2 [108]
worker2 [109]
worker1 [116]
worker1 [117]
worker2 [110]
worker1 [118]
worker1 [119]
worker2 [111]
worker2 [112]
worker2 [113]
worker1 [120]
worker1 [121]
worker2 [114]
worker2 [115]
worker1 [122]
worker1 [123]
worker2 [116]
worker2 [117]
worker1 [124]
worker1 [125]
worker2 [118]
worker2 [119]
worker1 [126]
worker1 [127]
worker2 [120]
worker2 [121]
worker1 [128]
worker1 [129]
worker2 [122]
worker2 [123]
worker2 [124]
worker1 [130]
worker1 [131]
worker2 [125]
worker2 [126]
worker1 [132]
worker1 [133]
worker2 [127]
worker2 [128]
worker1 [134]
worker1 [135]
worker2 [129]
worker2 [130]
worker1 [136]
worker1 [137]
worker2 [131]
worker2 [132]
worker1 [138]
worker1 [139]
worker2 [133]
worker2 [134]
worker2 [135]
worker1 [140]
worker1 [141]
worker2 [136]
worker2 [137]
worker1 [142]
worker1 [143]
worker2 [138]
worker2 [139]
worker1 [144]
worker1 [145]
worker2 [140]
worker2 [141]
worker1 [146]
worker1 [147]
worker2 [142]
worker2 [143]
worker1 [148]
worker1 [149]
worker2 [144]
worker2 [145]
worker2 [146]
worker1 [150]
worker1 [151]
worker2 [147]
worker2 [148]
worker1 [152]
worker1 [153]
worker2 [149]
worker2 [150]
worker1 [154]
worker1 [155]
worker2 [151]
worker2 [152]
worker1 [156]
worker1 [157]
worker2 [153]
worker2 [154]
worker1 [158]
worker1 [159]
worker2 [155]
worker2 [156]
worker2 [157]
worker1 [160]
worker1 [161]
worker2 [158]
worker2 [159]
worker1 [162]
worker1 [163]
worker2 [160]
worker2 [161]
worker1 [164]
worker1 [165]
worker2 [162]
worker2 [163]
worker1 [166]
worker1 [167]
worker2 [164]
worker2 [165]
worker1 [168]
worker1 [169]
worker2 [166]
worker2 [167]
worker2 [168]
worker1 [170]
worker1 [171]
worker2 [169]
worker2 [170]
worker1 [172]
worker1 [173]
worker2 [171]
worker2 [172]
worker1 [174]
worker1 [175]
worker2 [173]
worker2 [174]
worker1 [176]
worker1 [177]
worker2 [175]
worker2 [176]
worker1 [178]
worker1 [179]
worker2 [177]
worker2 [178]
worker1 [180]
worker1 [181]
worker1 [182]
worker2 [179]
worker2 [180]
worker1 [183]
worker1 [184]
worker2 [181]
worker2 [182]
worker1 [185]
worker1 [186]
worker2 [183]
worker2 [184]
worker1 [187]
worker1 [188]
worker2 [185]
worker2 [186]
worker1 [189]
worker1 [190]
worker2 [187]
worker2 [188]
worker1 [191]
worker1 [192]
worker2 [189]
worker2 [190]
worker1 [193]
worker1 [194]
worker1 [195]
worker2 [191]
worker2 [192]
worker1 [196]
worker1 [197]
worker2 [193]
worker2 [194]
worker1 [198]
worker1 [199]
worker2 [195]
worker2 [196]
worker1 [200]
worker1 [201]
worker2 [197]
worker2 [198]
worker1 [202]
worker1 [203]
worker2 [199]
worker2 [200]
worker1 [204]
worker1 [205]
worker2 [201]
worker2 [202]
worker1 [206]
worker1 [207]
worker2 [203]
worker2 [204]
worker2 [205]
worker1 [208]
worker1 [209]
worker2 [206]
worker2 [207]
worker1 [210]
worker1 [211]
worker2 [208]
worker2 [209]
worker1 [212]
worker1 [213]
worker2 [210]
worker2 [211]
worker1 [214]
worker1 [215]
worker2 [212]
worker2 [213]
worker1 [216]
worker1 [217]
worker2 [214]
worker2 [215]
worker2 [216]
worker1 [218]
worker1 [219]
worker2 [217]
worker2 [218]
worker1 [220]
worker1 [221]
worker2 [219]
worker2 [220]
worker1 [222]
worker1 [223]
worker2 [221]
worker2 [222]
worker1 [224]
worker1 [225]
worker2 [223]
worker2 [224]
worker1 [226]
worker1 [227]
worker1 [228]
worker2 [225]
worker2 [226]
worker1 [229]
worker1 [230]
worker2 [227]
worker2 [228]
worker1 [231]
worker1 [232]
worker2 [229]
worker2 [230]
worker1 [233]
worker1 [234]
worker2 [231]
worker2 [232]
worker1 [235]
worker1 [236]
worker2 [233]
worker2 [234]
worker1 [237]
worker1 [238]
worker1 [239]
worker2 [235]
worker2 [236]
worker1 [240]
worker1 [241]
worker2 [237]
worker2 [238]
worker1 [242]
worker1 [243]
worker2 [239]
worker2 [240]
worker1 [244]
worker1 [245]
worker2 [241]
worker2 [242]
worker1 [246]
worker1 [247]
worker2 [243]
worker2 [244]
worker1 [248]
worker1 [249]
worker1 [250]
worker2 [245]
worker2 [246]
worker1 [251]
worker1 [252]
worker2 [247]
worker2 [248]
worker1 [253]
worker1 [254]
worker2 [249]
worker2 [250]
worker1 [255]
worker1 [256]
worker2 [251]
worker2 [252]
worker1 ok!
worker2 [253]
worker2 [254]
worker2 [255]
worker2 [256]
worker2 ok!
Multi-task(Preemptible) ok!
  1. 最开始worker1运行了9次println,但是只运行了8次push_back,每次时间片耗尽触发了cfs调度算法,但是一直到这时候才轮到worker2vruntime最小(而不是触发时间片耗尽的round_robin).
  2. 随后worker2获取到双端队列之后,尝试输出队列最前边的内容.全部输出完之后,进入另一个分支输出worker2: nothing to do!,并且yield.但是由于vruntime仍然是worker2最小,因此又运行了两次,一共运行三次以后才切换回worker1.(这说明cfs调度算法的yield是考虑切换,而不是直接把当前任务放进队列最后)
  3. worker1在输出worker1 [10]之后还没来得及把10压入队列,这时候又切换回worker2.
  4. 这时候worker2发生了三次yield但是还是没有切换到work1.于是输出了一次worker2 [9],又触发了两次worker2: nothing to do!.
  5. 后边也都这样进行分析即可.可以看到在cfs算法中,即使你使用了yield还是可能切换回本任务.

第二个实验

make run A=tour/u_6_1

产生的log如下:

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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
OpenSBI v0.9
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
| |__| | |_) | __/ | | |____) | |_) || |_
\____/| .__/ \___|_| |_|_____/|____/_____|
| |
|_|

Platform Name : riscv-virtio,qemu
Platform Features : timer,mfdeleg
Platform HART Count : 1
Firmware Base : 0x80000000
Firmware Size : 100 KB
Runtime SBI Version : 0.2

Domain0 Name : root
Domain0 Boot HART : 0
Domain0 HARTs : 0*
Domain0 Region00 : 0x0000000080000000-0x000000008001ffff ()
Domain0 Region01 : 0x0000000000000000-0xffffffffffffffff (R,W,X)
Domain0 Next Address : 0x0000000080200000
Domain0 Next Arg1 : 0x0000000087000000
Domain0 Next Mode : S-mode
Domain0 SysReset : yes

Boot HART ID : 0
Boot HART Domain : root
Boot HART ISA : rv64imafdcsu
Boot HART Features : scounteren,mcounteren,time
Boot HART PMP Count : 16
Boot HART PMP Granularity : 4
Boot HART PMP Address Bits: 54
Boot HART MHPM Count : 0
Boot HART MHPM Count : 0
Boot HART MIDELEG : 0x0000000000000222
Boot HART MEDELEG : 0x000000000000b109

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

WaitQ is starting ...
worker1 ...
worker1 [0]
worker1 [1]
worker1 [2]
worker1 [3]
worker1 [4]
worker2 ...
Wait for workers to exit ...
worker2 [0]
worker2 [1]
worker2 [2]
worker2 [3]
worker2 [4]
worker1 [5]
worker2 [5]
worker1 [6]
worker2 [6]
worker1 [7]
worker2 [7]
worker1 [8]
worker2 [8]
worker1 [9]
worker2 [9]
worker1 [10]
worker2 [10]
worker1 [11]
worker2 [11]
worker1 [12]
worker2 [12]
worker1 [13]
worker2 [13]
worker1 [14]
worker2 [14]
worker1 [15]
worker2 [15]
worker1 [16]
worker2 [16]
worker1 [17]
worker2 [17]
worker1 [18]
worker2 [18]
worker1 [19]
worker2 [19]
worker1 [20]
worker2 [20]
worker1 [21]
worker2 [21]
worker1 [22]
worker2 [22]
worker1 [23]
worker2 [23]
worker1 [24]
worker2 [24]
worker1 [25]
worker2 [25]
worker1 [26]
worker2 [26]
worker1 [27]
worker2 [27]
worker1 [28]
worker2 [28]
worker1 [29]
worker2 [29]
worker1 [30]
worker2 [30]
worker1 [31]
worker2 [31]
worker1 [32]
worker2 [32]
worker1 [33]
worker2 [33]
worker1 [34]
worker2 [34]
worker1 [35]
worker2 [35]
worker1 [36]
worker2 [36]
worker1 [37]
worker2 [37]
worker1 [38]
worker2 [38]
worker1 [39]
worker2 [39]
worker1 [40]
worker2 [40]
worker1 [41]
worker2 [41]
worker1 [42]
worker2 [42]
worker1 [43]
worker2 [43]
worker1 [44]
worker2 [44]
worker1 [45]
worker2 [45]
worker1 [46]
worker2 [46]
worker1 [47]
worker2 [47]
worker1 [48]
worker2 [48]
worker1 [49]
worker2 [49]
worker1 [50]
worker2 [50]
worker1 [51]
worker2 [51]
worker1 [52]
worker2 [52]
worker1 [53]
worker2 [53]
worker1 [54]
worker2 [54]
worker1 [55]
worker2 [55]
worker1 [56]
worker2 [56]
worker1 [57]
worker2 [57]
worker1 [58]
worker2 [58]
worker1 [59]
worker2 [59]
worker1 [60]
worker2 [60]
worker1 [61]
worker2 [61]
worker1 [62]
worker2 [62]
worker1 [63]
worker2 [63]
worker1 [64]
worker2 [64]
worker1 [65]
worker2 [65]
worker1 [66]
worker2 [66]
worker1 [67]
worker2 [67]
worker1 [68]
worker2 [68]
worker1 [69]
worker2 [69]
worker1 [70]
worker2 [70]
worker1 [71]
worker2 [71]
worker1 [72]
worker2 [72]
worker1 [73]
worker2 [73]
worker1 [74]
worker2 [74]
worker1 [75]
worker2 [75]
worker1 [76]
worker2 [76]
worker1 [77]
worker2 [77]
worker1 [78]
worker2 [78]
worker1 [79]
worker2 [79]
worker1 [80]
worker2 [80]
worker1 [81]
worker2 [81]
worker1 [82]
worker2 [82]
worker1 [83]
worker2 [83]
worker1 [84]
worker2 [84]
worker1 [85]
worker2 [85]
worker1 [86]
worker2 [86]
worker1 [87]
worker2 [87]
worker1 [88]
worker2 [88]
worker1 [89]
worker2 [89]
worker1 [90]
worker2 [90]
worker1 [91]
worker2 [91]
worker1 [92]
worker2 [92]
worker1 [93]
worker2 [93]
worker1 [94]
worker2 [94]
worker1 [95]
worker2 [95]
worker1 [96]
worker2 [96]
worker1 [97]
worker2 [97]
worker1 [98]
worker2 [98]
worker1 [99]
worker2 [99]
worker1 [100]
worker2 [100]
worker1 [101]
worker2 [101]
worker1 [102]
worker2 [102]
worker1 [103]
worker2 [103]
worker1 [104]
worker2 [104]
worker1 [105]
worker2 [105]
worker1 [106]
worker2 [106]
worker1 [107]
worker2 [107]
worker1 [108]
worker2 [108]
worker1 [109]
worker2 [109]
worker1 [110]
worker2 [110]
worker1 [111]
worker2 [111]
worker1 [112]
worker2 [112]
worker1 [113]
worker2 [113]
worker1 [114]
worker2 [114]
worker1 [115]
worker2 [115]
worker1 [116]
worker2 [116]
worker1 [117]
worker2 [117]
worker1 [118]
worker2 [118]
worker1 [119]
worker2 [119]
worker1 [120]
worker2 [120]
worker1 [121]
worker2 [121]
worker1 [122]
worker2 [122]
worker1 [123]
worker2 [123]
worker1 [124]
worker2 [124]
worker1 [125]
worker2 [125]
worker1 [126]
worker2 [126]
worker1 [127]
worker2 [127]
worker1 [128]
worker2 [128]
worker1 [129]
worker2 [129]
worker1 [130]
worker2 [130]
worker1 [131]
worker2 [131]
worker1 [132]
worker2 [132]
worker1 [133]
worker2 [133]
worker1 [134]
worker2 [134]
worker1 [135]
worker2 [135]
worker1 [136]
worker2 [136]
worker1 [137]
worker2 [137]
worker1 [138]
worker2 [138]
worker1 [139]
worker2 [139]
worker1 [140]
worker2 [140]
worker1 [141]
worker2 [141]
worker1 [142]
worker2 [142]
worker1 [143]
worker2 [143]
worker1 [144]
worker2 [144]
worker1 [145]
worker2 [145]
worker1 [146]
worker2 [146]
worker1 [147]
worker2 [147]
worker1 [148]
worker2 [148]
worker1 [149]
worker2 [149]
worker1 [150]
worker2 [150]
worker1 [151]
worker2 [151]
worker1 [152]
worker2 [152]
worker1 [153]
worker2 [153]
worker1 [154]
worker2 [154]
worker1 [155]
worker2 [155]
worker1 [156]
worker2 [156]
worker1 [157]
worker2 [157]
worker1 [158]
worker2 [158]
worker1 [159]
worker2 [159]
worker1 [160]
worker2 [160]
worker1 [161]
worker2 [161]
worker1 [162]
worker2 [162]
worker1 [163]
worker2 [163]
worker1 [164]
worker2 [164]
worker1 [165]
worker2 [165]
worker1 [166]
worker2 [166]
worker1 [167]
worker2 [167]
worker1 [168]
worker2 [168]
worker1 [169]
worker2 [169]
worker1 [170]
worker2 [170]
worker1 [171]
worker2 [171]
worker1 [172]
worker2 [172]
worker1 [173]
worker2 [173]
worker1 [174]
worker2 [174]
worker1 [175]
worker2 [175]
worker1 [176]
worker2 [176]
worker1 [177]
worker2 [177]
worker1 [178]
worker2 [178]
worker1 [179]
worker2 [179]
worker1 [180]
worker2 [180]
worker1 [181]
worker2 [181]
worker1 [182]
worker2 [182]
worker1 [183]
worker2 [183]
worker1 [184]
worker2 [184]
worker1 [185]
worker2 [185]
worker1 [186]
worker2 [186]
worker1 [187]
worker2 [187]
worker1 [188]
worker2 [188]
worker1 [189]
worker2 [189]
worker1 [190]
worker2 [190]
worker1 [191]
worker2 [191]
worker1 [192]
worker2 [192]
worker1 [193]
worker2 [193]
worker1 [194]
worker2 [194]
worker1 [195]
worker2 [195]
worker1 [196]
worker2 [196]
worker1 [197]
worker2 [197]
worker1 [198]
worker2 [198]
worker1 [199]
worker2 [199]
worker1 [200]
worker2 [200]
worker1 [201]
worker2 [201]
worker1 [202]
worker2 [202]
worker1 [203]
worker2 [203]
worker1 [204]
worker2 [204]
worker1 [205]
worker2 [205]
worker1 [206]
worker2 [206]
worker1 [207]
worker2 [207]
worker1 [208]
worker2 [208]
worker1 [209]
worker2 [209]
worker1 [210]
worker2 [210]
worker1 [211]
worker2 [211]
worker1 [212]
worker2 [212]
worker1 [213]
worker2 [213]
worker1 [214]
worker2 [214]
worker1 [215]
worker2 [215]
worker1 [216]
worker2 [216]
worker1 [217]
worker2 [217]
worker1 [218]
worker2 [218]
worker1 [219]
worker2 [219]
worker1 [220]
worker2 [220]
worker1 [221]
worker2 [221]
worker1 [222]
worker2 [222]
worker1 [223]
worker2 [223]
worker1 [224]
worker2 [224]
worker1 [225]
worker2 [225]
worker1 [226]
worker2 [226]
worker1 [227]
worker2 [227]
worker1 [228]
worker2 [228]
worker1 [229]
worker2 [229]
worker1 [230]
worker2 [230]
worker1 [231]
worker2 [231]
worker1 [232]
worker2 [232]
worker1 [233]
worker2 [233]
worker1 [234]
worker2 [234]
worker1 [235]
worker2 [235]
worker1 [236]
worker2 [236]
worker1 [237]
worker2 [237]
worker1 [238]
worker2 [238]
worker1 [239]
worker2 [239]
worker1 [240]
worker2 [240]
worker1 [241]
worker2 [241]
worker1 [242]
worker2 [242]
worker1 [243]
worker2 [243]
worker1 [244]
worker2 [244]
worker1 [245]
worker2 [245]
worker1 [246]
worker2 [246]
worker1 [247]
worker2 [247]
worker1 [248]
worker2 [248]
worker1 [249]
worker2 [249]
worker1 [250]
worker2 [250]
worker1 [251]
worker2 [251]
worker1 [252]
worker2 [252]
worker1 [253]
worker2 [253]
worker1 [254]
worker2 [254]
worker1 [255]
worker2 [255]
worker1 [256]
worker2 [256]
worker2 ok!
worker1 ok!
WaitQ ok!

可以很明显看到加了WaitQ之后worker1worker2的调度更均匀了,或许说均匀并不好,应该说是worker2: nothing to do!基本消失了.

块设备

引入块设备之后引入了从块设备去加载磁盘的数据.而不是只读取PFlash这种简单的设备.

AllDevices管理系统上所有的设备.

static用泛型的方法高效率地对设备类型进行的封装.因此一个类型只能管理一个设备.

dyn的动态方法,其实是利用了动态可变Vec每个类型有多个实例.

设备的发现方式:

  1. pcie的协议来发现设备
  2. mmio通过设备树找设备

但是现在没有使用设备树.而是使用(for_each_drivers!)的方式.

virtio设备是用qemu的命令设置出来的虚拟设备.

有八个槽位来放这些驱动.

virtio-mmio驱动是对各个槽位发送查询请求,就此获得槽位的设备类型.

IPI中断是多核之间通信的中断,是中断但是是通过发送一个命令到另一个核触发硬中断实现的.

文件系统的操作流程,就是从Root节点一直进行lookup的方式一直到目标节点,然后对目标节点进行操作.

文件系统的示例:

  1. Ext2
  2. ArceOS使用的文件系统是Fat32

mount可以看成文件系统在内存中的展开操作.

就是目录树存在储存介质里的时候是扁平的,而现在要使用的是展开的立体结构.

mount可以把另一个文件系统的目录树挂载到当前文件系统的目录树上.

mount做挂载的时候,可以通过读取最长的路径来解决一部分问题.

读取块设备的实验

make run A=tour/u_7_0 BLK=y

有一个feature叫做dyn,只有在开启这个的时候才是每种类型的设备有一个Vec来管理,不然都是每种类型的设备都只有一个.

这里用的驱动不是mmio而是pci的驱动,虽然PPT上讲了很多关于mmio的内容.这令人感到奇怪,看modules/axdriver/Cargo.toml里的描述,default=["bus-pci"],这里我们改成default = ["bus-mmio"]也可以正常运行,而且看LOG是不一样的.

cargo b -vv在编译的时候看build.rs的输出.

同样地,这个实验里被预先加载进块设备的内容在scripts/make/utils.mk里有定义,详见之前的mk_pflash的部分.

从文件系统加载数据的实验

1
make run A=tour/u_8_0 BLK=y

这个任务没什么好说的,主要看这个实验的Cargo文件,为axstd打开了fs这个feature.

我们可以进去尽情看一下实验中调用的openread的实现.

为shell增加文件操作命令

让我难受了很久的一个问题就是这个rename,ax_rename对应的(ax_)fatfsrename也是只能使用改名当前目录下的文件.
实际上调用的rust-fatfsrename是支持改名一个文件到另一个目录下边的.实际上也就是允许把一个inode更替到另一个inode下边.
这样实际上就能用rename实现mv的功能.

这一个部分心太急了,没有看其它的api的运行流程.

在编译运行的时候应该利用好LOG="level"

实验1

这部分实验是上部分的作业暂时略过.

LinuxApp

实验命令:

1
2
3
make payload
./update_disk.sh payload/hello_c/hello
make run A=tour/m_3_0 BLK=y

这里看payload/hello_c/Makefile,可以看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
TARGET := hello

CC := riscv64-linux-musl-gcc
STRIP := riscv64-linux-musl-strip

all: $(TARGET)

%: %.c
$(CC) -static $< -o $@
$(STRIP) $@

clean:
@rm -rf ./$(TARGET)

可以看到我们使用的编译器信息移除工具都是指定的版本是linux.

这张图有些害人匪浅了,

这个图是linux应用的用户栈.

但是我们从实用的角度来看,应用主函数的原型:

1
int main(int argc, char *argv[], char *enp[]);

我们只需要在栈里边按顺序保存:

  1. argc
  2. arg_ptr
  3. env_ptr
  4. auxv

即可,只要argc的值是对的,arg_ptrenv_ptr指向的实例是对的即可.

这里有一个疑问:到底谁是对的?

kernel-elf-parser里的src/user_stack.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
27
28
//! Initialize the user stack for the application
//!
//! The structure of the user stack is described in the following figure:
//! position content size (bytes) + comment
//! ------------------------------------------------------------------------
//! stack pointer -> [ argc = number of args ] 8
//! [ argv[0] (pointer) ] 8 (program name)
//! [ argv[1] (pointer) ] 8
//! [ argv[..] (pointer) ] 8 * x
//! [ argv[n - 1] (pointer) ] 8
//! [ argv[n] (pointer) ] 8 (= NULL)
//! [ envp[0] (pointer) ] 8
//! [ envp[1] (pointer) ] 8
//! [ envp[..] (pointer) ] 8
//! [ envp[term] (pointer) ] 8 (= NULL)
//! [ auxv[0] (Elf32_auxv_t) ] 16
//! [ auxv[1] (Elf32_auxv_t) ] 16
//! [ auxv[..] (Elf32_auxv_t) ] 16
//! [ auxv[term] (Elf32_auxv_t) ] 16 (= AT_NULL vector)
//! [ padding ] 0 - 16
//! [ argument ASCIIZ strings ] >= 0
//! [ environment ASCIIZ str. ] >= 0
//!
//! (0xbffffff8) [ end marker ] 8 (= NULL)
//!
//! (0xc0000000) < bottom of stack > 0 (virtual)
//!
//! More details can be found in the link: <https://articles.manugarg.com/aboutelfauxiliaryvectors.html>

形成的栈:

|300

运行log:

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
102
103
104
105
106
107
108
109
110
111
112
113
OpenSBI v0.9
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
| |__| | |_) | __/ | | |____) | |_) || |_
\____/| .__/ \___|_| |_|_____/|____/_____|
| |
|_|

Platform Name : riscv-virtio,qemu
Platform Features : timer,mfdeleg
Platform HART Count : 1
Firmware Base : 0x80000000
Firmware Size : 100 KB
Runtime SBI Version : 0.2

Domain0 Name : root
Domain0 Boot HART : 0
Domain0 HARTs : 0*
Domain0 Region00 : 0x0000000080000000-0x000000008001ffff ()
Domain0 Region01 : 0x0000000000000000-0xffffffffffffffff (R,W,X)
Domain0 Next Address : 0x0000000080200000
Domain0 Next Arg1 : 0x0000000087000000
Domain0 Next Mode : S-mode
Domain0 SysReset : yes

Boot HART ID : 0
Boot HART Domain : root
Boot HART ISA : rv64imafdcsu
Boot HART Features : scounteren,mcounteren,time
Boot HART PMP Count : 16
Boot HART PMP Granularity : 4
Boot HART PMP Address Bits: 54
Boot HART MHPM Count : 0
Boot HART MHPM Count : 0
Boot HART MIDELEG : 0x0000000000000222
Boot HART MEDELEG : 0x000000000000b109

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 = info

[ 1.746356 0 axruntime:130] Logging is enabled.
[ 1.856119 0 axruntime:131] Primary CPU 0 started, dtb = 0x87000000.
[ 1.905723 0 axruntime:133] Found physcial memory regions:
[ 1.962960 0 axruntime:135] [PA:0x80200000, PA:0x80232000) .text (READ | EXECUTE | RESERVED)
[ 2.026512 0 axruntime:135] [PA:0x80232000, PA:0x80241000) .rodata (READ | RESERVED)
[ 2.073912 0 axruntime:135] [PA:0x80241000, PA:0x80244000) .data .tdata .tbss .percpu (READ | WRITE | RESERVED)
[ 2.124278 0 axruntime:135] [PA:0x80244000, PA:0x80284000) boot stack (READ | WRITE | RESERVED)
[ 2.168556 0 axruntime:135] [PA:0x80284000, PA:0x802ad000) .bss (READ | WRITE | RESERVED)
[ 2.212764 0 axruntime:135] [PA:0x802ad000, PA:0x88000000) free memory (READ | WRITE | FREE)
[ 2.261680 0 axruntime:135] [PA:0x101000, PA:0x102000) mmio (READ | WRITE | DEVICE | RESERVED)
[ 2.305544 0 axruntime:135] [PA:0xc000000, PA:0xc210000) mmio (READ | WRITE | DEVICE | RESERVED)
[ 2.349843 0 axruntime:135] [PA:0x10000000, PA:0x10001000) mmio (READ | WRITE | DEVICE | RESERVED)
[ 2.394978 0 axruntime:135] [PA:0x10001000, PA:0x10009000) mmio (READ | WRITE | DEVICE | RESERVED)
[ 2.440055 0 axruntime:135] [PA:0x22000000, PA:0x24000000) mmio (READ | WRITE | DEVICE | RESERVED)
[ 2.485718 0 axruntime:135] [PA:0x30000000, PA:0x40000000) mmio (READ | WRITE | DEVICE | RESERVED)
[ 2.530990 0 axruntime:135] [PA:0x40000000, PA:0x80000000) mmio (READ | WRITE | DEVICE | RESERVED)
[ 2.583846 0 axruntime:208] Initialize global memory allocator...
[ 2.621634 0 axruntime:209] use TLSF allocator.
[ 2.816195 0 axmm:81] Initialize virtual memory management...
[ 3.188863 0 axruntime:150] Initialize platform devices...
[ 3.249907 0 axtask::api:68] Initialize scheduling...
[ 3.436552 0 axtask::api:74] use Completely Fair scheduler.
[ 3.474966 0 axdriver:152] Initialize device drivers...
[ 3.510394 0 axdriver:153] device model: static
[ 3.664938 0 virtio_drivers::device::blk:59] config: 0xffffffc040006000
[ 3.721121 0 virtio_drivers::device::blk:64] found a block device of size 65536KB
[ 3.787426 0 axdriver::bus::pci:104] registered a new Block device at 00:01.0: "virtio-blk"
[ 21.285217 0 axfs:41] Initialize filesystems...
[ 21.329601 0 axfs:44] use block device 0: "virtio-blk"
[ 22.099152 0 fatfs::dir:139] Is a directory
[ 22.277106 0 fatfs::dir:139] Is a directory
[ 22.556181 0 fatfs::dir:139] Is a directory
[ 22.683443 0 fatfs::dir:139] Is a directory
[ 22.770783 0 axruntime:176] Initialize interrupt handlers...
[ 22.932112 0 axruntime:186] Primary CPU 0 init OK.
[ 23.210370 0:2 m_3_0::loader:58] e_entry: 0x50E
phdr: offset: 0x0=>0x0 size: 0x17CC=>0x17CC
VA:0x0 - VA:0x2000
phdr: offset: 0x1E70=>0x2E70 size: 0x338=>0x9A8
VA:0x2000 - VA:0x4000
entry: 0x50e
Mapping user stack: VA:0x3fffff0000 -> VA:0x4000000000
New user address space: AddrSpace {
va_range: VA:0x0..VA:0x4000000000,
page_table_root: PA:0x8064e000,
}
[ 23.946790 0:4 m_3_0::task:56] Enter user space: entry=0x50e, ustack=0x3fffffffc0, kstack=VA:0xffffffc0806a7010
handle_syscall [96] ...
handle_syscall [29] ...
Unimplemented syscall: SYS_IOCTL
handle_syscall [66] ...
Hello, UserApp!
handle_syscall [66] ...

handle_syscall [94] ...
[SYS_EXIT_GROUP]: system is exiting ..
monolithic kernel exit [Some(0)] normally!
[ 24.504671 0:2 axhal::platform::riscv64_qemu_virt::misc:3] Shutting down...

可以看到运行过程中还调用了:SYS_IOCTLSYS_SET_TID_ADDRESS两个系统调用.

这是因为:”示例m_3_0基于musl工具链以静态方式编译,工具链为应用附加的部分也会调用syscall。”

就是添加的这个_start_exit的系统调用.

set_tid_address会设置clear_child_tid的值,在进程创建和释放的时候会用到.
set_tid_address在父线程创建一个子线程的时候会把自己的tid写到这个address的区域里.
clear_child_tid在释放自己线程或者锁和其它资源的时候,会把返回的值里写入到address里.

ioctl是用来设置对外输出终端属性的.
现在用的是sbiputchar,因此可以直接跳过.

对于不同的体系结构,系统调用号不同。示例是基于riscv64的系统调用号规范。

最后总结就是我们设置好合理的syscall,把系统调用号设置好,那么就可以实现一定程度上的兼容.

像这个APP只需要提供syscall的兼容层就行了.

其余的兼容层根据APP不同也需要实现.

对Linux常用文件系统的支持

arceOS是通过axfs_ramfsprocfssysfs提供兼容.通过axfs_devfs提供devfs的兼容.
目前用ramfs进行兼容是一个临时的方法.
也就是使用内存文件系统.访问的时候相当于访问了一个基于内存的节点,里边有一些基于内存的数据,这些数据是其它子系统填充过来的数据.
正常的Linux是你访问这个proc之类的文件的时候实际上是调用了一个回调函数去获取系统状态.

实现mmap系统调用

实现方法:

  1. 通过sys_read方法读取到文件里的内容.
  2. 读取当前的任务的user space.
  3. 寻找空闲的映射空间的虚拟地址
  4. 构造flag.
  5. 创建一块frame,并且把虚拟地址映射到frame.
  6. 把文件内容拷贝到内存中去
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
#[allow(unused_variables)]
fn sys_mmap(
addr: *mut usize,
length: usize,
prot: i32,
flags: i32,
fd: i32,
_offset: isize,
) -> isize {
const MAX_MMAP_SIZE: usize = 64;
let mut buf: [u8; 64] = [0u8;MAX_MMAP_SIZE];
unsafe {
let buf_ptr = &mut buf as *mut _ as *mut c_void;
sys_read(fd, buf_ptr, length+_offset as usize);
}
let mut buf = &buf[_offset as usize..length+_offset as usize];

let binding = current();
let mut uspace = &mut binding.task_ext().aspace.lock();

let free_va = if addr.is_null() {
uspace.find_free_area(
(addr as usize).into(),
length,
VirtAddrRange::new(
uspace.base(),
uspace.end()))
.unwrap_or_else(|| panic!("No free area for mmap"))
}else{
(addr as usize).into()
};

// 把prot转换成MappingFlags
let mut flags = MappingFlags::from(MmapProt::from_bits_truncate(prot));
flags.set(MappingFlags::USER, true);

uspace.map_alloc(
free_va,
PAGE_SIZE_4K,
flags,
true)
.unwrap();
let (paddr, _, _) = uspace
.page_table()
.query(free_va)
.unwrap_or_else(|_| panic!("Mapping failed for segment"));
unsafe {
core::ptr::copy_nonoverlapping(
buf.as_ptr(),
phys_to_virt(paddr).as_mut_ptr(),
PAGE_SIZE_4K,
);
}
free_va.as_usize() as isize
}

这里flags的处理还是很不到位,需要后续增加.

学习内容

介绍地址空间和页表相关的内容.任务调度下次课说.

  1. 如何在一个主任务的基础上启用一个子任务,让他完成一系列的工作
  2. 启用一个单任务的基础上能够开两个任务,然后完成两个任务之间的通信

makefile的原理

调用是这样实现的:

首先是Makefile里的:

1
run: build justrun

它需要有buildjustrun这两个虚拟文件.

再去看build:

1
build: $(OUT_DIR) $(OUT_BIN)

需要的是OUT_DIROUT_BIN这两个实体文件.

创建它们两个的文件在scripts/make/build.mk:

1
2
3
4
5
$(OUT_DIR):
$(call run_cmd,mkdir,-p $@)

$(OUT_BIN): _cargo_build $(OUT_ELF)
$(call run_cmd,$(OBJCOPY),$(OUT_ELF) --strip-all -O binary $@)

这里调用的run_cmdscripts/make/utils.mk:

1
2
3
4
5
6
7
8
9
10
11
GREEN_C := \033[92;1m
CYAN_C := \033[96;1m
YELLOW_C := \033[93;1m
GRAY_C := \033[90m
WHITE_C := \033[37m
END_C := \033[0m

define run_cmd
@printf '$(WHITE_C)$(1)$(END_C) $(GRAY_C)$(2)$(END_C)\n'
@$(1) $(2)
endef

这里$(1)$(2)表示接受的是两个参数.

这个是两个操作,

  1. 通过颜色参数把要执行的命令输出出来(第一行)
  2. 第二行相当于执行接受的两个参数
  3. $@是代表这个虚拟文件本身

其实这一套操作下来就是创建这个OUT_DIR这个名字的文件夹.

Makefile中:

1
2
3
4
5
6
A ?= tour/u_1_0
APP ?= $(A)
... ...

# Paths
OUT_DIR ?= $(APP)

这时候把目光转回OUT_BIN.它在scripts/make/build.mk中:

1
2
$(OUT_BIN): _cargo_build $(OUT_ELF)
$(call run_cmd,$(OBJCOPY),$(OUT_ELF) --strip-all -O binary $@)

那么它的构建需要虚拟文件_cargo_build和实体文件OUT_ELF.

那么_cargo_build的功能也是先进行输出,随后调用cargo_build:

1
2
3
4
5
6
7
8
_cargo_build:
@printf " $(GREEN_C)Building$(END_C) App: $(APP_NAME), Arch: $(ARCH), Platform: $(PLATFORM_NAME), App type: $(APP_TYPE)\n"
ifeq ($(APP_TYPE), rust)
$(call cargo_build,$(APP),$(AX_FEAT) $(LIB_FEAT) $(APP_FEAT))
@cp $(rust_elf) $(OUT_ELF)
else ifeq ($(APP_TYPE), c)
$(call cargo_build,ulib/axlibc,$(AX_FEAT) $(LIB_FEAT))
endif

那么cargo_buildscripts/make/cargo.mk里:

1
2
3
define cargo_build
$(call run_cmd,cargo -C $(1) build,$(build_args) --features "$(strip $(2))")
endef

由于我们知道run_cmd是什么套路了,因此这边就是执行cargo来构建一个elf文件.

回到OUT_BIN这边,得到两个所需文件之后,通过OBJCOPY ?= rust-objcopy --binary-architecture=$(ARCH)(在Makefile)中定义,把elf多余的信息头去掉,只留下可执行二进制文件:

1
2
$(OUT_BIN): _cargo_build $(OUT_ELF)
$(call run_cmd,$(OBJCOPY),$(OUT_ELF) --strip-all -O binary $@)

最后回到justrun,它调用了run_qemu(在scripts/make/qemu.mk),

1
2
3
4
define run_qemu
@printf " $(CYAN_C)Running$(END_C) on qemu...\n"
$(call run_cmd,$(QEMU),$(qemu_args-y))
endef

这里调用了QEMU,是依赖于ARCH ?= riscv64(在Makefile):

1
QEMU := qemu-system-$(ARCH)

这里调用了qemu_args-y,同样是依赖于ARCH的这里不赘述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
qemu_args-x86_64 := \
-machine q35 \
-kernel $(OUT_ELF)

qemu_args-riscv64 := \
-machine virt \
-bios default \
-kernel $(OUT_BIN)

qemu_args-aarch64 := \
-cpu cortex-a72 \
-machine virt \
-kernel $(OUT_BIN)

qemu_args-y := -m 128M -smp $(SMP) $(qemu_args-$(ARCH))

ReadPFlash

本节目标:

  1. 引入页表管理组件,通过地址空间重映射,支持设备MMIO
  2. 地址空间概念,重映射的意义,页表机制

希望能从PFlash把应用的数据加载进来,以为运行后边的程序做基础.

实验没有paging时的情况

正常运行

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
OpenSBI v0.9
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
| |__| | |_) | __/ | | |____) | |_) || |_
\____/| .__/ \___|_| |_|_____/|____/_____|
| |
|_|

Platform Name : riscv-virtio,qemu
Platform Features : timer,mfdeleg
Platform HART Count : 1
Firmware Base : 0x80000000
Firmware Size : 100 KB
Runtime SBI Version : 0.2

Domain0 Name : root
Domain0 Boot HART : 0
Domain0 HARTs : 0*
Domain0 Region00 : 0x0000000080000000-0x000000008001ffff ()
Domain0 Region01 : 0x0000000000000000-0xffffffffffffffff (R,W,X)
Domain0 Next Address : 0x0000000080200000
Domain0 Next Arg1 : 0x0000000087000000
Domain0 Next Mode : S-mode
Domain0 SysReset : yes

Boot HART ID : 0
Boot HART Domain : root
Boot HART ISA : rv64imafdcsu
Boot HART Features : scounteren,mcounteren,time
Boot HART PMP Count : 16
Boot HART PMP Granularity : 4
Boot HART PMP Address Bits: 54
Boot HART MHPM Count : 0
Boot HART MHPM Count : 0
Boot HART MIDELEG : 0x0000000000000222
Boot HART MEDELEG : 0x000000000000b109

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

Try to access dev region [0xFFFFFFC022000000], got 0x646C6670
Got pflash magic: pfld

pfld在哪?

scripts/make/utils.mk中,在pflash中写入了pfld:

1
2
3
4
5
6
7
8
9
10
define mk_pflash
@RUSTFLAGS="" cargo build -p origin --target riscv64gc-unknown-none-elf --release
@rust-objcopy --binary-architecture=riscv64 --strip-all -O binary ./target/riscv64gc-unknown-none-elf/release/origin /tmp/origin.bin
@printf "pfld\00\00\00\01" > /tmp/prefix.bin
@printf "%08x" `stat -c "%s" /tmp/origin.bin` | xxd -r -ps > /tmp/size.bin
@cat /tmp/prefix.bin /tmp/size.bin > /tmp/head.bin
@dd if=/dev/zero of=./$(1) bs=1M count=32
@dd if=/tmp/head.bin of=./$(1) conv=notrunc
@dd if=/tmp/origin.bin of=./$(1) seek=16 obs=1 conv=notrunc
endef

那么这个在哪里调用呢?答案是没有调用.

我们是直接pull下来的,如果调用make pflash_img就会重新生成它.

scripts/make/qemu.mk里:

1
2
3
4
5
6
7
8
9
10
qemu_args-y := -m 128M -smp $(SMP) $(qemu_args-$(ARCH))

qemu_args-$(PFLASH) += \
-drive if=pflash,file=$(CURDIR)/$(PFLASH_IMG),format=raw,unit=1
... ...

define run_qemu
@printf " $(CYAN_C)Running$(END_C) on qemu...\n"
$(call run_cmd,$(QEMU),$(qemu_args-y))
endef

而在MakefilePFLASH被指定为y.这样就会在运行qemu的时候加上pflash.img这个文件.

没有指定paging的情况

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#![cfg_attr(feature = "axstd", no_std)]
#![cfg_attr(feature = "axstd", no_main)]

#[macro_use]
#[cfg(feature = "axstd")]
extern crate axstd as std;

use core::{mem, str};
use std::os::arceos::modules::axhal::mem::phys_to_virt;

/// Physical address for pflash#1
const PFLASH_START: usize = 0x2200_0000;

#[cfg_attr(feature = "axstd", no_mangle)]
fn main() {
// Makesure that we can access pflash region.
let va = phys_to_virt(PFLASH_START.into()).as_usize();
let ptr = va as *const u32;
unsafe {
println!("Try to access dev region [{:#X}], got {:#X}", va, *ptr);
let magic = mem::transmute::<u32, [u8; 4]>(*ptr);
println!("Got pflash magic: {}", str::from_utf8(&magic).unwrap());
}
}

这里需要看下一节关于PFlash的部分,因为这时候需要访问外设,在没有remap的时候是1G恒等映射,外设没有映射到地址空间中,因此报错.

一开始只映射了物理空间的0x8000_00000xC000_0000.

这里访问的物理地址是0x2200_0000,本来就不属于刚刚提到的物理空间,因此通过恒等映射平移也得不到恒等映射之后的虚拟地址.

产生的log:

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
OpenSBI v0.9
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
| |__| | |_) | __/ | | |____) | |_) || |_
\____/| .__/ \___|_| |_|_____/|____/_____|
| |
|_|

Platform Name : riscv-virtio,qemu
Platform Features : timer,mfdeleg
Platform HART Count : 1
Firmware Base : 0x80000000
Firmware Size : 100 KB
Runtime SBI Version : 0.2

Domain0 Name : root
Domain0 Boot HART : 0
Domain0 HARTs : 0*
Domain0 Region00 : 0x0000000080000000-0x000000008001ffff ()
Domain0 Region01 : 0x0000000000000000-0xffffffffffffffff (R,W,X)
Domain0 Next Address : 0x0000000080200000
Domain0 Next Arg1 : 0x0000000087000000
Domain0 Next Mode : S-mode
Domain0 SysReset : yes

Boot HART ID : 0
Boot HART Domain : root
Boot HART ISA : rv64imafdcsu
Boot HART Features : scounteren,mcounteren,time
Boot HART PMP Count : 16
Boot HART PMP Granularity : 4
Boot HART PMP Address Bits: 54
Boot HART MHPM Count : 0
Boot HART MHPM Count : 0
Boot HART MIDELEG : 0x0000000000000222
Boot HART MEDELEG : 0x000000000000b109

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

Try to access dev region [0xFFFFFFC022000000], got [ 2.002842 0 axruntime::lang_items:5] panicked at modules/axhal/src/arch/riscv/trap.rs:55:13:
Unhandled trap Exception(LoadFault) @ 0xffffffc080203fa6:
TrapFrame {
regs: GeneralRegisters {
ra: 0xffffffc08020376c,
sp: 0xffffffc0802499f0,
gp: 0x0,
tp: 0x0,
t0: 0x3f,
t1: 0x23,
t2: 0x5,
s0: 0xffffffc080249a80,
s1: 0xffffffc080249c60,
a0: 0xffffffc022000000,
a1: 0xffffffc080249a80,
a2: 0x0,
a3: 0xffffffc080203f9e,
a4: 0x2,
a5: 0xffffffc080202b76,
a6: 0xa,
a7: 0x1,
s2: 0x2,
s3: 0xffffffc080249bc0,
s4: 0xffffffc080249bf0,
s5: 0x38,
s6: 0xffffffc080205098,
s7: 0x2,
s8: 0x1,
s9: 0x24000,
s10: 0x2,
s11: 0xffffffc08026e000,
t3: 0x23,
t4: 0x3a,
t5: 0x5000,
t6: 0x55555555,
},
sepc: 0xffffffc080203fa6,
sstatus: 0x8000000000006100,
}

分支名称:tour_u_3_0_no_paging

MAP和REMAP

ArceOS Unikernel包括两阶段地址空间映射,
Boot阶段默认开启1G空间的恒等映射;
如果需要支持设备MMIO区间,通过指定一个feature - “paging”来实现重映射。

上一节说了启动之后需要remap,这样才可以实现重映射.

那么就需要打开paging.

初始化的线性页表

其实是创建了两个映射,相当于拿前一个恒等映射做了跳板,因为要求开启MMU之后仍然可以以原来的物理地址正常访问.

#TODO

后续创建多级页表

#TODO

PFlash

是一个模拟闪存磁盘.QEMU启动的时候会自动从内存中加载内容到固定的MMIO区域.

读操作是不需要驱动的,但是写是需要驱动的.

目前我们只需要读,只要加载成功即可.

物理地址空间

外设被映射到一个物理地址空间里边.

注意:linker_riscv64-qemu-virt.lds,段布局都是听的它的.

分页

类似于rCore的三级页表,我们实验中用的也是SV39.

分页阶段1-恒等映射+偏移

我们希望sbikernel都保存在高地址空间.

  1. 开局的时候把虚拟空间内的地址和物理空间内的地址完全对应上.
  2. 给指针寄存器pc栈寄存器sp加偏移,这里的偏移是0xffff_ffc0_0000_0000.

sbi还是放在0x8000_0000,kernel还是放在0x8020_0000.

那么在这个情况下其实已经是物理地址了,就是一个线性偏移的操作实现虚拟地址和物理地址的映射.

如果不需要访问物理设备,现在就可以完成了.

分页阶段2-重建映射

重映射的时候干脆在虚拟地址空间里把sbi去掉,因为不应该继续访问sbi了.

不同的数据段的权限不一样,比如READ,WRITE,EXECUTE不一样.比如代码段就只能读和运行不能写.在重建的时候就不需要给它这些权限.

这样设备的地址空间也可以被映射进来,权限粒度也更细.

这里似乎仍然是线性映射.

多任务

需求:启用多任务,开一个子任务,让子任务替主任务完成一些具体工作,然后回到主任务.

并发是多个任务同时在等待使用CPU而不是同时运行.并行是真的需要同时运行.

调度的一个很好的描述:一个是保存现场,一个是任务无感知.

实验多任务

make run A=tour/u_4_0

任务代码解析:

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
#![cfg_attr(feature = "axstd", no_std)]
#![cfg_attr(feature = "axstd", no_main)]

#[macro_use]
#[cfg(feature = "axstd")]
extern crate axstd as std;

use core::{mem, str};
use std::thread;
use std::os::arceos::modules::axhal::mem::phys_to_virt;

/// Physical address for pflash#1
const PFLASH_START: usize = 0x2200_0000;

#[cfg_attr(feature = "axstd", no_mangle)]
fn main() {
println!("Multi-task is starting ...");

let worker = thread::spawn(move || {
println!("Spawned-thread ...");

// Makesure that we can access pflash region.
let va = phys_to_virt(PFLASH_START.into()).as_usize();
let ptr = va as *const u32;
let magic = unsafe {
mem::transmute::<u32, [u8; 4]>(*ptr)
};
if let Ok(s) = str::from_utf8(&magic) {
println!("Got pflash magic: {s}");
0
} else {
-1
}
});

let ret = worker.join();
// Makesure that worker has finished its work.
assert_eq!(ret, Ok(0));

println!("Multi-task OK!");
}

和上一节的内容一样,同样是访问了我们预导入的pflash.img的前几个字符pfld.

只不过用了spawn的方法生成,并且用join的方法等待.

任务的数据结构

有一个关键点在于task_ext,是任务的拓展属性,是面向宏内核Hypervisor的关键.

通用调度框架

分层,并且实现同样的接口,这样就可以自己决定是什么样的调度机制.

系统默认内置任务

  • GC: 除main之外的任务(线程)退出后,由gc负责回收清理。
  • IDLE: 当其它所有任务都阻塞时,执行它。对某些arch,wait_for_irqs对应非忙等指令.比如等待中断什么的,而不是忙等,如果发生新的”event“(自己取的名)那么就会马上响应.

MsgQueue

本节目标:

  1. 任务的切换机制,协作式调度算法
  2. 同步的方式,Mutex的机制

运行实验

执行make run A=tour/u_5_0:

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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
OpenSBI v0.9
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
| |__| | |_) | __/ | | |____) | |_) || |_
\____/| .__/ \___|_| |_|_____/|____/_____|
| |
|_|

Platform Name : riscv-virtio,qemu
Platform Features : timer,mfdeleg
Platform HART Count : 1
Firmware Base : 0x80000000
Firmware Size : 100 KB
Runtime SBI Version : 0.2

Domain0 Name : root
Domain0 Boot HART : 0
Domain0 HARTs : 0*
Domain0 Region00 : 0x0000000080000000-0x000000008001ffff ()
Domain0 Region01 : 0x0000000000000000-0xffffffffffffffff (R,W,X)
Domain0 Next Address : 0x0000000080200000
Domain0 Next Arg1 : 0x0000000087000000
Domain0 Next Mode : S-mode
Domain0 SysReset : yes

Boot HART ID : 0
Boot HART Domain : root
Boot HART ISA : rv64imafdcsu
Boot HART Features : scounteren,mcounteren,time
Boot HART PMP Count : 16
Boot HART PMP Granularity : 4
Boot HART PMP Address Bits: 54
Boot HART MHPM Count : 0
Boot HART MHPM Count : 0
Boot HART MIDELEG : 0x0000000000000222
Boot HART MEDELEG : 0x000000000000b109

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

Multi-task is starting ...
Wait for workers to exit ...
worker1 ...
worker1 [0]
worker2 ...
worker2 [0]
worker2: nothing to do!
worker1 [1]
worker2 [1]
worker2: nothing to do!
worker1 [2]
worker2 [2]
worker2: nothing to do!
worker1 [3]
worker2 [3]
worker2: nothing to do!
worker1 [4]
worker2 [4]
worker2: nothing to do!
worker1 [5]
worker2 [5]
worker2: nothing to do!
worker1 [6]
worker2 [6]
worker2: nothing to do!
worker1 [7]
worker2 [7]
worker2: nothing to do!
worker1 [8]
worker2 [8]
worker2: nothing to do!
worker1 [9]
worker2 [9]
worker2: nothing to do!
worker1 [10]
worker2 [10]
worker2: nothing to do!
worker1 [11]
worker2 [11]
worker2: nothing to do!
worker1 [12]
worker2 [12]
worker2: nothing to do!
worker1 [13]
worker2 [13]
worker2: nothing to do!
worker1 [14]
worker2 [14]
worker2: nothing to do!
worker1 [15]
worker2 [15]
worker2: nothing to do!
worker1 [16]
worker2 [16]
worker2: nothing to do!
worker1 [17]
worker2 [17]
worker2: nothing to do!
worker1 [18]
worker2 [18]
worker2: nothing to do!
worker1 [19]
worker2 [19]
worker2: nothing to do!
worker1 [20]
worker2 [20]
worker2: nothing to do!
worker1 [21]
worker2 [21]
worker2: nothing to do!
worker1 [22]
worker2 [22]
worker2: nothing to do!
worker1 [23]
worker2 [23]
worker2: nothing to do!
worker1 [24]
worker2 [24]
worker2: nothing to do!
worker1 [25]
worker2 [25]
worker2: nothing to do!
worker1 [26]
worker2 [26]
worker2: nothing to do!
worker1 [27]
worker2 [27]
worker2: nothing to do!
worker1 [28]
worker2 [28]
worker2: nothing to do!
worker1 [29]
worker2 [29]
worker2: nothing to do!
worker1 [30]
worker2 [30]
worker2: nothing to do!
worker1 [31]
worker2 [31]
worker2: nothing to do!
worker1 [32]
worker2 [32]
worker2: nothing to do!
worker1 [33]
worker2 [33]
worker2: nothing to do!
worker1 [34]
worker2 [34]
worker2: nothing to do!
worker1 [35]
worker2 [35]
worker2: nothing to do!
worker1 [36]
worker2 [36]
worker2: nothing to do!
worker1 [37]
worker2 [37]
worker2: nothing to do!
worker1 [38]
worker2 [38]
worker2: nothing to do!
worker1 [39]
worker2 [39]
worker2: nothing to do!
worker1 [40]
worker2 [40]
worker2: nothing to do!
worker1 [41]
worker2 [41]
worker2: nothing to do!
worker1 [42]
worker2 [42]
worker2: nothing to do!
worker1 [43]
worker2 [43]
worker2: nothing to do!
worker1 [44]
worker2 [44]
worker2: nothing to do!
worker1 [45]
worker2 [45]
worker2: nothing to do!
worker1 [46]
worker2 [46]
worker2: nothing to do!
worker1 [47]
worker2 [47]
worker2: nothing to do!
worker1 [48]
worker2 [48]
worker2: nothing to do!
worker1 [49]
worker2 [49]
worker2: nothing to do!
worker1 [50]
worker2 [50]
worker2: nothing to do!
worker1 [51]
worker2 [51]
worker2: nothing to do!
worker1 [52]
worker2 [52]
worker2: nothing to do!
worker1 [53]
worker2 [53]
worker2: nothing to do!
worker1 [54]
worker2 [54]
worker2: nothing to do!
worker1 [55]
worker2 [55]
worker2: nothing to do!
worker1 [56]
worker2 [56]
worker2: nothing to do!
worker1 [57]
worker2 [57]
worker2: nothing to do!
worker1 [58]
worker2 [58]
worker2: nothing to do!
worker1 [59]
worker2 [59]
worker2: nothing to do!
worker1 [60]
worker2 [60]
worker2: nothing to do!
worker1 [61]
worker2 [61]
worker2: nothing to do!
worker1 [62]
worker2 [62]
worker2: nothing to do!
worker1 [63]
worker2 [63]
worker2: nothing to do!
worker1 [64]
worker2 [64]
worker2 ok!
worker1 ok!
Multi-task OK!

分析代码,worker1是尝试获取Arc中的这个双端队列,然后尝试在队列的最后放东西.

由于是协作式调度,worker1每次放入的之后都会yield,因此worker2就会接手,然后尝试把队列里所有的内容都打出来,如果队列为空就报告worker2: nothing to do!,然后再由worker1接手CPU.

协作式调度

rCore不同,现在使用的是List而不是一个数组,原理上就不设置任务的个数了.

互斥锁和自旋锁

自旋锁可能是我们脑子中的那个锁,每次访问资源需要访问这个锁,如果没办法访问那你这个任务要处理这种情况.

互斥锁则是自己加了一个等待队列,如果有任务在等待这个资源,那么这个任务被加入等待队列之后不会参与调度,这样就节省了很多任务切换时的资源.

bump内存分配算法

#TODO

挑战性作业

initialize global allocator at: [0xffffffc08026f000, 0xffffffc088000000)

5376*2

align是8的是有关于Vec的内存的分配

131,665,920

1048576

524288+8000

#TODO

内容

虚拟机运行的实验内核是第一周的u_3_0:从pflash设备读出数据,验证开头部分。

有两种处理方式:

  1. 模拟模式 - 为虚拟机模拟一个pflash,以file1为后备文件。当Guest读该设备时,提供file1文件的内容。
  2. 透传模式 - 直接把宿主物理机(即qemu)的pflash透传给虚拟机。

优劣势:模拟模式可为不同虚拟机提供不同的pflash内容,但效率低;透传模式效率高,但是捆绑了设备。

实验

课后作业