mirror of
https://github.com/cccriscv/mini-riscv-os.git
synced 2025-11-16 12:34:33 +00:00
add tw document 01-HelloOs
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
// ref: https://www.activexperts.com/serial-port-component/tutorials/uart/
|
|
||||||
#define UART 0x10000000
|
#define UART 0x10000000
|
||||||
#define UART_THR (uint8_t*)(UART+0x00) // THR:transmitter holding register
|
#define UART_THR (uint8_t*)(UART+0x00) // THR:transmitter holding register
|
||||||
#define UART_LSR (uint8_t*)(UART+0x05) // LSR:line status register
|
#define UART_LSR (uint8_t*)(UART+0x05) // LSR:line status register
|
||||||
@@ -18,5 +17,6 @@ void lib_puts(char *s) {
|
|||||||
int os_main(void)
|
int os_main(void)
|
||||||
{
|
{
|
||||||
lib_puts("Hello OS!\n");
|
lib_puts("Hello OS!\n");
|
||||||
|
while (1) {}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,3 +52,4 @@ in the `LICENSE` file.
|
|||||||
|
|
||||||
* [Adventures in RISC-V](https://matrix89.github.io/writes/writes/experiments-in-riscv/)
|
* [Adventures in RISC-V](https://matrix89.github.io/writes/writes/experiments-in-riscv/)
|
||||||
* [Xv6, a simple Unix-like teaching operating system](https://pdos.csail.mit.edu/6.828/2020/xv6.html)
|
* [Xv6, a simple Unix-like teaching operating system](https://pdos.csail.mit.edu/6.828/2020/xv6.html)
|
||||||
|
* [Basics of programming a UART](https://www.activexperts.com/serial-port-component/tutorials/uart/)
|
||||||
|
Before Width: | Height: | Size: 298 KiB After Width: | Height: | Size: 298 KiB |
3
doc/ref/seL4.md
Normal file
3
doc/ref/seL4.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# seL4
|
||||||
|
|
||||||
|
* https://github.com/seL4/seL4/blob/master/src/arch/riscv/traps.S
|
||||||
272
doc/tw/01-HelloOs.md
Normal file
272
doc/tw/01-HelloOs.md
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
# 01-HelloOs
|
||||||
|
|
||||||
|
專案 -- https://github.com/ccc-c/mini-riscv-os/tree/master/01-HelloOs
|
||||||
|
|
||||||
|
## os.c
|
||||||
|
|
||||||
|
https://github.com/ccc-c/mini-riscv-os/blob/master/01-HelloOs/os.c
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define UART 0x10000000
|
||||||
|
#define UART_THR (uint8_t*)(UART+0x00) // THR:transmitter holding register
|
||||||
|
#define UART_LSR (uint8_t*)(UART+0x05) // LSR:line status register
|
||||||
|
#define UART_LSR_EMPTY_MASK 0x40 // LSR Bit 6: Transmitter empty; both the THR and LSR are empty
|
||||||
|
|
||||||
|
int lib_putc(char ch) {
|
||||||
|
while ((*UART_LSR & UART_LSR_EMPTY_MASK) == 0);
|
||||||
|
return *UART_THR = ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lib_puts(char *s) {
|
||||||
|
while (*s) lib_putc(*s++);
|
||||||
|
}
|
||||||
|
|
||||||
|
int os_main(void)
|
||||||
|
{
|
||||||
|
lib_puts("Hello OS!\n");
|
||||||
|
while (1) {}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
QEMU 中預設的 RISC-V 虛擬機稱為 virt ,其中的 UART 記憶體映射位置從 0x10000000 開始,映射方式如下所示:
|
||||||
|
|
||||||
|
```
|
||||||
|
UART 映射區
|
||||||
|
|
||||||
|
0x10000000 THR (Transmitter Holding Register) 同時也是 RHR (Receive Holding Register)
|
||||||
|
0x10000001 IER (Interrupt Enable Register)
|
||||||
|
0x10000002 ISR (Interrupt Status Register)
|
||||||
|
0x10000003 LCR (Line Control Register)
|
||||||
|
0x10000004 MCR (Modem Control Register)
|
||||||
|
0x10000005 LSR (Line Status Register)
|
||||||
|
0x10000006 MSR (Modem Status Register)
|
||||||
|
0x10000007 SPR (Scratch Pad Register)
|
||||||
|
```
|
||||||
|
|
||||||
|
我們只要將某字元往 UART 的 THR 送,就可以印出該字元,但送之前必須確認 LSR 的第六位元是否為 1 (代表 UART 傳送區為空,可以傳送了)。
|
||||||
|
|
||||||
|
```
|
||||||
|
THR Bit 6: Transmitter empty; both the THR and shift register are empty if this is set.
|
||||||
|
```
|
||||||
|
|
||||||
|
所以我們寫了下列函數來送一個字元給 UART 以便印出到宿主機 (host) 上。(因為嵌入式系統通常沒有顯示裝置,所以會送回宿主機顯示)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int lib_putc(char ch) {
|
||||||
|
while ((*UART_LSR & UART_LSR_EMPTY_MASK) == 0);
|
||||||
|
return *UART_THR = ch;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
能印出一個字之後,就能用以下的 lib_puts(s) 印出一大串字。
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void lib_puts(char *s) {
|
||||||
|
while (*s) lib_putc(*s++);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
於是我們的主程式就呼叫 lib_puts 印出了 `Hello OS!` 。
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int os_main(void)
|
||||||
|
{
|
||||||
|
lib_puts("Hello OS!\n");
|
||||||
|
while (1) {}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
雖然我們的主程式只有短短的 22 行,但是 01-HelloOs 專案包含的不是只有主程式,還有啟動程式 start.s ,連結檔案 os.ld,以及建置檔 Makefile。
|
||||||
|
|
||||||
|
## 專案建置檔 Makefile
|
||||||
|
|
||||||
|
mini-riscv-os 裏的 Makefile 通常大同小異,以下是 01-HelloOs 的 Makefile.
|
||||||
|
|
||||||
|
```Makefile
|
||||||
|
CC = riscv64-unknown-elf-gcc
|
||||||
|
CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32
|
||||||
|
|
||||||
|
QEMU = qemu-system-riscv32
|
||||||
|
QFLAGS = -nographic -smp 4 -machine virt -bios none
|
||||||
|
|
||||||
|
OBJDUMP = riscv64-unknown-elf-objdump
|
||||||
|
|
||||||
|
all: os.elf
|
||||||
|
|
||||||
|
os.elf: start.s os.c
|
||||||
|
$(CC) $(CFLAGS) -T os.ld -o os.elf $^
|
||||||
|
|
||||||
|
qemu: $(TARGET)
|
||||||
|
@qemu-system-riscv32 -M ? | grep virt >/dev/null || exit
|
||||||
|
@echo "Press Ctrl-A and then X to exit QEMU"
|
||||||
|
$(QEMU) $(QFLAGS) -kernel os.elf
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f *.elf
|
||||||
|
```
|
||||||
|
|
||||||
|
你可以看到我們使用 riscv64-unknown-elf-gcc 去編譯,然後用 qemu-system-riscv32 去執行, 01-HelloOs的執行過程如下:
|
||||||
|
|
||||||
|
```
|
||||||
|
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/01-HelloOs (master)
|
||||||
|
$ make clean
|
||||||
|
rm -f *.elf
|
||||||
|
|
||||||
|
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/01-HelloOs (master)
|
||||||
|
$ make
|
||||||
|
riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -T os.ld -o os.elf start.s os.c
|
||||||
|
|
||||||
|
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/01-HelloOs (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
|
||||||
|
Hello OS!
|
||||||
|
QEMU: Terminated
|
||||||
|
```
|
||||||
|
|
||||||
|
首先用 make clean 清除上次的編譯產出,然後用 make 呼叫 riscv64-unknown-elf-gcc 編譯專案,以下是完整的編譯指令
|
||||||
|
|
||||||
|
```
|
||||||
|
$ riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -T os.ld -o os.elf start.s os.c
|
||||||
|
```
|
||||||
|
|
||||||
|
其中 `-march=rv32ima` 代表我們要 [產生 32 位元 I+M+A 指令集的碼](https://www.sifive.com/blog/all-aboard-part-1-compiler-args):
|
||||||
|
|
||||||
|
```
|
||||||
|
I: 基本整數指令集 (Integer)
|
||||||
|
M: 包含乘除法 (Multiply)
|
||||||
|
A: 包含原子指令 (Atomic)
|
||||||
|
C: 使用 16 位元壓縮 (Compact) -- 注意:我們沒加 C ,所以產生的指令機器碼是純粹 32 位元指令,沒有被壓成 16 位元,因為我們希望指令長度大小一致,從頭到尾都是 32 bits。
|
||||||
|
```
|
||||||
|
|
||||||
|
而 `-mabi=ilp32` 代表產生的二進位目的碼的整數是以 32 位元為主的架構。
|
||||||
|
|
||||||
|
- ilp32: int, long, and pointers are all 32-bits long. long long is a 64-bit type, char is 8-bit, and short is 16-bit.
|
||||||
|
- lp64: long and pointers are 64-bits long, while int is a 32-bit type. The other types remain the same as ilp32.
|
||||||
|
|
||||||
|
還有 `-mcmodel=medany` 參數代表產生符號位址必須可在 2GB 內,可以使用靜態連結的方式定址。
|
||||||
|
|
||||||
|
- `-mcmodel=medany`
|
||||||
|
* Generate code for the medium-any code model. The program and its statically defined symbols must be within any single 2 GiB address range. Programs can be statically or dynamically linked.
|
||||||
|
|
||||||
|
更詳細的 RISC-V gcc 參數可以參考下列文件:
|
||||||
|
|
||||||
|
* https://gcc.gnu.org/onlinedocs/gcc/RISC-V-Options.html
|
||||||
|
|
||||||
|
另外還使用了 `-nostdlib -fno-builtin` 這兩個參數代表不去連結使用標準函式庫 (因為是嵌入式系統,函式庫通常得自製),請參考下列文件:
|
||||||
|
|
||||||
|
* https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html
|
||||||
|
|
||||||
|
|
||||||
|
## Link Script 連結檔 (os.ld)
|
||||||
|
|
||||||
|
還有 `-T os.ld` 參數指定了 link script 為 os.ld 檔案如下:(link script 是描述程式段 TEXT、資料段 DATA 與 BSS 未初始化資料段分別該如何放入記憶體的指引檔)
|
||||||
|
|
||||||
|
```ld
|
||||||
|
OUTPUT_ARCH( "riscv" )
|
||||||
|
|
||||||
|
ENTRY( _start )
|
||||||
|
|
||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
|
||||||
|
}
|
||||||
|
|
||||||
|
PHDRS
|
||||||
|
{
|
||||||
|
text PT_LOAD;
|
||||||
|
data PT_LOAD;
|
||||||
|
bss PT_LOAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
.text : {
|
||||||
|
PROVIDE(_text_start = .);
|
||||||
|
*(.text.init) *(.text .text.*)
|
||||||
|
PROVIDE(_text_end = .);
|
||||||
|
} >ram AT>ram :text
|
||||||
|
|
||||||
|
.rodata : {
|
||||||
|
PROVIDE(_rodata_start = .);
|
||||||
|
*(.rodata .rodata.*)
|
||||||
|
PROVIDE(_rodata_end = .);
|
||||||
|
} >ram AT>ram :text
|
||||||
|
|
||||||
|
.data : {
|
||||||
|
. = ALIGN(4096);
|
||||||
|
PROVIDE(_data_start = .);
|
||||||
|
*(.sdata .sdata.*) *(.data .data.*)
|
||||||
|
PROVIDE(_data_end = .);
|
||||||
|
} >ram AT>ram :data
|
||||||
|
|
||||||
|
.bss :{
|
||||||
|
PROVIDE(_bss_start = .);
|
||||||
|
*(.sbss .sbss.*) *(.bss .bss.*)
|
||||||
|
PROVIDE(_bss_end = .);
|
||||||
|
} >ram AT>ram :bss
|
||||||
|
|
||||||
|
PROVIDE(_memory_start = ORIGIN(ram));
|
||||||
|
PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 啟動程式 (start.s)
|
||||||
|
|
||||||
|
嵌入式系統除了主程式外,通常還需要一個組合語言寫的啟動程式,01-HelloOs 裏的啟動程式 start.s 內容如下:(主要是讓多核心的架構下,只有一個核心被啟動,其他核心都睡著,這樣可以讓事情變得更單純,不需考慮太多平行處理方面的問題)。
|
||||||
|
|
||||||
|
```s
|
||||||
|
.equ STACK_SIZE, 8192
|
||||||
|
|
||||||
|
.global _start
|
||||||
|
|
||||||
|
_start:
|
||||||
|
# setup stacks per hart
|
||||||
|
csrr t0, mhartid # read current hart id
|
||||||
|
slli t0, t0, 10 # shift left the hart id by 1024
|
||||||
|
la sp, stacks + STACK_SIZE # set the initial stack pointer
|
||||||
|
# to the end of the stack space
|
||||||
|
add sp, sp, t0 # move the current hart stack pointer
|
||||||
|
# to its place in the stack space
|
||||||
|
|
||||||
|
# park harts with id != 0
|
||||||
|
csrr a0, mhartid # read current hart id
|
||||||
|
bnez a0, park # if we're not on the hart 0
|
||||||
|
# we park the hart
|
||||||
|
|
||||||
|
j os_main # hart 0 jump to c
|
||||||
|
|
||||||
|
park:
|
||||||
|
wfi
|
||||||
|
j park
|
||||||
|
|
||||||
|
stacks:
|
||||||
|
.skip STACK_SIZE * 4 # allocate space for the harts stacks
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## 用 QEMU 執行
|
||||||
|
|
||||||
|
而當你打入 make qemu 時,Make 會執行下列指令
|
||||||
|
|
||||||
|
```
|
||||||
|
qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel os.elf
|
||||||
|
```
|
||||||
|
|
||||||
|
代表要用 qemu-system-riscv32 執行 os.elf 這個 kernel 檔案,`-bios none` 不使用基本輸出入 bios,`-nographic` 不使用繪圖模式 ,而指定的機器架構為 `-machine virt` ,也就是 QEMU 預設的 RISC-V 虛擬機 virt。
|
||||||
|
|
||||||
|
於是當你打入 `make qemu` 時,就會看到下列畫面了!
|
||||||
|
|
||||||
|
```
|
||||||
|
$ make qemu
|
||||||
|
Press Ctrl-A and then X to exit QEMU
|
||||||
|
qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel os.elf
|
||||||
|
Hello OS!
|
||||||
|
QEMU: Terminated
|
||||||
|
```
|
||||||
|
|
||||||
|
這就是 RISC-V 嵌入式系統裏一個最簡單的 Hello 程式之基本樣貌了。
|
||||||
0
doc/tw/02-ContextSwitch.md
Normal file
0
doc/tw/02-ContextSwitch.md
Normal file
5
doc/tw/03-MultiTasking.md
Normal file
5
doc/tw/03-MultiTasking.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# 03-MultiTasking
|
||||||
|
|
||||||
|
專案 -- https://github.com/ccc-c/mini-riscv-os/tree/master/03-MultiTasking
|
||||||
|
|
||||||
|
|
||||||
5
doc/tw/04-TimerInterrupt.md
Normal file
5
doc/tw/04-TimerInterrupt.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# 04-TimerInterrupt
|
||||||
|
|
||||||
|
專案 -- https://github.com/ccc-c/mini-riscv-os/tree/master/04-TimerInterrupt
|
||||||
|
|
||||||
|
|
||||||
5
doc/tw/05-Preemptive.md
Normal file
5
doc/tw/05-Preemptive.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# 05-Preemptive
|
||||||
|
|
||||||
|
專案 -- https://github.com/ccc-c/mini-riscv-os/tree/master/05-Preemptive
|
||||||
|
|
||||||
|
|
||||||
65
doc/tw/README.md
Normal file
65
doc/tw/README.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# mini-riscv-os
|
||||||
|
|
||||||
|
一步一步自製嵌入式作業系統
|
||||||
|
|
||||||
|
## 簡介
|
||||||
|
|
||||||
|
mini-riscv-os 是仿照 [jserv](https://github.com/jserv) 的 [mini-arm-os](https://github.com/jserv/mini-arm-os) 所做出來的專案。
|
||||||
|
|
||||||
|
陳鍾誠 [ccckmit](https://github.com/ccckmit) 參考 mini-arm-os 專案,改寫成 RISC-V 處理器的 mini-riscv-os,然後在 Windows 10 作業系統中編譯並執行 。
|
||||||
|
|
||||||
|
## 安裝指引
|
||||||
|
|
||||||
|
目前只在 windows 10 平台中建置並測試,請安裝 git bash 與 [FreedomStudio](https://www.sifive.com/software)。
|
||||||
|
|
||||||
|
下載 FreedomStudio 的 windows 版後,請將 `riscv64-unknown-elf-gcc/bin` 和 `riscv-qemu/bin` 加入系統路徑 PATH 設到 中。
|
||||||
|
|
||||||
|
舉例而言,在我的電腦中,我將下列兩個資料夾加入 PATH 中。
|
||||||
|
|
||||||
|
```
|
||||||
|
D:\install\FreedomStudio-2020-06-3-win64\SiFive\riscv64-unknown-elf-gcc-8.3.0-2020.04.1\bin
|
||||||
|
|
||||||
|
D:\install\FreedomStudio-2020-06-3-win64\SiFive\riscv-qemu-4.2.0-2020.04.0\bin
|
||||||
|
```
|
||||||
|
|
||||||
|
然後請用 git bash 來建置編譯本課程的專案。我是在 vscode (Visual Studio Code) 中開終端機 Select Default Shell 設成 bash ,然後編譯這些專案的。
|
||||||
|
|
||||||
|
## 自製 RISC-V 嵌入式作業系統課程
|
||||||
|
|
||||||
|
* [01-HelloOs](01-HelloOs.md)
|
||||||
|
- 使用 UART 印出 `"Hello OS!"` 訊息
|
||||||
|
* [02-ContextSwitch](02-ContextSwitch.md)
|
||||||
|
- 從 OS 切到第一個使用者行程 (user task)
|
||||||
|
* [03-MultiTasking](03-MultiTasking.md)
|
||||||
|
- 可以在作業系統與兩個行程之間切換 (協同式多工,非強制切換 Non Preemptive)
|
||||||
|
* [04-TimerInterrupt](04-TimerInterrupt.md)
|
||||||
|
- 示範如何引發時間中斷 (大約每秒一次)
|
||||||
|
* [05-Preemptive](05-Preemptive.md)
|
||||||
|
- 透過時間中斷強制切換的 Preemptive 作業系統。
|
||||||
|
|
||||||
|
## 建置方法
|
||||||
|
|
||||||
|
* 先切到對應資料夾,例如 `cd 01-HelloOs`,然後執行下列指令。
|
||||||
|
|
||||||
|
```
|
||||||
|
make
|
||||||
|
make qemu
|
||||||
|
```
|
||||||
|
|
||||||
|
如果你沒有 make,請嘗試下列方式:
|
||||||
|
|
||||||
|
1. 用 choco 安裝 make
|
||||||
|
* [choco install make](https://chocolatey.org/packages/make)
|
||||||
|
* 當然在此之前得先 [安裝好 choco](https://chocolatey.org/install)
|
||||||
|
2. 或者下載 CodeBlocks ,然後加入 `CodeBlocks\MinGW\bin` 到 PATH 中。
|
||||||
|
* 例如我的系統裡 make 是在 `C:\Program Files (x86)\CodeBlocks\MinGW\bin`
|
||||||
|
|
||||||
|
## 授權聲明
|
||||||
|
|
||||||
|
`mini-riscv-os` 在遵守 BSD 授權協議的情況下,可以被自由的複製、散布與修改,詳細授權聲明請看 [LICENSE](../../LICENSE)。
|
||||||
|
|
||||||
|
## 參考文獻
|
||||||
|
|
||||||
|
* [Adventures in RISC-V](https://matrix89.github.io/writes/writes/experiments-in-riscv/)
|
||||||
|
* [Xv6, a simple Unix-like teaching operating system](https://pdos.csail.mit.edu/6.828/2020/xv6.html)
|
||||||
|
* [Basics of programming a UART](https://www.activexperts.com/serial-port-component/tutorials/uart/)
|
||||||
Reference in New Issue
Block a user