Skip to content

Commit

Permalink
Merge pull request #46 from ToolmanP/main
Browse files Browse the repository at this point in the history
docs: enhance docs
  • Loading branch information
ToolmanP authored Oct 21, 2024
2 parents 55c71c1 + a398b69 commit c339f0d
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 37 deletions.
62 changes: 60 additions & 2 deletions Pages/Lab3/RTFSC.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ end
```

我们在Lab2中主要完成mm_init以及内存管理器与vmspace和pmo的互联,现在我们再从第一个线程创建的数据流来梳理并分析
chcore微内核的资源管理模式
我们在`Lab2`中主要完成mm_init以及内存管理器与vmspace和pmo的互联,现在我们再从第一个线程创建的数据流来梳理并分析
Chcore微内核的资源管理模式

### 内核对象管理

Expand All @@ -72,3 +72,61 @@ Chcore通过能力组机制管理所有的系统资源,能力组本身只是
> [!NOTE]
> 你可以根据上述的描述来梳理根进程创建以及普通进程创建的异同,最后梳理出创建进程的标准模式。
### 用户态构建
我们在`Lab1`的代码导读阶段说明了`kernel`目录下的代码是如何被链接成内核镜像的,我们在内核镜像链接中引入了`procmgr`这个预先构建的二进制文件。在`Lab3`中,我们引入了用户态的代码构建,所以我们将`procmgr`的依赖改为使用用户态的代码生成。下图为具体的构建规则图。
```mermaid
flowchart LR
topcmake["CMakeLists.txt"]
chcorelibc["chcore-libc"]
libcso["libc.so"]
procmgr["procmgr"]
ramdisk["ramdisk"]
ramdisk_cpio["ramdisk.cpio"]
tmpfs["ramdisk/tmpfs.srv"]
procmgr_tool["procmgr_tool"]
kernel["kernel"]
kernel_img["kernel.img"]
subgraph libc
chcorelibc-->|autotools|libcso
end
subgraph system_services
ramdisk-->|cpio|ramdisk_cpio
ramdisk_cpio-->tmpfs
tmpfs-->procmgr
libcso-->procmgr
procmgr-->procmgr_tool
procmgr_tool-->procmgr
end
topcmake-->system_services
topcmake-->libc
procmgr-->kernel_img
kernel-->kernel_img
```
`procmgr`是一个自包含的`ELF`程序,其代码在`procmgr`中列出,其主要包含一个`ELF`执行器以及作为Chcore微内核的`init`程序启动,其构建主要依赖于`fsm.srv`以及`tmpfs.srv`,其中`fsm.srv`为文件系统管理器其扮演的是虚拟文件系统的角色用于桥接不同挂载点上的文件系统的实现,而`tmpfs.srv`则是`Chcore`的根文件系统其由`ramdisk`下面的所有文件以及构建好`libc.so`所打包好的`ramdisk.cpio`构成。当构建完`tmpfs.srv`后其会跟`libc.so`进行动态链接,最终`tmpfs.srv`以及`fsm.srv`会以incbin脚本的形式以二进制的方式被连接至`procmgr`的最后。在构建`procmgr`的最后一步,`cmake`会调用`read_procmgr_elf_tool`将`procmgr`这个`ELF`文件的缩略信息粘贴至`procmgr`之前。此后`procmgr`也会以二进制的方式进一步嵌套进入内核镜像之后,最终会在`create_root_thread`的阶段通过其`elf`符号得以加载。 最终,Chcore的Kernel镜像的拓扑结构如下

```mermaid
flowchart LR
kernel_img("kernel.img")
kernel_objects("kernel/*.o")
procmgr("procmgr")
chcore_libc("libc.so")
ramdisk("ramdisk")
ramdisk_cpio("ramdisk.cpio")
tmpfs("tmpfs.srv")
fsm("fsm.srv")
kernel_img-->kernel_objects
kernel_img-->procmgr
procmgr-->fsm
procmgr-->tmpfs
tmpfs-->ramdisk_cpio
ramdisk_cpio-->ramdisk
ramdisk_cpio-->chcore_libc
```
20 changes: 8 additions & 12 deletions Pages/Lab4.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
# Lab 4:多核调度与IPC

在本实验中,ChCore将支持在多核处理器上启动(第一部分);实现多核调度器以调度执行多个线程(第二部分);最后实现进程间通信IPC(第三部分)。注释`/* LAB 4 TODO BEGIN (exercise #) */``/* LAB 4 TODO END (exercise #) */`之间代表需要填空的代码部分。
在本实验中,我们将逐步实现ChCore的多核支持以及微内核系统的核心:进程间通信,本Lab包含四个部分:]

## Preparation
1. [多核启动支持](./Lab4/multicore.html): 使ChCore通过树莓派厂商所提供的固件唤醒多核执行
2. [多核调度](./Lab4/scheduler.html): 使ChCore实现在多核上进行[round-robin](https://en.wikipedia.org/wiki/Round-robin_scheduling)调度。
3. [IPC](./Lab4/IPC.html): 使ChCore支持进程间通信
4. [IPC调优](./Lab4/performance.html): 为ChCore的IPC针对测试的特点进行调优。

实验 4 与实验 3相同,需要在根目录下拉取 `musl-libc` 代码。

>```bash
> git submodule update --init --recursive
>```
使用 `make build` 检查是否能够正确项目编译。
> [!WARNING]
> 请确保成功拉取`musl-libc`代码后再尝试进行编译。若未成功拉取`musl-libc`就进行编译将会自动创建`musl-libc`文件夹,这可能导致无法成功拉取`musl-libc`代码,也无法编译成功。出现此种情况可以尝试将`user/chcore-libc/musl-libc`文件夹删除,并运行以上指令重新拉取代码。
跟先前的Lab相同,本实验代码包含了基础的 ChCore 操作系统镜像,除了练习题相关部分的源码以外(指明需要阅读的代码),其余部分通过二进制格式提供。
在正确完成本实验的练习题之后,你可以在树莓派3B+QEMU或开发板上进入 ChCore shell。
注释`/* LAB 4 TODO BEGIN (exercise #) */``/* LAB 4 TODO END (exercise #) */`之间代表需要填空的代码部分。
9 changes: 5 additions & 4 deletions Pages/Lab4/IPC.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

在本部分,我们将实现ChCore的进程间通信,从而允许跨地址空间的两个进程可以使用IPC进行信息交换。

## ChCore进程间通讯概览
## 进程间通讯概览
![](./assets/IPC-overview.png)

ChCore的IPC接口不是传统的send/recv接口。其更像客户端/服务器模型,其中IPC请求接收者是服务器,而IPC请求发送者是客户端。 服务器进程中包含三类线程:
Expand All @@ -17,10 +17,10 @@ ChCore的IPC接口不是传统的send/recv接口。其更像客户端/服务器
> [!IMPORTANT] 注意
> 注册回调线程和服务线程都不再拥有调度上下文(Scheduling Context),也即不会主动被调度器调度到。其在客户端申请建立IPC连接或者发起IPC请求的时候才会被调度执行。为了实现该功能,这两种类型的线程会继承IPC客户端线程的调度上下文(即调度时间片budget),从而能被调度器正确地调度。
## ChCore IPC具体流程
## 具体流程
为了实现ChCore IPC的功能,首先需要在Client与Server端创建起一个一对一的IPC Connection。该Connection保存了IPC Server的服务线程(即上图中IPC handler Thread)、Client与Server的共享内存(用于存放IPC通信的内容)。同一时刻,一个Connection只能有一个Client接入,并使用该Connection切换到Server的处理流程。ChCore提供了一系列机制,用于创建Connection以及创建每个Connection对应的服务线程。下面将以具体的IPC注册到调用的流程,详细介绍ChCore的IPC机制:

1. IPC服务器的主线程调用`ipc_register_server`user/chcore-libc/musl-libc/src/chcore-port/ipc.c中来声明自己为IPC的服务器端。
1. IPC服务器的主线程调用: `ipc_register_server` (user/chcore-libc/musl-libc/src/chcore-port/ipc.c中)来声明自己为IPC的服务器端。

* 参数包括server_handler和client_register_handler,其中server_handler为服务端用于提供服务的回调函数(比如上图中IPC handler Thread的入口函数`ipc_dispatcher`);client_register_handler为服务端提供的用于注册的回调函数,该函数会创建一个注册回调线程。

Expand Down Expand Up @@ -49,6 +49,7 @@ ChCore的IPC接口不是传统的send/recv接口。其更像客户端/服务器
> [!CODING] 练习题 7
> 在user/chcore-libc/musl-libc/src/chcore-port/ipc.c与kernel/ipc/connection.c中实现了大多数IPC相关的代码,请根据注释补全kernel/ipc/connection.c中的代码。之后运行ChCore可以看到 “[TEST] Test IPC finished!” 输出,你可以通过 Test IPC 测试点。
---

> [!IMPORTANT]
> [!SUCCESS]
> 以上为Lab4 Part3的所有内容
30 changes: 20 additions & 10 deletions Pages/Lab4/multicore.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
# 多核支持

<!-- toc -->
> [!NOTE]
> 本部分实验没有代码题,仅有思考题。
---

本部分实验没有代码题,仅有思考题。为了让ChCore支持多核,我们需要考虑如下问题:
<!-- toc -->

为了让ChCore支持多核,我们需要考虑如下问题:
- 如何启动多核,让每个核心执行初始化代码并开始执行用户代码?
- 如何区分不同核心在内核中保存的数据结构(比如状态,配置,内核对象等)?
- 如何保证内核中对象并发正确性,确保不会由于多个核心同时访问内核对象导致竞争条件?

在启动多核之前,我们先介绍ChCore如何解决第二个问题。ChCore对于内核中需要每个CPU核心单独存一份的内核对象,都根据核心数量创建了多份(即利用一个数组来保存)。ChCore支持的核心数量为PLAT_CPU_NUM(该宏定义在 kernel/common/machine.h 中,其代表可用CPU核心的数量,根据具体平台而异)。 比如,实验使用的树莓派3平台拥有4个核心,因此该宏定义的值为4。ChCore会CPU核心的核心ID作为数组的索引,在数组中取出对应的CPU核心本地的数据。为了方便确定当前执行该代码的CPU核心ID,我们在 kernel/arch/aarch64/machine/smp.c中提供了smp_get_cpu_id函数。该函数通过访问系统寄存器tpidr_el1来获取调用它的CPU核心的ID,该ID可用作访问上述数组的索引。
在启动多核之前,我们先介绍ChCore如何解决第二个问题。ChCore对于内核中需要每个CPU核心单独存一份的内核对象,都根据核心数量创建了多份(即利用一个数组来保存)。ChCore支持的核心数量为PLAT_CPU_NUM(该宏定义在 `kernel/common/machine.h` 中,其代表可用CPU核心的数量,根据具体平台而异)。 比如,实验使用的树莓派3平台拥有4个核心,因此该宏定义的值为4。ChCore会CPU核心的核心ID作为数组的索引,在数组中取出对应的CPU核心本地的数据。为了方便确定当前执行该代码的CPU核心ID,我们在 `kernel/arch/aarch64/machine/smp.c`中提供了smp_get_cpu_id函数。该函数通过访问系统寄存器tpidr_el1来获取调用它的CPU核心的ID,该ID可用作访问上述数组的索引。

```c
{{#include ../../Lab4/kernel/include/arch/aarch64/plat/raspi3/machine.h:16:20}}
```
## 启动多核
在实验1中我们已经介绍,在QEMU模拟的树莓派中,所有CPU核心在开机时会被同时启动。在引导时这些核心会被分为两种类型。一个指定的CPU核心会引导整个操作系统和初始化自身,被称为主CPU(primary CPU)。其他的CPU核心只初始化自身即可,被称为其他CPU(backup CPU)。CPU核心仅在系统引导时有所区分,在其他阶段,每个CPU核心都是被相同对待的。
在实验1中我们已经介绍,在QEMU模拟的树莓派中,所有CPU核心在开机时会被同时启动。在引导时这些核心会被分为两种类型。一个指定的CPU核心会引导整个操作系统和初始化自身,被称为CPU主核(primary CPU)。其他的CPU核心只初始化自身即可,被称为CPU从核(backup CPU)。CPU核心仅在系统引导时有所区分,在其他阶段,每个CPU核心都是被相同对待的。
> [!QUESTION] 思考题 1
> 阅读汇编代码kernel/arch/aarch64/boot/raspi3/init/start.S。说明ChCore是如何选定主CPU,并阻塞其他其他CPU的执行的。
> 阅读`Lab1`中的汇编代码kernel/arch/aarch64/boot/raspi3/init/start.S。说明ChCore是如何选定主CPU,并阻塞其他其他CPU的执行的。
在树莓派真机中,还需要主CPU手动指定每一个CPU核心的的启动地址。这些CPU核心会读取固定地址的上填写的启动地址,并跳转到该地址启动。在kernel/arch/aarch64/boot/raspi3/init/init_c.c中,我们提供了wakeup_other_cores函数用于实现该功能,并让所有的CPU核心同在QEMU一样开始执行_start函数。
然而在树莓派真机中,从还需要主C核手动指定每一个CPU核心的的启动地址。这些CPU核心会读取固定地址的上填写的启动地址,并跳转到该地址启动。在kernel/arch/aarch64/boot/raspi3/init/init_c.c中,我们提供了`wakeup_other_cores`函数用于实现该功能,并让所有的CPU核心同在QEMU一样开始执行_start函数。
与之前的实验一样,主CPU在第一次返回用户态之前会在kernel/arch/aarch64/main.c中执行main函数,进行操作系统的初始化任务。在本小节中,ChCore将执行enable_smp_cores函数激活各个其他CPU。
与之前的实验一样,主CPU在第一次返回用户态之前会在`kernel/arch/aarch64/main.c`中执行main函数,进行操作系统的初始化任务。在本小节中,ChCore将执行enable_smp_cores函数激活各个其他CPU。
> [!QUESTION] 思考题 2
> 阅读汇编代码kernel/arch/aarch64/boot/raspi3/init/start.S, init_c.c以及kernel/arch/aarch64/main.c,解释用于阻塞其他CPU核心的secondary_boot_flag是物理地址还是虚拟地址?是如何传入函数enable_smp_cores中,又是如何赋值的(考虑虚拟地址/物理地址)?
> 阅读汇编代码`kernel/arch/aarch64/boot/raspi3/init/start.S`, init_c.c以及kernel/arch/aarch64/main.c,解释用于阻塞其他CPU核心的secondary_boot_flag是物理地址还是虚拟地址?是如何传入函数`enable_smp_cores`中,又是如何赋值的(考虑虚拟地址/物理地址)?
---
> [!IMPORTANT]
> 以上为Lab4 part1 的所有内容
> [!SUCCESS]
> 以上为Lab4 part1 的所有内容
6 changes: 5 additions & 1 deletion Pages/Lab4/performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ IPC性能测试程序的测试用例包括:

在测试能够顺利通过的前提下,你可以修改任意代码。(测试程序所调用的函数位于 `user/chcore-libc/libchcore/porting/overrides/src/chcore-port/ipc.c`

> [!HINT]
> 我们所有的任务都要求多次创建ipc链接并进行操作,你需要具体理解ipc链接的创建过程并根据测试的单独场景进行优化。
---

> [!SUCCESS]
> 以上为Lab4 的所有内容
> 以上为Lab4 的所有内容
12 changes: 9 additions & 3 deletions Pages/Lab4/scheduler.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@

ChCore已经可以启动多核,但仍然无法对多个线程进行调度。本部分将首先实现协作式调度,从而允许当前在CPU核心上运行的线程主动退出或主动放弃CPU时,CPU核心能够切换到另一个线程继续执行。其后,我们将驱动树莓派上的物理定时器,使其以一定的频率发起中断,使得内核可以在一定时间片后重新获得对CPU核心的控制,并基于此进一步实现抢占式调度。

ChCore中与调度相关的函数与数据结构定义在kernel/include/sched/sched.h中。
ChCore中与调度相关的函数与数据结构定义在`kernel/include/sched/sched.h`中。

```c
{{#include ../../Lab4/kernel/include/sched/sched.h:76:84}}
```
sched_ops是用于抽象ChCore中调度器的一系列操作。它存储指向不同调度操作的函数指针,以支持不同的调度策略。
cur_sched_ops则是一个sched_ops的实例,其在内核初始化过程中(main函数)调用sched_init进行初始化。
ChCore用在 kernel/include/sched/sched.h 中定义的静态函数封装对cur_sched_ops的调用。sched_ops中定义的调度器操作如下所示:
* sche_init:初始化调度器。
* sched_init:初始化调度器。
* sched:进行一次调度。即将正在运行的线程放回就绪队列,然后在就绪队列中选择下一个需要执行的线程返回。
* sched_enqueue:将新线程添加到调度器的就绪队列中。
* sched_dequeue:从调度器的就绪队列中取出一个线程。
Expand Down Expand Up @@ -101,5 +105,7 @@ ChCore记录每个线程所拥有的时间片(`thread->thread_ctx->sc->budget`
> [!SUCCESS]
> 在完成填写之后,运行 ChCore 将可以成功进入用户态并打断创建的“自旋线程”让内核和主线程可以拿回CPU核心的控制权,你可以看到输出`"Hello, I am thread 3. I'm spinning."`和`“Thread 1 successfully regains the control!”`并通过 `Preemptive Scheduling` 测试点。
> [!IMPORTANT]
---
> [!SUCCESS]
> 以上为Lab4 Part2的所有内容
10 changes: 7 additions & 3 deletions Pages/Lab5.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

虚拟文件系统(Virtual File System,VFS)提供了一个抽象层,使得不同类型的文件系统可以在应用程序层面以统一的方式进行访问。这个抽象层隐藏了不同文件系统之间的差异,使得应用程序和系统内核可以以一致的方式访问各种不同类型的文件系统,如 ext4、tmpfs、 FAT32 等。在 ChCore 中,我们通过 FSM 系统服务以及 FS_Base 文件系统 wrapper 将不同的文件系统整合起来,给运行在 ChCore 上的应用提供了统一的抽象。

> [!CODING] 练习1
> 阅读 `user/chcore-libc/libchcore/porting/overrides/src/chcore-port/file.c``chcore_openat` 函数,分析 ChCore 是如何处理 `openat` 系统调用的,关注 IPC 的调用过程以及 IPC 请求的内容。
本Lab一共分为三个部分:

Lab5 的所有代码都运行在用户态,不同应用间通过 IPC 进行通信,可调用的 IPC 相关函数定义在 `user/chcore-libc/libchcore/porting/overrides/include/chcore/ipc.h`
1. [Posix适配](./Lab5/posix.html):分析ChCore是如何实现兼容posix的文件接口的。
2. [FSM](./Lab5/FSM.html):FSM是ChCore的虚拟文件系统的实现层,其主要负责页缓存,挂载点管理,以及路径对接。我们在此部分实现这一文件系统转发层。
3. [FS_Base](./Lab5/base.html): FS_Base是文件系统实现层,由于在微内核系统中文件系统实际由一个个进程实现,所以我们统一包装标准的文件操作到通用库即为FS_Base,我们需要在这一个部分实现它。

跟先前的Lab相同,本实验代码包含了基础的 ChCore 操作系统镜像,除了练习题相关部分的源码以外(指明需要阅读的代码),其余部分通过二进制格式提供。
在正确完成本实验的练习题之后,你可以在树莓派3B+QEMU或开发板上进入 ChCore shell。与之前的Lab不同的地方是,本Lab不涉及任何内核态的代码编写,你需要将所有的目光聚焦在`user`这个目录下面的文件。注释`/* LAB 5 TODO BEGIN (exercise #) */``/* LAB 5 TODO END (exercise #) */`之间代表需要填空的代码部分。
5 changes: 4 additions & 1 deletion Pages/Lab5/FSM.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,8 @@ struct mount_point_info_node {
我们提供了所有需要实现的文件的 Obj 版本,你可以修改 CMakeLists.txt,将编译所需的源文件从未实现的 C 文件替换为包含了正确实现的 Obj 文件,以此验证某一部分练习的正确性。如果你需要调试某一个部分,你可以将 `scripts/grade/cmakelists` 下的CMakeLists对应复制到 `FSM` 以及 `FS_Base` 的目录下覆盖并重新编译,运行 `make qemu` 后你就可以查看到 printf 的调试信息。

---

> [!SUCCESS]
> 完成第一部分后,执行 `make grade`,可以得到 `Scores: 20/100`
> 以上为Lab5 Part2的所有内容
> 执行 `make grade`,可以得到 `Scores: 20/100`
6 changes: 6 additions & 0 deletions Pages/Lab5/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,9 @@ FS_Base 的 IPC handler 在处理 IPC 请求时,会先把 IPC 消息中包含
> [!QUESTION] 练习7
> 思考 ChCore 当前实现 VFS 的方式有何利弊?如果让你在微内核操作系统上实现 VFS 抽象,你会如何实现?

---

> [!SUCCESS]
> 以上为Lab5 Part3的所有内容
Loading

0 comments on commit c339f0d

Please sign in to comment.