0%

学习内容

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

  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

stage3总结

u1.0

很简单,定位到 axstd 里修改 println 的输出,前后加上转义字符就好了

u2.0

找到 axstd 里,把 collection 替换成自己的 collection 包就可以,在自己的 collection 包实现 HashMap 结构体,只需要简单实现接口,覆盖测试用例即可。另外自己在替换 axstd 的 collection 的时候,遇到的问题是模块的导出关系没弄清导致的刚开始测试用例找不到我的代码.

u3.0

做的过程中,先按照自己的想法做了一下,发现编译不过哈哈,然后看群里消息,学到了 const generic 这个知识点。然后顺便看到了 blogOS 对于 bump allocator 的介绍,就拿过来参考了一下,这个 bump allocator 确实简单的不可思议。 最后写完之后还是不过,打了日志才发现是自己初始化漏了 b_pos 和 p_pos 的初始化.

h1.0 & h2.0

实验内容还是很简单的,但是虚拟化这块之前没有了解过,但是比较感兴趣。中间一周把宏内核实验翘掉读了虚拟化原理那本书。在听老师讲解过程中感觉,对一个不熟悉的东西,尽量简化,然后做出一个最小 work 的样板,然后不断迭代添加复杂功能,是非常好的学习方式。

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结合的异步运行时的实现差异

第一阶段练习总结

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

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

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

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

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

一阶段学习体会

早就听说过大名鼎鼎的 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 学习体验不错,希望接下来的三阶段能学到一些有意思、更贴近实际场景的东西。

开源操作系统训练营第四阶段总结-改进Starry文档

学习心得

赶着入职前进入到了第四阶段,留给我的时间不多,都不到一周,但是陈老师没有放弃我,仍然耐心的给与了我如何开始学习Starry的指导,我非常感动。四阶段项目一群中的群友也给与了我很大的帮助,让我感受到了开源社区的魅力。

在四阶段学习中,我收获良多,由于不是计算机专业的学生,有许许多多的第一次:

  • 第一次学习操作系统:还是 RISCV 指令集的,在MacOS上跑代码没少折腾。很多内容让我回想起了本科学习计算机组成原理的时光(还是很不相同)。
  • 第一次使用 docker:本来倔强的我想在 MacOS 上坚持到最后,没想到还是在第三阶段涉及到交叉编译的部分败下阵来,配置 docker 环境的时候可能由于网络问题也没少折腾。
  • 第一次提交 PR:之前使用GitHub也只是上传论文的代码,单打独斗。在刚进入第四阶段的时候,陈老师很耐心的用一个修改 README.md 的机会指导我怎么提交 PR。第一次了解了团队合作乐趣。
  • 第一次了解 GitHub 工作流:前三阶段大概知道有这么一个东西在给我们提交的文档打分,在第三阶段中 musl 被官方屏蔽了微软的访问之后一直访问超时,我才开始去理解其中的含义。

闲聊了这么多,还是因为我入门尚浅,翻来覆去看代码也只知道还要学习的有很多,难以下手,留给我的时间又只有一周。还好陈老师给我指了一条明路——完善文档,接下来我就介绍一下这一周我的工作以及将来的计划。

本周工作

我的工作主要是分析starry-next,并阅读 starry-next tutorial book,从各个层面改进starry-next tutorial book,帮助自己和其他初学者更好地学习操作系统开发。

在开始完善文档之前,我阅读了郑友捷同学的综合论文训练——《组件化操作系统 ArceOS 的异构实践》,宏观的了解了 StarryOS 的设计理念和目标。在完善文档的过程中,郑友捷同学也给予了我很多建议和指导,在此表示感谢。

我的工作具体如下:

  1. PR#23 修复了指导书不能切换主题的bug, 可以切换到深色模式,便于完善文档时的调试(一边黑一边亮容易被吸引注意力)。

  2. PR#24 在阅读完郑同学的论文之后,修改了文档的欢迎页,说明了 StarryOS 和 ArceOS 之间的关系和差别。因为从第三阶段到第四阶段,作为初学者的我一开始是一头雾水,不知道 Starry 要实现一个什么样的目标,所以我也在欢迎页添加了设计目标的说明(仍需完善)。在我与郑同学的交流中感觉到 Starry 的目标可能是:

    • 在 crate 层中希望能够开发一些独立的组件,一部分就像 ArceOS 一样,能够被任意类型内核复用, 一部分则是能够被其他的宏内核使用。

    • 使 Starry 能够兼容 Linux 程序,即提供 POSIX API 接口。

    • 完成作为一个宏内核该有的功能,包括进程管理、信号处理等。

      作为一个第一次接触 Starry 的开发者,我觉得可能还需要一个更宏大的目标,或者更明确的商用的可能性来吸引更多的人加入我们的开源社区,并做出贡献。

  3. pr#25 在进入到第四阶段的时候,配置完环境后,只是按照 README.md 中给的指令运行了一遍,但是依然没有理解具体 Starry 究竟做了些什么,他的目标是什么。而且我在创建镜像的过程中遇到了一些问题——loop设备满了导致无法加载,我也是通过读了一遍Makefile的流程才定位到这个错误。因而我写了一个一个案例快速上手 Starry,介绍了这些指令内部的一些细节,帮助初学者快速理解。PS: 今天我再读的时候发现了其中的一些错误(镜像文件应该是给QEMU加载的,不是ArceOS的文件系统),而且没有介绍案例 nimbos 的主要作用,可能对于学习过操作系统的同学来说,不用说也知道是一个测试操作系统功能的测试集,从一个门外汉的角度来看,他就是测试了几个testcase而已。我会在后续的工作中对文档进行修正。

  4. pr#26 可能对于大佬来说,各种 git 指令都已经理解透彻,烂熟于心,但对于初学者来说,只会用一个

    1
    2
    3
    git add .
    git commit "update XXX"
    git push

    在这之前我都没有创建过分支来提交PR,导致了一些混乱,因此在附录中添加了一个创建分支提交PR的标准流程。此外对于理解StarryOS究竟在做什么,理解他的工作流很有必要,因此我也在附录中添加了对于工作流相关的说明。

总结和将来的工作

由于时间不多,我的水平有限,相比其他同学对社区的贡献,我的工作可能微不足道。如果社区的大佬们不嫌弃,入职工作之后我也愿意继续帮助完成文档(主要因为这个项目里面用到的Rust语法很全面,我想要学习rust, 还包含了很多汇编和c,将来也能对用rust驱动硬件做为一个参考)。

可能有一部分同学和我一样,在阶段一到阶段三主要注重于做题(阅读测例->知道预期的结果是什么->查看相关的接口和需要用到的函数->实现功能),完成任务即可。没有特别理解整体的设计和原理,因而到了阶段四之后没有了题目之后感到迷茫。

因而为了帮助第三阶段的同学能够丝滑的进入到第四阶段,我觉得当务之急是需要完善ArceOS的文档(我第三阶段完全就是参考PPT完成的,对于很多细节没有掌握)我接下来的工作就是理解ArceOS的细节,说明Starry如何使用了ArceOS的接口和模块。然后首先实现文档整体框架的从无到有,最后在掌握了细节和整体设计思路之后再完善指导书并修正其中的错误。也希望有大佬能够一起加入到完善文档队伍中,一起交流。