add tw document 01-HelloOs

This commit is contained in:
ccckmit
2020-11-14 17:08:52 +08:00
parent cdeb1fb7f1
commit f262ca56ba
15 changed files with 357 additions and 1 deletions

View File

@@ -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;
} }

View File

@@ -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/)

View File

Before

Width:  |  Height:  |  Size: 298 KiB

After

Width:  |  Height:  |  Size: 298 KiB

3
doc/ref/seL4.md Normal file
View 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
View 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 程式之基本樣貌了。

View File

View File

@@ -0,0 +1,5 @@
# 03-MultiTasking
專案 -- https://github.com/ccc-c/mini-riscv-os/tree/master/03-MultiTasking

View 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
View 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
View 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/)