diff --git a/05-Preemptive/sys.s b/05-Preemptive/sys.s index 1b8c8fd..515bf60 100644 --- a/05-Preemptive/sys.s +++ b/05-Preemptive/sys.s @@ -138,12 +138,12 @@ trap_vector: # trap_handler will return the return address via a0. csrw mepc, a0 - # restore context(registers). + # load context(registers). csrr t6, mscratch reg_load t6 - la a1, os_kernel # mepc = sys_kernel - csrw mepc, a1 # mret : will jump to sys_kernel - # return to whatever we were doing before trap. + # jump to sys_kernel + la a1, os_kernel # a1 = os_kernel + csrw mepc, a1 # mepc = sys_kernel mret diff --git a/05-Preemptive/timer.c b/05-Preemptive/timer.c index 0f5e091..b2dc42c 100644 --- a/05-Preemptive/timer.c +++ b/05-Preemptive/timer.c @@ -1,7 +1,5 @@ #include "timer.h" -extern void os_kernel(); - // a scratch area per CPU for machine-mode timer interrupts. reg_t timer_scratch[NCPU][5]; diff --git a/AUTHORS b/AUTHORS index d5aa51a..2b7c452 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,5 +1,8 @@ mini-riscv-os is written by: Chung-Chen Chen +Other contributors: + Ian Chen + Copyrighted by: National Quemoy University, Taiwan diff --git a/doc/tw/05-Preemptive.md b/doc/tw/05-Preemptive.md index 9054c22..80411ef 100644 --- a/doc/tw/05-Preemptive.md +++ b/doc/tw/05-Preemptive.md @@ -1,22 +1,17 @@ # 05-Preemptive -- RISC-V 的嵌入式作業系統 -[lib.c]:https://github.com/ccc-c/mini-riscv-os/blob/master/05-Preemptive/lib.c - -[os.c]:https://github.com/ccc-c/mini-riscv-os/blob/master/05-Preemptive/os.c - -[timer.c]:https://github.com/ccc-c/mini-riscv-os/blob/master/05-Preemptive/timer.c - -[sys.s]:https://github.com/ccc-c/mini-riscv-os/blob/master/05-Preemptive/sys.s - -[task.c]:https://github.com/ccc-c/mini-riscv-os/blob/master/05-Preemptive/task.c - -[user.c]:https://github.com/ccc-c/mini-riscv-os/blob/master/05-Preemptive/user.c +[lib.c]: https://github.com/ccc-c/mini-riscv-os/blob/master/05-Preemptive/lib.c +[os.c]: https://github.com/ccc-c/mini-riscv-os/blob/master/05-Preemptive/os.c +[timer.c]: https://github.com/ccc-c/mini-riscv-os/blob/master/05-Preemptive/timer.c +[sys.s]: https://github.com/ccc-c/mini-riscv-os/blob/master/05-Preemptive/sys.s +[task.c]: https://github.com/ccc-c/mini-riscv-os/blob/master/05-Preemptive/task.c +[user.c]: https://github.com/ccc-c/mini-riscv-os/blob/master/05-Preemptive/user.c 專案 -- https://github.com/ccc-c/mini-riscv-os/tree/master/05-Preemptive 第三章的 [03-MultiTasking](03-MultiTasking.md) 中我們實作了一個《協同式多工》作業系統。不過由於沒有引入時間中斷機制,無法成為一個《搶先式》(Preemptive) 多工系統。 -第四章的 [04-TimerInterrupt](04-TimerInterrupt.md) 中我們示範了 RISC-V 的時間中斷機制原理。 +第四章的 [04-TimerInterrupt](04-TimerInterrupt.md) 中我們示範了 RISC-V 的時間中斷機制原理。 終於到了第五章,我們打算結合前兩章的技術,實作一個具有強制時間中斷的《可搶先式》(Preemptive) 作業系統。這樣的系統就可以算是一個微型的嵌入式作業系統了。 @@ -24,7 +19,7 @@ 首先讓我們和看系統的執行狀況,您可以看到下列執行結果中,系統在 OS, Task0, Task1 之間輪流的切換著。 -```sh +```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 @@ -115,7 +110,7 @@ void lib_delay(volatile int count) ## 作業系統 [os.c] -* https://github.com/ccc-c/mini-riscv-os/blob/master/05-Preemptive/os.c +- https://github.com/ccc-c/mini-riscv-os/blob/master/05-Preemptive/os.c 作業系統 os.c 一開始會呼叫 user_init() ,讓使用者建立 task (在本範例中會在 [user.c] 裏建立 user_task0 與 user_task1。 @@ -149,7 +144,7 @@ void user_init() { 然後作業系統會在 os_start() 裏透過 timer_init() 函數設定時間中斷,接著就是進入 os_main() 的主迴圈裏,該迴圈採用 Round-Robin 的大輪迴排班方法,每次切換就選下一個 task 來執行 (若已到最後一個 task ,接下來就是第 0 個 task)。 ```cpp - + #include "os.h" void os_kernel() { @@ -165,7 +160,7 @@ void os_start() { int os_main(void) { os_start(); - + int current_task = 0; while (1) { lib_puts("OS: Activate next task\n"); @@ -178,16 +173,102 @@ int os_main(void) } ``` -05-Preemptive 的時間中斷原理和上一章相同,都是在 [timer.c] 的 timer_init() 中設定好第一次的時間中斷。 +在 05-Preemptive 的中斷機制中,我們修改了中斷向量表: + +```c= +.globl trap_vector +# the trap vector base address must always be aligned on a 4-byte boundary +.align 4 +trap_vector: + # save context(registers). + csrrw t6, mscratch, t6 # swap t6 and mscratch + reg_save t6 + csrw mscratch, t6 + # call the C trap handler in trap.c + csrr a0, mepc + csrr a1, mcause + call trap_handler + + # trap_handler will return the return address via a0. + csrw mepc, a0 + + # load context(registers). + csrr t6, mscratch + reg_load t6 + + # jump to sys_kernel + la a1, os_kernel # a1 = os_kernel + csrw mepc, a1 # mepc = sys_kernel + mret +``` + +當中斷發生時,中斷向量表 `trap_vector()` 會呼叫 `trap_handler()` : + +```c= +reg_t trap_handler(reg_t epc, reg_t cause) +{ + reg_t return_pc = epc; + reg_t cause_code = cause & 0xfff; + + if (cause & 0x80000000) + { + /* Asynchronous trap - interrupt */ + switch (cause_code) + { + case 3: + lib_puts("software interruption!\n"); + break; + case 7: + lib_puts("timer interruption!\n"); + timer_handler(); + break; + case 11: + lib_puts("external interruption!\n"); + break; + default: + lib_puts("unknown async exception!\n"); + break; + } + } + else + { + /* Synchronous trap - exception */ + lib_puts("Sync exceptions!\n"); + while (1) + { + /* code */ + } + } + return return_pc; +} +``` + +跳到 `trap_handler()` 之後,它會針對不同類型的中斷呼叫不同的 handler ,所以我們可以將它視為一個中斷的派發任務中繼站: + +``` + +----------------+ + | soft_handler() | + +-------+----------------+ + | ++----------------+-------+-----------------+ +| trap_handler() | | timer_handler() | ++----------------+ +-----------------+ + | + +-------+-----------------+ + | exter_handler() | + +-----------------+ +``` + +`trap_handler` 可以根據不同的中斷類型,將中斷處理交給不同的 handler ,這樣子做就可以大大的提高作業系統的擴充性。 ```cpp #include "timer.h" -extern void os_kernel(); - // a scratch area per CPU for machine-mode timer interrupts. reg_t timer_scratch[NCPU][5]; +#define interval 20000000 // cycles; about 2 second in qemu. + void timer_init() { // each CPU has a separate source of timer interrupts. @@ -195,8 +276,8 @@ void timer_init() // ask the CLINT for a timer interrupt. // int interval = 1000000; // cycles; about 1/10th second in qemu. - int interval = 20000000; // cycles; about 2 second in qemu. - *(reg_t*)CLINT_MTIMECMP(id) = *(reg_t*)CLINT_MTIME + interval; + + *(reg_t *)CLINT_MTIMECMP(id) = *(reg_t *)CLINT_MTIME + interval; // prepare information in scratch[] for timervec. // scratch[0..2] : space for timervec to save registers. @@ -207,48 +288,33 @@ void timer_init() scratch[4] = interval; w_mscratch((reg_t)scratch); - // set the machine-mode trap handler. - w_mtvec((reg_t)sys_timer); - - // enable machine-mode interrupts. - w_mstatus(r_mstatus() | MSTATUS_MIE); - // enable machine-mode timer interrupts. w_mie(r_mie() | MIE_MTIE); } static int timer_count = 0; -void timer_handler() { +void timer_handler() +{ lib_printf("timer_handler: %d\n", ++timer_count); - os_kernel(); + int id = r_mhartid(); + *(reg_t *)CLINT_MTIMECMP(id) = *(reg_t *)CLINT_MTIME + interval; } ``` -但不同的是,[sys.s] 裏的 sys_timer 會呼叫上面 [timer.c] 裏的 timer_handler(),其中包含了 `os_kernel()` 這個函數,該函數會呼叫 [task.c] 裏的 task_os(), task_os() 會呼叫 [sys.s] 裏的 sys_switch 去切換回 kernel,於是作業系統就透過時間中斷將控制權強制取回來了。 +看到 [timer.c] 裏的 `timer_handler()`,它會將 `MTIMECMP` 做 reset 的動作,等到 `timer_handler()` 執行完畢,中斷向量表 `trap_vector()` 會將 mepc 指向 `os_kernel()` ,做到任務切換的功能。 -```s -# ... -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) -# ... -sys_kernel: - addi sp, sp, -128 # alloc stack space - reg_save sp # save all registers - call timer_handler # call timer_handler in timer.c - reg_load sp # restore all registers - addi sp, sp, 128 # restore stack pointer - jr a7 # jump to a7=mepc , return to timer break point -# ... -sys_timer: -# ... - csrr a7, mepc # a7 = mepc, for sys_kernel jump back to interrupted point - la a1, sys_kernel # mepc = sys_kernel - csrw mepc, a1 # mret : will jump to sys_kernel -# ... +最後,記得在 Kernel 開機時導入 trap 以及 timer 的初始化動作: + +```c= +void os_start() +{ + lib_puts("OS start\n"); + user_init(); + trap_init(); + timer_init(); // start timer interrupt ... +} ``` 透過時間中斷強制取回控制權,我們就不用擔心有惡霸行程把持 CPU 不放,系統也就不會被惡霸卡住而整個癱瘓了,這就是現代作業系統中最重要的《行程管理機制》。 @@ -259,29 +325,25 @@ sys_timer: 還好,這些事情已經有人做好了,您可以透過學習 xv6-riscv 這個由 MIT 所設計的教學型作業系統,進一步了解這些較複雜的機制,xv6-riscv 的原始碼總共有八千多行,雖然不算太少,但是比起那些動則數百萬行到數千萬行的 Linux / Windows 而言,xv6-riscv 算是非常精簡的系統了。 -* https://github.com/mit-pdos/xv6-riscv +- https://github.com/mit-pdos/xv6-riscv 然而 xv6-riscv 原本只能在 linux 下編譯執行,但是我把其中的 mkfs/mkfs.c 修改了一下,就能在 windows + git bash 這樣和 mini-riscv-os 一樣的環境下編譯執行了。 您可以從下列網址中取得 windows 版的 xv6-riscv 原始碼,然後編譯執行看看,應該可以站在 mini-riscv-os 的基礎上,進一步透過 xv6-riscv 學習更進階的作業系統設計原理。 -* https://github.com/ccc-c/xv6-riscv-win +- https://github.com/ccc-c/xv6-riscv-win 以下提供更多關於 RISC-V 的學習資源,以方便大家在學習 RISC-V 作業系統設計時,不需再經過太多的摸索。 -* [RISC-V 手册 - 一本开源指令集的指南 (PDF)](http://crva.ict.ac.cn/documents/RISC-V-Reader-Chinese-v2p1.pdf) -* [The RISC-V Instruction Set Manual Volume II: Privileged Architecture Privileged Architecture (PDF)](https://riscv.org//wp-content/uploads/2019/12/riscv-spec-20191213.pdf) -* [RISC-V Assembly Programmer's Manual](https://github.com/riscv/riscv-asm-manual/blob/master/riscv-asm.md) -* https://github.com/riscv/riscv-opcodes - * https://github.com/riscv/riscv-opcodes/blob/master/opcodes-rv32i -* [SiFive Interrupt Cookbook (SiFive 的 RISC-V 中斷手冊)](https://gitlab.com/ccc109/sp/-/blob/master/10-riscv/mybook/riscv-interrupt/sifive-interrupt-cookbook-zh.md) -* [SiFive Interrupt Cookbook -- Version 1.0 (PDF)](https://sifive.cdn.prismic.io/sifive/0d163928-2128-42be-a75a-464df65e04e0_sifive-interrupt-cookbook.pdf) -* 進階: [proposal for a RISC-V Core-Local Interrupt Controller (CLIC)](https://github.com/riscv/riscv-fast-interrupt/blob/master/clic.adoc) +- [RISC-V 手册 - 一本开源指令集的指南 (PDF)](http://crva.ict.ac.cn/documents/RISC-V-Reader-Chinese-v2p1.pdf) +- [The RISC-V Instruction Set Manual Volume II: Privileged Architecture Privileged Architecture (PDF)](https://riscv.org//wp-content/uploads/2019/12/riscv-spec-20191213.pdf) +- [RISC-V Assembly Programmer's Manual](https://github.com/riscv/riscv-asm-manual/blob/master/riscv-asm.md) +- https://github.com/riscv/riscv-opcodes + - https://github.com/riscv/riscv-opcodes/blob/master/opcodes-rv32i +- [SiFive Interrupt Cookbook (SiFive 的 RISC-V 中斷手冊)](https://gitlab.com/ccc109/sp/-/blob/master/10-riscv/mybook/riscv-interrupt/sifive-interrupt-cookbook-zh.md) +- [SiFive Interrupt Cookbook -- Version 1.0 (PDF)](https://sifive.cdn.prismic.io/sifive/0d163928-2128-42be-a75a-464df65e04e0_sifive-interrupt-cookbook.pdf) +- 進階: [proposal for a RISC-V Core-Local Interrupt Controller (CLIC)](https://github.com/riscv/riscv-fast-interrupt/blob/master/clic.adoc) 希望這份 mini-riscv-os 教材能幫助讀者在學習 RISC-V OS 設計上節省一些寶貴的時間! 陳鍾誠 2020/11/15 於金門大學 - - - -