0%

stage4总结

stage 4 算是很扎实的一个阶段。这个阶段从头开始学习了我原来不怎么熟悉的异步运行时。整体上来说算是比较系统的学习了如何构建一个异步运行时,同时也阅读了不少tokio这种生产环境级别的异步运行时代码,受益良多。
本阶段还学习了一些关于 uring io 的知识,同时也顺便比较系统的梳理了 linux io 这块的知识点。通过每周一次的交流活动中能比较有效的调整自己的学习方向,同时也能补充很多有用的信息。很多同学水平很高,在交流过程中深感差距,还需要不断学习。

stage4 工作进度

本周工作(第一周):

阅读以下两个链接中文档,比较系统的了解了 rust 的异步原理

####下周安排(第二周):

  • 阅读 tokio 源码,学习生产环境中的异步运行时实现

本周工作(第二周):

由于本项目最终要实现一个基于 uring io 的异步运行时,于是决定从 io 的角度切入 tokio 的源码阅读。在阅读过程中发现 tokio 的文件 io 部分都是转发给某个独立线程,是基于阻塞式的操作。为了对比文件 io ,还阅读了部分 tcp 相关的源码,证实了的确网络 io 是使用了基于 epoll 的 mio 来做管理。而且本周对 uring io 有一个粗略地了解,目前看来 uring io 在文件 io 方面可能优势会更明显。 网络 io 这块相较于 epoll 优势没那么大,那么接下来第三周可能要优先实现基于 uring io 的文件 io 异步运行时的相关工作。

此外本周还阅读了以下资料

下周安排(第三周):

编写一个简易的基于 uring io 的文件 io 的异步运行时。

本周工作(第三周):

本周首先调研了一下在 smol 中实现基于 uring io 的可能性。首先 smol 社区中已经有一个 PR
async-io PR:Integrate io_uring in the Reactor
● 简要介绍了实现思路,
● 作者说要等等 polling Expose raw handles for the Poller 这个 issuing合并.
大概粗略浏览了一下,但是由于对 uring io 不太熟悉用法,还没有看懂,暂时搁置。

然后继续阅读了 uring io 的相关资料,包括

最后是实现我自己的uring io异步运行时
我的async-fs-uring,这里实现了一个建议的基于 reactor 模式异步运行时,并实现了基于uring io的文件读。主要思想就是把 io 委托给 reactor 去提交,然后 reactor 不断轮询,如果有 io 完成了,就返回给对应的异步任务。实现过程中比较困难的点就是buf 管理,需要保证 buf 在异步读过程中一直有效。我这里做法是直接把 buf 的所有权移交给 UringReadFuture.这只是一个权宜之计,因为我这里实现的比较简单,在异步读进行过程中 UringReadFuture不会被 drop 掉。实际上后来也阅读了 tokio-uring 的相关设计文档,也了解到了一些更合理的设计方案,但是还没有时间来实现。

未来计划:通过实现一个建议的基于 uring io 的异步运行时让我对 uring io 有了基本的了解。后续可能会进一步了解生产环境级的基于 uring io 的异步运行时的实现以及与传统阻塞式和epoll结合的异步运行时的实现差异

阶段一-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

下一步

第一阶段练习总结

作为一名Cpper,我之前对Rust的了解可以说是毫无了解,这次受到朋友的邀请,第一次接触Rust,刚开始以为就是像C一样的过程语言。然而,当我真正开始深入学习和使用Rust时,我发现它远比我预想的要有趣和富有挑战性。特别是在进行Rustlings练习的过程中,我收获颇丰,也深刻体会到了Rust语言的强大和魅力。

Rustlings是一个设计精良的Rust语言练习项目,通过一系列由简到难的练习题,帮助开发者逐步掌握Rust的语法和特性。在练习的过程中,我遇到了许多看似简单但实则深奥的问题,这些问题让我不断思考和探索,也让我对Rust有了更深入的理解。

通过Rustlings的练习,我只能感叹面向编译器编程的魅力。

此外,Rustlings的练习还让我认识到了自己在编程思维方面的不足总的来说,Rustlings练习是我学习Rust过程中的一个重要环节。它不仅让我掌握了Rust的基本语法和特性,还锻炼了我的编程思维和解决问题的能力。我相信,在未来的学习和工作中,我会继续利用Rustlings等资源来深化对Rust的理解和应用,并不断提升自己的编程水平。同时,我也期待在未来的项目中能够充分发挥Rust的优势,写出更加健壮、高效的程序。

一阶段学习体会

早就听说过大名鼎鼎的 rustlings, 这次借着 oscamp 的机会来做一做。做的过程中发现 oscamp 的 rustlings 似乎比官方的 rustlings 加了很多私货,难度大了不少,
但是也的确把编写 很多有用的 rust 特性 和 os 过程中会用得到的一些 rust 的 feature 都涵盖了。在做的过程中,也学习到了很多以前自己没有注意或者使用过的 rust 特性。
更重要的是,通过一些题目的学习,能摆脱原来写 c++ 的思维,写出一些更 rust 风格的代码,比如返回值多用 Option 和 Result,更多的使用 match 和 if let, 而不是用原来 c++/c 风格的错误判断,
以及更多的用迭代器等方法。

一阶段学完之后,尤其是经历了大量和编译器的斗智斗勇之后,我对 rust 的理解加深很多,也对 rust 带来的相比 c++ 的很多优势有了更深的体会。相信 rust 和 c++ 的学习会互相促进。

二阶段学习体会

本科期间零零散散一致有学过 rust 和 rcore 的一些东西,由于各种各样的事情没能坚持下来。
读硕士之后刚好又系统学习了一下 rust ,正巧有同学来约我参加 oscamp , 于是就来参加。

如今再做 rcore 感觉要比本科期间轻松了很多。在游刃有余之后,能从 rcore 中汲取的知识也就更多了。
文件系统的组织方式、并发的实现和管理等,这些本科期间令我头晕转向的知识点,现在都能比较轻松地理解。也能从中窥探出一个真实生产环境下的操作系统是如何设计的。

本次实验中,前四个实验都比较轻松,思路清晰地拿下了。最让我头晕转向的是第五个实验,也就是死锁检测的实现。
刚开始被银行家算法迷惑,感觉很难实现。后来又做了尝试做了环路检测来检测死锁。但是做的过程中发现环路检测可能比较难处理信号量。于是又回过头来思考银行家算法。
结合前面尝试做环路检测的过程,觉得只要实现一套类似银行家算法的检测机制就好了。于是把代码全部推翻,从头来过很快就拿下了。

这一次 rcore 学习体验不错,希望接下来的三阶段能学到一些有意思、更贴近实际场景的东西。

2024秋冬季OS2EDU第三阶段总结报告

概述

在2024秋冬季开源操作系统训练营的第三阶段中,我深入研究并实现了一个高效的内存分配器,该分配器结合了Slab分配器和TLSF(Two-Level Segregated Fit)分配器的优势。本报告将详细分析每个链接中的源码,并探讨我实现的新调度器的优势。

Slab分配器

源码分析

Slab分配器的核心思想是将内存分割成固定大小的块,并将这些块组织成一个或多个列表,以便于快速分配和释放。每个Slab结构体维护了一个特定大小的块列表。

1
2
3
4
pub struct Slab<const BLK_SIZE: usize> {
free_block_list: FreeBlockList<BLK_SIZE>,
total_blocks: usize,
}

Slab结构体包含一个free_block_list,这是一个双向链表,用于存储可用的块,以及total_blocks,记录总块数。Slab的实现提供了newgrowallocatedeallocate等方法,用于初始化、扩展、分配和释放内存块。

优势分析

  • 固定大小的块:由于每个Slab只管理固定大小的块,因此分配和释放操作可以非常快速。
  • 缓存局部性Slab分配器倾向于将最近释放的块重新分配给请求,这有助于提高缓存局部性。
  • 减少外部碎片:通过固定大小的块分配,Slab分配器可以减少外部碎片。

TLSF分配器

源码分析

TLSF分配器实现了一种动态内存分配算法,它通过维护多个空闲块列表来优化内存分配。TLSF分配器的关键特点是其二级索引结构,它允许快速找到合适的空闲块。

1
2
3
4
5
pub struct TlsfByteAllocator {
inner: Tlsf<'static, u32, u32, 16, 2>,
total_bytes: usize,
used_bytes: usize,
}

TlsfByteAllocator结构体封装了rlsf::Tlsf,并提供了initadd_memoryallocdealloc等方法,用于初始化、添加内存、分配和释放内存。

优势分析

  • 高内存利用率:TLSF通过低粒度(SLLEN为2)的索引结构,可以更有效地利用内存,减少内部碎片。
  • 快速分配和释放:TLSF的二级索引结构允许快速定位空闲块,从而加快分配和释放速度。

新调度器实现

设计理念

在我的新调度器设计中,我结合了SlabTLSF的优点,将较小的块分配任务交给Slab分配器,而将较大的块分配任务交给TLSF分配器。这种设计允许我复用已经分配的指定大小的块,从而提高内存分配的效率。

优势分析

  • 灵活性:新调度器可以根据请求的大小灵活选择使用SlabTLSF,这使得它可以适应不同的内存分配需求。
  • 内存利用率:通过将较大的块分配给TLSF,我可以利用其高内存利用率的优势,同时Slab分配器可以快速处理小规模的分配请求。
  • 性能优化:新调度器通过复用已经分配的块,减少了分配和释放操作的开销,提高了整体性能。

学习感想

通过参与2024秋冬季开源操作系统训练营,我对内存管理有了更深入的理解。实现SlabTLSF分配器的过程让我体会到了操作系统中内存管理的复杂性和重要性。我学到了如何设计和实现高效的内存分配策略,这对于我未来的职业生涯和学术研究都是非常宝贵的经验。此外,我也认识到了团队合作的重要性,因为在实现过程中,我与队友们进行了深入的讨论和协作,共同解决了遇到的技术难题。总的来说,这次训练营不仅提升了我的技术能力,也锻炼了我的团队合作能力。

结论

通过实现基于SlabTLSF的内存分配器,我成功地提高了内存分配的效率和内存利用率。新调度器的设计不仅灵活,而且能够根据请求的大小选择合适的分配器,这在操作系统的内存管理中是非常重要的。我的实现展示了如何在保证性能的同时,优化内存使用,这对于任何需要高效内存管理的系统都是至关重要的。

lab1: print_with_color

  • 本实验比较较为简单, 修改 arceos/exercises/print_with_color/src/main.rs 文件, 为 println 添加 ANSI 转义码即可. ANSI 转义码用于在终端中控制光标位置、文本颜色、文本样式等, 其以 ESC (Escape) 字符开头.
  • 初次在 Rust 中使用 \033[34m[WithColor]: Hello, Arceos!\033[0m 形式的 ANSI 转义码, 发现其不奏效; 但是在 C/C++ 中却是正常. 查阅资料后得知, \033 是 C 语言的传统写法, 在 C/C++ 中使用较多. 在 Rust 中, 应该使用 \x1b, 即 \x1b[34m[WithColor]: Hello, Arceos!\x1b[0m. 本是上来说, \033\x1b 是同一内容的不同表示.

lab2: support_hashmap

  • 本实验直接使用 hashbrown 来支持 hashmap. 在 arceos/ulib/axstd/Cargo.toml 中添加 hashbrown 依赖, 在 axstd 中添加 collections 模块, 并在 arceos/ulib/axstd/Cargo.toml 中声明 pub mod collections;. 在 collections 模块中, 使用 pub use alloc::collections::*;, 将 alloc::collections 的内容(比如 BTreeMap)导入当前的模块, 使用 pub use hashbrown::*; 将第三方库的 HashMap 等导入当前模块, 此时可以通过 axstd::collections 使用 HashMap(hashbrown 提供)、BTreeMap(alloc::collections 提供).
  • 还有一点要注意, 应该在启用 alloc feature 时才会声明并挂载 collections (#[cfg(feature = "alloc")]), 若不加以控制, print_with_color 则会出现报错. 原因在于, 若 collections 的编译不被 alloc feature 控制, 其中的 BTreeMap 等会依赖全局内存分配器, 但是因没有启用 alloc feature, 故而找不到全局内存分配器. 应该启用 alloc feature 时才会编译 collections.

lab3: alt_alloc

  • 此实验是实现简单的内存分配器, 在 arceos/modules/alt_axalloc/src/lib.rs 中具体实现.
  • arceos/exercises/alt_alloc/Cargo.toml 声明依赖 axstd: workspace = true 表明 axstd 是工作区成员; features = ["alt_alloc"] 使 axstd 开启 alt_alloc 特性. 当 axstdalt_alloc 特性被启用时, 其会继续将此传递下去, 还会根据 [features]alt_alloc = ["arceos_api/alt_alloc", "axfeat/alt_alloc"] 启用 arceos_api 中的 alt_alloc 特性和 axfeat 中的 alt_alloc 特性.

lab4: ramfs_rename

  • 实验要求在 arceos/axfs_ramfs 中实现 rename 操作, 但 arceos 原本依赖远程仓库的版本, 故需要调整.
  • 可以采用 patch 方式让工程临时使用 arceos/axfs_ramfs 的本地组件仓库. 为方便起见, 直接在 arceos/exercises/ramfs_rename/Cargo.toml 中将 axfs_ramfs = { version = "0.1", optional = true } 中的 version = "0.1" 替换为 workspace = true, 多次运行发现还是提示 Operation not supported. 然后对代码进行详细分析, 发现应用 ramfs_rename 会依赖 axfs(间接) 和 axfs_ramfs, axfs 还会依赖 axfs_ramfs, 此时 ramfs_rename 依赖的 axfs_ramfsaxfs 依赖的不同. 故还需将修改 arceos/modules/axfs/Cargo.toml, 替换 version = "0.1"workspace = true.
  • 具体实现 rename 功能, 只需修改 arceos/axfs_ramfs/src/dir.rs. 阅读测试用例后发现, 文件重命名只在同一个目录下操作, 故在具体实现中只考虑此情况, 并未做特殊处理.

lab5: sys_map

  • 初次运行 sys_map 时, 发现运行结果与课件中展示的内容有很大出入, 课件显示 not implement, 而本地运行则会显示 Cannot load app!. 经排查, 发现未设置 musl-libc, 导致无法编译出 mapfile 可执行文件.
  • 实现 sys_map 时针对测试用例做了简化处理, 未考虑复杂的情况. 实现 sys_mmap 系统调用主要有 5 个步骤: 获取当前进程的地址空间, 并在此查找空闲内存区域, 再准备参数执行内存映射, 再读取文件内容到缓冲区 buf 并将其写入映射的内存区域, 最后返回映射起始位置. 实现过程中, 务必查找空闲的内存区域再映射, 否则会出现 Mapping error: AlreadyExists.

lab6: simple_hv

  • 运行此实验代码, 发现QEMU会卡死. 经过仔细排查, 是由于 payload 构建失败导致的. 在外层 Makefile 中定义的 RUSTFLAGS 会被默认传递给 make -C ./payload 递归调用, 这会干扰编译 payload (尤其是 skernel). 故在 payloadMakefile 中添加 unexport RUSTFLAGS 来避免 RUSTFLAGS 变量的传播.
  • 正常运行后, 根据 panic 指示修改实现, 使测例通过即可. 在异常处理分支中, panic 改为 ax_println, 还要调整客户机的 spec, 即 ctx.guest_regs.sepc += 4;. 注意, 还要设置 A0 寄存器的值为 0x6688, A1 寄存器的值为 0x1234.

对 rcore 早有耳闻,曾报名了 23 年秋冬的训练营,但是当前初学 rust、初学 riscv 等缺少了一种毅力,25 年秋冬重新报名参加,有所学、有所获。

虽然我是虚拟化方向的研究生,但是对于操作系统中的很多概念等停留在理论阶段,尤其在接触到了 rcore 之后,发现本科学的操作系统只是浮在表面的那一层,如今接触 rcore,做了一些实验,进一步加深了对操作系统的敬仰。

虽然走到了这里,但是对于 rcore 的很多非实验的部分的理解还不够,还需要好好地梳理,这里对 stage-3 arceos 实验进行总结。

前言

实验涉及 unikernel、宏内核、hypervisor,但是明显感觉到 stage3 实验更多得是对组件化操作系统的上手。

其中很多实验并不像 rcore 中的那样复杂,比如 print_color、simple_hv,可能一点点代码就能通过测例,更重要的是理解其组件化的思想,可以快速构建异构内核的能力,unikernel、宏内核、hypervisor,把他们的共同点抽离出来,封装成一个个组件,可以实现快速的内核定制。

下面说一下遇到的一些问题:

hashmap

起初,我通过 hashbrown 引入第三方的实现,发现并未涉及视频中提到的随机数,通过查找资料以及翻阅 std 下的 hash_map 实现。最终决定使用 hashbrown 下的 HashMap::with_hasher,并选择 foldhash 下的 FixedState::with_seed 提供一个哈希计算,其中 seed 通过 axhal 下的 random 生成。

ramfs_rename

最开始,发现怎么改动都不生效,arceos 的实验使用了 workspace,这个在之前我从未接触,最终发现是 ramfs_rename 对应的这个内核的 Cargo.toml 文件依赖的不是 workspace 中的 axfs_ramfs 导致。
除此之外,还有修改 axfs 下的 Cargo.toml。

sys_mmap

从文件中映射到内存中,分三步走,在虚拟地址空间找一个空闲虚拟页,借助 uspace.find_free_area,借助 uspace.map_alloc 进行预填充的分配,即完成虚实映射,然后将文件读到改页。
如果使用刚刚分配的虚拟地址可以读,但是会出现异常,比如权限不足,需要转到内核虚拟地址进行读写,这里通过 phys_to_virt 将物理地址转到内核到虚拟地址,随后通过 sys_read 这个 api 完成文件的读取。

其他

自我感觉对于 rcore 的掌握还不到家,目前仅仅是通过了测例而已。

rcore 的文档和代码常读常新,能加深操作系统的理解。