mirror of
https://github.com/cccriscv/mini-riscv-os.git
synced 2025-11-16 04:24:33 +00:00
add mini-riscv-os tw document 01-03
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
## Build & Run
|
||||
|
||||
```
|
||||
```sh
|
||||
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/01-HelloOs (master)
|
||||
$ make clean
|
||||
rm -f *.elf
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
# 04-MultiTasking
|
||||
# 03-MultiTasking
|
||||
|
||||
## Build & Run
|
||||
|
||||
```
|
||||
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/04-MultiTasking (master)
|
||||
```sh
|
||||
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/03-MultiTasking
|
||||
(master)
|
||||
$ make clean
|
||||
rm -f *.elf
|
||||
|
||||
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/04-MultiTasking (master)
|
||||
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/03-MultiTasking
|
||||
(master)
|
||||
$ make
|
||||
riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -T os.ld -o os.elf start.s sys.s lib.c task.c os.c user.c
|
||||
riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima
|
||||
-mabi=ilp32 -T os.ld -o os.elf start.s sys.s lib.c task.c os.c user.c
|
||||
|
||||
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/04-MultiTasking (master)
|
||||
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/03-MultiTasking
|
||||
(master)
|
||||
$ make qemu
|
||||
Press Ctrl-A and then X to exit QEMU
|
||||
qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel os.elf
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Build & Run
|
||||
|
||||
```
|
||||
```sh
|
||||
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/04-TimerInterrupt (master)
|
||||
$ make clean
|
||||
rm -f *.elf
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Build & Run
|
||||
|
||||
```
|
||||
```sh
|
||||
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/05-Preemptive (master)
|
||||
$ make clean
|
||||
rm -f *.elf
|
||||
|
||||
@@ -109,7 +109,34 @@ clean:
|
||||
rm -f *.elf
|
||||
```
|
||||
|
||||
你可以看到我們使用 riscv64-unknown-elf-gcc 去編譯,然後用 qemu-system-riscv32 去執行, 01-HelloOs的執行過程如下:
|
||||
Makefile 的有些語法不容易懂,特別是下列的符號:
|
||||
|
||||
```
|
||||
$@ : 該規則的目標文件 (Target file)
|
||||
$* : 代表 targets 所指定的檔案,但不包含副檔名
|
||||
$< : 依賴文件列表中的第一個依賴文件 (Dependencies file)
|
||||
$^ : 依賴文件列表中的所有依賴文件
|
||||
$? : 依賴文件列表中新於目標文件的文件列表
|
||||
$* : 代表 targets 所指定的檔案,但不包含副檔名
|
||||
|
||||
?= 語法 : 若變數未定義,則替它指定新的值。
|
||||
:= 語法 : make 會將整個 Makefile 展開後,再決定變數的值。
|
||||
```
|
||||
|
||||
所以上述 Makefile 中的下列兩行:
|
||||
|
||||
```Makefile
|
||||
os.elf: start.s os.c
|
||||
$(CC) $(CFLAGS) -T os.ld -o os.elf $^
|
||||
```
|
||||
|
||||
其中的 `$^` 被代換成 `start.s os.c` ,於是整個 `$(CC) $(CFLAGS) -T os.ld -o os.elf $^` 整行展開後就變成了下列指令。
|
||||
|
||||
```
|
||||
riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -T os.ld -o os.elf start.s os.c
|
||||
```
|
||||
|
||||
在 Makefile 中我們使用 riscv64-unknown-elf-gcc 去編譯,然後用 qemu-system-riscv32 去執行, 01-HelloOs 的執行過程如下:
|
||||
|
||||
```
|
||||
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/01-HelloOs (master)
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
# 02-ContextSwitch
|
||||
|
||||
專案 -- https://github.com/ccc-c/mini-riscv-os/tree/master/02-ContextSwitch
|
||||
|
||||
在前一章的 [01-HelloOs](01-HelloOs.md) 中我們介紹了如何在 RISC-V 架構下印出字串到 UART 序列埠的方法,這一章我們將往作業系統邁進,介紹神秘的《內文切換》(Context-Switch) 技術。
|
||||
|
||||
## os.c
|
||||
|
||||
以下是 02-ContextSwitch 的主程式 os.c ,該程式除了 os 本身以外,還有一個《任務》(task)。
|
||||
|
||||
* https://github.com/ccc-c/mini-riscv-os/blob/master/02-ContextSwitch/os.c
|
||||
|
||||
```cpp
|
||||
#include "os.h"
|
||||
|
||||
#define STACK_SIZE 1024
|
||||
uint8_t task0_stack[STACK_SIZE];
|
||||
struct context ctx_os;
|
||||
struct context ctx_task;
|
||||
|
||||
extern void sys_switch();
|
||||
|
||||
void user_task0(void)
|
||||
{
|
||||
lib_puts("Task0: Context Switch Success !\n");
|
||||
while (1) {} // stop here.
|
||||
}
|
||||
|
||||
int os_main(void)
|
||||
{
|
||||
lib_puts("OS start\n");
|
||||
ctx_task.ra = (reg_t) user_task0;
|
||||
ctx_task.sp = (reg_t) &task0_stack[STACK_SIZE-1];
|
||||
sys_switch(&ctx_os, &ctx_task);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
任務 task 是一個函數,在上面的 os.c 裡是 user_task0,為了進行切換,我們將 ctx_task.ra 設為 user_task0,由於 ra 是 return address 暫存器,其功能為在函數返回執行 ret 指令時,用 ra 取代程式計數器 pc,這樣在執行 ret 指令時就能跳到該函數去執行。
|
||||
|
||||
```cpp
|
||||
ctx_task.ra = (reg_t) user_task0;
|
||||
ctx_task.sp = (reg_t) &task0_stack[STACK_SIZE-1];
|
||||
sys_switch(&ctx_os, &ctx_task);
|
||||
```
|
||||
|
||||
但是每個任務都必須有堆疊空間,才能在 C 語言環境中進行函數呼叫。所以我們分配了 task0 的堆疊空間,並用 ctx_task.sp 指向堆疊開頭。
|
||||
|
||||
然後我們呼叫了 `sys_switch(&ctx_os, &ctx_task)` 從主程式切換到 task0,其中的 sys_switch 是個位於 [sys.s](https://github.com/ccc-c/mini-riscv-os/blob/master/02-ContextSwitch/sys.s) 裏組合語言函數,內容如下:
|
||||
|
||||
```s
|
||||
# Context switch
|
||||
#
|
||||
# void sys_switch(struct context *old, struct context *new);
|
||||
#
|
||||
# Save current registers in old. Load from new.
|
||||
|
||||
.globl sys_switch
|
||||
.align 4
|
||||
sys_switch:
|
||||
ctx_save a0 # a0 => struct context *old
|
||||
ctx_load a1 # a1 => struct context *new
|
||||
ret # pc=ra; swtch to new task (new->ra)
|
||||
```
|
||||
|
||||
在 RISC-V 中,參數主要放在 a0, a1, ..., a7 這些暫存器當中,當參數超過八個時,才會放在堆疊裏傳遞。
|
||||
|
||||
sys_switch 對應的 C 語言函數如下:
|
||||
|
||||
```cpp
|
||||
void sys_switch(struct context *old, struct context *new);
|
||||
```
|
||||
|
||||
上述程式的 a0 對應 old (舊任務的 context),a1 對應 new (新任務的context),整個 sys_switch 的功能是儲存舊任務的 context ,然後載入新任務 context 開始執行。
|
||||
|
||||
最後的一個 ret 指令非常重要,因為當新任務的 context 載入時會把 ra 暫存器也載進來,於是當 ret 執行時,就會設定 pc=ra,然後跳到新任務 (例如 `void user_task0(void)` 去執行了。
|
||||
|
||||
sys_switch 中的 `ctx_save` 和 `ctx_load` 是兩個組合語言巨集,其定義如下:
|
||||
|
||||
```s
|
||||
# ============ MACRO ==================
|
||||
.macro ctx_save base
|
||||
sw ra, 0(\base)
|
||||
sw sp, 4(\base)
|
||||
sw s0, 8(\base)
|
||||
sw s1, 12(\base)
|
||||
sw s2, 16(\base)
|
||||
sw s3, 20(\base)
|
||||
sw s4, 24(\base)
|
||||
sw s5, 28(\base)
|
||||
sw s6, 32(\base)
|
||||
sw s7, 36(\base)
|
||||
sw s8, 40(\base)
|
||||
sw s9, 44(\base)
|
||||
sw s10, 48(\base)
|
||||
sw s11, 52(\base)
|
||||
.endm
|
||||
|
||||
.macro ctx_load base
|
||||
lw ra, 0(\base)
|
||||
lw sp, 4(\base)
|
||||
lw s0, 8(\base)
|
||||
lw s1, 12(\base)
|
||||
lw s2, 16(\base)
|
||||
lw s3, 20(\base)
|
||||
lw s4, 24(\base)
|
||||
lw s5, 28(\base)
|
||||
lw s6, 32(\base)
|
||||
lw s7, 36(\base)
|
||||
lw s8, 40(\base)
|
||||
lw s9, 44(\base)
|
||||
lw s10, 48(\base)
|
||||
lw s11, 52(\base)
|
||||
.endm
|
||||
# ============ Macro END ==================
|
||||
```
|
||||
|
||||
RISC-V 行程切換時必須儲存 ra, sp, s0, ... s11 等暫存器,上述的程式碼基本上是我從 xv6 這個教學作業系統中抄來後修改為 RISC-V 32 位元版的,其原始網址如下:
|
||||
|
||||
* https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/swtch.S
|
||||
|
||||
在 [riscv.h](https://github.com/ccc-c/mini-riscv-os/blob/master/02-ContextSwitch/riscv.h) 這個表頭檔中,我們定義了 struct context 這個對應的 C 語言結構,其內容如下:
|
||||
|
||||
```cpp
|
||||
// Saved registers for kernel context switches.
|
||||
struct context {
|
||||
reg_t ra;
|
||||
reg_t sp;
|
||||
|
||||
// callee-saved
|
||||
reg_t s0;
|
||||
reg_t s1;
|
||||
reg_t s2;
|
||||
reg_t s3;
|
||||
reg_t s4;
|
||||
reg_t s5;
|
||||
reg_t s6;
|
||||
reg_t s7;
|
||||
reg_t s8;
|
||||
reg_t s9;
|
||||
reg_t s10;
|
||||
reg_t s11;
|
||||
};
|
||||
```
|
||||
|
||||
這樣,我們就介紹完《內文切換》任務的細節了,於是下列主程式就能順利地從 os_main 切換到 user_task0 了。
|
||||
|
||||
```cpp
|
||||
int os_main(void)
|
||||
{
|
||||
lib_puts("OS start\n");
|
||||
ctx_task.ra = (reg_t) user_task0;
|
||||
ctx_task.sp = (reg_t) &task0_stack[STACK_SIZE-1];
|
||||
sys_switch(&ctx_os, &ctx_task);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
以下是整個專案的執行結果:
|
||||
|
||||
```sh
|
||||
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/03-ContextSwitch (master)
|
||||
$ make
|
||||
riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -T os.ld -o os.elf start.s sys.s lib.c os.c
|
||||
|
||||
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/03-ContextSwitch (master)
|
||||
$ make qemu
|
||||
Press Ctrl-A and then X to exit QEMU
|
||||
qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel os.elf
|
||||
OS start
|
||||
Task0: Context Switch Success !
|
||||
QEMU: Terminated
|
||||
```
|
||||
|
||||
以上就是 RISC-V 裏的《內文切換》(Context-Switch) 機制的實作方法!
|
||||
|
||||
@@ -1,5 +1,239 @@
|
||||
# 03-MultiTasking
|
||||
|
||||
[os.c]:https://github.com/ccc-c/mini-riscv-os/blob/master/03-MultiTasking/os.c
|
||||
|
||||
[task.c]:https://github.com/ccc-c/mini-riscv-os/blob/master/03-MultiTasking/task.c
|
||||
|
||||
[user.c]:https://github.com/ccc-c/mini-riscv-os/blob/master/03-MultiTasking/user.c
|
||||
|
||||
[sys.s]:https://github.com/ccc-c/mini-riscv-os/blob/master/03-MultiTasking/sys.s
|
||||
|
||||
專案 -- https://github.com/ccc-c/mini-riscv-os/tree/master/03-MultiTasking
|
||||
|
||||
在前一章的 [02-ContextSwitch](02-ContextSwitch.md) 中我們介紹了 RISC-V 架構下的內文切換機制,這一章我們將進入多行程的世界,介紹如何撰寫一個《協同式多工》作業系統。
|
||||
|
||||
## 協同式多工
|
||||
|
||||
現代的作業系統,都有透過時間中斷強制中止行程的《搶先》(Preemptive) 功能,這樣就能在某行程霸佔 CPU 太久時,強制將其中斷,切換給別的行程執行。
|
||||
|
||||
但是在沒有時間中斷機制的系統中,作業系統《無法中斷惡霸行程》,因此必須依賴各個行程主動交回控制權給作業系統,才能讓所有行程都有機會執行。
|
||||
|
||||
這種仰賴自動交還機制的多行程系統,稱為《協同式多工》(Coorperative Multitasking) 系統。
|
||||
|
||||
1991 年微軟推出的 Windows 3.1 ,還有單板電腦 arduino 上的 [HeliOS](https://github.com/MannyPeterson/HeliOS),都是《協同式多工》機制的作業系統。
|
||||
|
||||
在本章中,我們將設計一個在 RISC-V 處理器上的《協同式多工》作業系統。
|
||||
|
||||
首先讓我們來看看該系統的執行結果。
|
||||
|
||||
```sh
|
||||
$ make qemu
|
||||
Press Ctrl-A and then X to exit QEMU
|
||||
qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel os.elf
|
||||
OS start
|
||||
OS: Activate next task
|
||||
Task0: Created!
|
||||
Task0: Now, return to kernel mode
|
||||
OS: Back to OS
|
||||
|
||||
OS: Activate next task
|
||||
Task1: Created!
|
||||
Task1: Now, return to kernel mode
|
||||
OS: Back to OS
|
||||
|
||||
OS: Activate next task
|
||||
Task0: Running...
|
||||
OS: Back to OS
|
||||
|
||||
OS: Activate next task
|
||||
Task1: Running...
|
||||
OS: Back to OS
|
||||
|
||||
OS: Activate next task
|
||||
Task0: Running...
|
||||
OS: Back to OS
|
||||
|
||||
OS: Activate next task
|
||||
Task1: Running...
|
||||
OS: Back to OS
|
||||
|
||||
OS: Activate next task
|
||||
Task0: Running...
|
||||
OS: Back to OS
|
||||
|
||||
OS: Activate next task
|
||||
Task1: Running...
|
||||
OS: Back to OS
|
||||
|
||||
OS: Activate next task
|
||||
Task0: Running...
|
||||
QEMU: Terminated
|
||||
```
|
||||
|
||||
您可以看到該系統在兩個任務 Task0, Task1 之間不斷切換,但實際上的切換過程如下:
|
||||
|
||||
```
|
||||
OS=>Task0=>OS=>Task1=>OS=>Task0=>OS=>Task1 ....
|
||||
```
|
||||
|
||||
## 使用者任務 [user.c]
|
||||
|
||||
在 [user.c] 中我們定義了 user_task0 與 user_task1 兩個 task,並且在 user_init 函數終將這兩個 task 初始化。
|
||||
|
||||
* https://github.com/ccc-c/mini-riscv-os/blob/master/03-MultiTasking/user.c
|
||||
|
||||
```cpp
|
||||
#include "os.h"
|
||||
|
||||
void user_task0(void)
|
||||
{
|
||||
lib_puts("Task0: Created!\n");
|
||||
lib_puts("Task0: Now, return to kernel mode\n");
|
||||
os_kernel();
|
||||
while (1) {
|
||||
lib_puts("Task0: Running...\n");
|
||||
lib_delay(1000);
|
||||
os_kernel();
|
||||
}
|
||||
}
|
||||
|
||||
void user_task1(void)
|
||||
{
|
||||
lib_puts("Task1: Created!\n");
|
||||
lib_puts("Task1: Now, return to kernel mode\n");
|
||||
os_kernel();
|
||||
while (1) {
|
||||
lib_puts("Task1: Running...\n");
|
||||
lib_delay(1000);
|
||||
os_kernel();
|
||||
}
|
||||
}
|
||||
|
||||
void user_init() {
|
||||
task_create(&user_task0);
|
||||
task_create(&user_task1);
|
||||
}
|
||||
```
|
||||
|
||||
## 主程式 [os.c]
|
||||
|
||||
然後在作業系統主程式 os.c 當中,我們使用大輪迴的方式,安排每個行程按照順序輪迴的執行。
|
||||
|
||||
* https://github.com/ccc-c/mini-riscv-os/blob/master/03-MultiTasking/os.c
|
||||
|
||||
```cpp
|
||||
#include "os.h"
|
||||
|
||||
void os_kernel() {
|
||||
task_os();
|
||||
}
|
||||
|
||||
void os_start() {
|
||||
lib_puts("OS start\n");
|
||||
user_init();
|
||||
}
|
||||
|
||||
int os_main(void)
|
||||
{
|
||||
os_start();
|
||||
|
||||
int current_task = 0;
|
||||
while (1) {
|
||||
lib_puts("OS: Activate next task\n");
|
||||
task_go(current_task);
|
||||
lib_puts("OS: Back to OS\n");
|
||||
current_task = (current_task + 1) % taskTop; // Round Robin Scheduling
|
||||
lib_puts("\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
上述排程方法原則上和 [Round Robin Scheduling](https://en.wikipedia.org/wiki/Round-robin_scheduling) 一致,但是 Round Robin Scheduling 原則上必須搭配時間中斷機制,但本章的程式碼沒有時間中斷,所以只能說是協同式多工版本的 Round Robin Scheduling。
|
||||
|
||||
協同式多工必須依賴各個 task 主動交回控制權,像是在 user_task0 裏,每當呼叫 os_kernel() 函數時,就會呼叫內文切換機制,將控制權交回給作業系統 [os.c] 。
|
||||
|
||||
```cpp
|
||||
void user_task0(void)
|
||||
{
|
||||
lib_puts("Task0: Created!\n");
|
||||
lib_puts("Task0: Now, return to kernel mode\n");
|
||||
os_kernel();
|
||||
while (1) {
|
||||
lib_puts("Task0: Running...\n");
|
||||
lib_delay(1000);
|
||||
os_kernel();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
其中 [os.c] 的 os_kernel() 會呼叫 [task.c] 的 task_os()
|
||||
|
||||
```cpp
|
||||
void os_kernel() {
|
||||
task_os();
|
||||
}
|
||||
```
|
||||
|
||||
而 task_os() 則會呼叫組合語言 [sys.s] 裏的 sys_switch 去切換回作業系統中。
|
||||
|
||||
```cpp
|
||||
// switch back to os
|
||||
void task_os() {
|
||||
struct context *ctx = ctx_now;
|
||||
ctx_now = &ctx_os;
|
||||
sys_switch(ctx, &ctx_os);
|
||||
}
|
||||
```
|
||||
|
||||
於是整個系統就在 os_main(), user_task0(), user_task1() 三者的協作下,以互相禮讓的方式輪流執行著。
|
||||
|
||||
[os.c]
|
||||
|
||||
```cpp
|
||||
int os_main(void)
|
||||
{
|
||||
os_start();
|
||||
|
||||
int current_task = 0;
|
||||
while (1) {
|
||||
lib_puts("OS: Activate next task\n");
|
||||
task_go(current_task);
|
||||
lib_puts("OS: Back to OS\n");
|
||||
current_task = (current_task + 1) % taskTop; // Round Robin Scheduling
|
||||
lib_puts("\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
[user.c]
|
||||
|
||||
```cpp
|
||||
void user_task0(void)
|
||||
{
|
||||
lib_puts("Task0: Created!\n");
|
||||
lib_puts("Task0: Now, return to kernel mode\n");
|
||||
os_kernel();
|
||||
while (1) {
|
||||
lib_puts("Task0: Running...\n");
|
||||
lib_delay(1000);
|
||||
os_kernel();
|
||||
}
|
||||
}
|
||||
|
||||
void user_task1(void)
|
||||
{
|
||||
lib_puts("Task1: Created!\n");
|
||||
lib_puts("Task1: Now, return to kernel mode\n");
|
||||
os_kernel();
|
||||
while (1) {
|
||||
lib_puts("Task1: Running...\n");
|
||||
lib_delay(1000);
|
||||
os_kernel();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
以上就是 RISC-V 處理器上一個具體而微的協同式多工作業系統範例了!
|
||||
|
||||
|
||||
Reference in New Issue
Block a user