iT邦幫忙

2022 iThome 鐵人賽

DAY 7
0

前言

由於下面在追蹤 xv6 的相關議題,如架構與啟動,會涉及到一些暫存器的操作,以及一些暫存器操作的指令,或是在進行 System call 時,涉及的特權模式的變化。因此在這一邊簡單的介紹 RISC-V 相關的暫存器以及 CSR 和相關的指令以及特權模式。最後面提及作業系統中 Microkernel 和 Monolithic kernel 設計上的差異。

簡介RISC-V

ISA 指令集架構 (Instruction Set Architecture) :
為整個電腦架構的一部份,主要是與程式設計的部分有關,包含指令集,暫存器等等, 指令集會在微架構 (microarchitecture) 上執行。微架構用來描述電腦各個元件之間如何構成與如何去執行指令集。

不同微架構的電腦可以執行相同的指令集,如 Intel core 和 AMD Ryzen 共用 x86 指令集架構。指令集架構中的指令集可能會有不同的數量與長度,指令集可以分成 CISC (Complex Instruction Set Computer) 和 RISC (Reduced Instruction Set Computer),RISC-V 顧名思義屬於 RISC 的部分。

RISC-V 為開源指令集架構 (ISA),起源於 2010 年加州大學柏克萊分校。RISC-V 並非單一的 ISA,而是指整個 RISC-V ISA 的家族 RISC-V 有一個基本的指令集,以及許多擴充指令集 (下面將介紹 RISC-V 指令集使用了模組化設計)。

RISC-V 指令集架構

指令架構命名

RV[AAA][BBB]
  • RV : 表示為 RISC-V 架構
  • [AAA] : [16,32,64...]表示 user address space 的大小以及整數暫存器檔案的寬度
  • [BBB] : 表示支援的擴充指令集
Extension Description
I 整數
M 整數乘法和除法
A Atomics
F 單精度浮點數
D 倍精度浮點數
G General Purpose (所有以上指令集的總和) = IMAFD
C 16-bit Compressed Instructions (壓縮指令集)
Xext Non-standard extension "ext"

RISC-V 與其他 ISA 不同之處在於使用了模組化的設計,RV32I 為最基本 (Basic) 的指令集架構 (ISA),不會如同 x86 指令集隨著時間不斷的增長 (x86為了相容性考量,需要相容於舊版本的系統,因此指令集十分的龐大),模組化設計可以根據使用需求進行指令集的擴充,硬體可以選擇是否要包含那一些擴充指令集,這讓 RISC-V 能夠變得更小,更有效率,更低功耗,可以運用在嵌入式的系統上,且編譯器可以更方便的對程式碼進行優化。

而擴充指令集的命名方式如同上面所示,RV32IMFD 是將 RV32I 加上 RV32M, RV32F, RV32D 的擴充。

RISC-V function call

函式呼叫可以分成6個部分

  1. 將參數儲存。
  2. 跳轉到函式開始的記憶體地址 (使用jal跳躍)。
  3. 獲得函式所需要的資源 (變數,暫存器等等)。
  4. 執行函式中的指令。
  5. 將回傳值儲存到函式呼叫者 (Caller) 能夠存取到的記憶體地址,並且恢復暫存器,釋放函式使用的資源。
  6. 返回函式調用的記憶體地址 (使用ret)。

為了性能考慮,我們最好把函式的一些參數都放在暫存器中,而不是將變數通通推入 Stack 中讓我們需要不斷的去存取記憶體,而如果暫存器數量少,我們會需要頻繁的恢復暫存器以及儲存暫存器的值,這也會需要不斷的存取記憶體。

由於在 RISC-V 中有足夠多的暫存器,可以確保函式呼叫有足夠好的效能,既可以把函式變數 (操作數) 存放於暫存器中,又可以避免不斷的存取記憶體恢復暫存器的值。RISC-V 中的暫存器分為臨時暫存器(函式呼叫時不保留暫存器的值),另外一種為儲存暫存器。使用 Caller-Saved 和 Callee-Saved 做為表示。

32個暫存器 (64位元)

以下為32個暫存器的 RISC-V ABI 名稱 (由RISC-V ABI所定義的暫存器名稱) 和他們在函式呼叫中是否需要保留暫存器的值。

Register ABI Name Description Saver
x0 Zero 數值為0且無法更動,可用於設置暫存器的值
x1 ra 存放 Return address Caller
x2 sp Stack pointer Callee
x3 gp Global pointer
x4 tp Thread Pointer
x5 t0 Temporaries/Working register Caller
x6~x7 t1~t2 Temporaries Caller
x8 s0/fp Callee-Saved/frame pointer Callee
x9 s1 Callee-Saved Callee
x10~x11 a0~a1 Function arguments/return values Caller
x12~x17 a2~a7 Function arguments Caller
x18~x27 s2~s11 Saved registers Callee
x28~x31 t3~t6 Temporaries Caller
  • Return Address : 存放函式執行結束時需要返回的記憶體地址 (被呼叫函式 (Callee) 回到呼叫函式 (Caller) ),當一個函式執行結束並且回傳時,便會回到此暫存器所存放的記憶體地址,而這個暫存器又會被稱為 Caller Saver。
  • Thread pointer : 在 xv6 中會包含 Thread ID。
  • Global pointer : 由編譯器使用,裡面的值設置之後基本上就不再進行更動了,用來存取一些全域或是區域變數,用來增進效能。
  • Callee-Saved : 函式呼叫時不可以修改此暫存器的值,如果需要使用到這一些暫存器,需要先將其內容的值儲存起來才可以使用,並且在函式結束前,需要復原原先的值,反之則為 Caller-Saved。

RISC-V privilege level(特權等級)

Machine Mode (M) Level 3.

  • 最高級,擁有最高權限的模式
  • 在開機,或是重啟的時候,核心最初進入的模式
  • xv6 中此模式運用在啟動 (Startup) 以及初始化 (initialization) 的時候
  • Timer Interrupts 需要運作在此模式底下

Supervisor Mode (S) Level 1.

  • 所有 Kernel Program 都是運作在這個模式底下
  • 有一些需要特殊權限 (Privileged) 的指令需要在此模式底下進行 (或是 Machine Mode),而不能運作到 User Mode 上

User Mode (U) Level 0.

  • 所有 User Program 都是運作在此模式底下
  • 如果在 User Mode 執行一些需要特殊權限 (privileged) 才能夠執行的指令,會觸發 trap 切換到 Supervisor Mode 且 kernel 會中止 (abort) 該 Process
  • 如果應用程式需要 System call,如 read,則必須切換到 kernel (entering kernel) 中,CPU 提供了一條稱為 ecall 的指令可以從 User Mode 切換到 Supervisor Mode,從 kernel 指定的入口 (entry) 進入到 kernel。

Control And Status Registers (CSR)

CSRs 為 CPU 中儲存各種資訊的暫存器(CSRs 為 CSR 的複數,表示多個 CSR),在 RISC-V 中總共有4096個 CSR (以下只會介紹部分),以 m 開頭的 CSR 表示需要至少 Machine Mode 才可以使用,s 開頭表示至少需要 Supervisor Mode 才可以使用。

前面說到,RISC-V 依靠了許多擴充來擴充指令以及暫存器,而用於擴充 CSR 以及相關操作指令的擴充為 Zicsr。

Machine Supervisor Description
mhartid Hart ID
mstatus sstatus Status Register
mtvec stvec Trap Vector/Handler address (trap 發生時,需要跳轉到的記憶體地址)
mepc sepc Previous (Exception) Program Counter (當 trap 發生時,先前的 Program Counter)
scause Trap cause code (Trap發生原因)
mscratch sscratch for trap handler
satp address translation pointer (指向 page table)
mie sie interrupts enable (是否允許中斷)
sip interrupts pending
medeleg
mideleg

上面這一些 CSR,也有對於 CSR 進行操作的指令,注意到所有對於 CSR 讀,寫,修改的指令都是屬於原子操作,像是 CSRRW,CSRRS,CSRRC...。

作業系統強隔離

User Mode 和 Kernel Mode

Kernel Mode 和上面在 RISC-V 中提及的 Supervisor Mode 為同一個 Mode,由於在 xv6 中 Machine Mode 使用的不多,因此這裡的 Kernel Mode 代指 Supervisor Mode。

當 CPU 的 thread(Hart) 處於 User Mode 時,只能執行一些非權限 (unprivileged instructions) 的指令,諸如 ADD, SUB, JRC 等等,而處於 Kernel Mode 時,可以執行一些特殊權限指令 (privileged instructions),例如禁用中斷 (interrupts),設置 page table 相關的暫存器等等,在處理器上有各式各樣的狀態被暫存器所保留 (也就是 CSRs),我們只能通過一些特殊權限指令進行變更,以下舉例:

  • csrr a0, sstatus : 將暫存器 sstatus 的值移動到暫存器 a0 中 (read CSRs)。
  • csrw sstatus, a0 : 將暫存器 a0 的值移動到暫存器 sstatus 中 (write CSRs)。
  • csrrw a0, mscratch, a0 : 將暫存器 mscratch 的值移動到暫存器 a0 中,並將暫存器 a0 的值移動到暫存器 mscratch,此交換具備原子性,也就是一個操作內所有動作,要就是全部都發生,不然就是全部都不發生 (atomic swap)。

虛擬記憶體概述

前面我們知道為了讓每一個程式之間不相互汙染記憶體,我們使用了 Process 這個概念表示一個執行中的程式,並且每一個 Process 都有自己的 Memory space,而這其中就使用到了虛擬記憶體的特性,基本上 CPU 包含了 page table,而 page table 會將虛擬記憶體地址和實體的物理記憶體地址進行對應。

每一個 Process 有自己的 page table 與物理記憶體地址進行對應,也就是每一個 Process 只能存取 page table 對應到的記憶體地址,而作業系統會設置 page table 使得每一個 Process 之間的實體可存取的記憶體地址不會發生重合的情況,讓 Process 不能夠去存取其他 Process 的記憶體地址,虛擬記憶體的特性提供了記憶體上的強隔離性。

System call 分析

有了上面大略的 RISC-V 概念後,我們可以對前幾天一直使用到的 System call 進行一些分析。我們在 Day-02 看到在 xv6 在 /kernel/syscall.h 中定義了每一個 System call 對應到的編號,當應用程式需要某一項 System call 時,首先會將 System call 對應到的號碼存入暫存器 a7 中,接著會執行 ecall,並且傳入一個引數代表我們想要呼叫的 System call。ecall 會讓我們從 kernel 的進入點進入到 kernel,並執行對應的 System call,執行完畢後將控制權交還給 Process。

無論是 Shell 或是其他的應用程式,當他們執行 fork() 的時候,並不是直接呼叫作業系統對應到的函式,而是通過 ecall,並將 fork()syscall.h 中對應到的數字作為引數傳入,通過 ecall 跳躍到 kernel。

在 user 資料夾中有一個名為 usys.pl 的檔案,功用為生出對應的組合語言,我們可以通過閱讀 usys.pl 得知一些 System call 的行為

sub entry {
    my $name = shift;
    print ".global $name\n";
    print "${name}:\n";
    print " li a7, SYS_${name}\n";
    print " ecall\n";
    print " ret\n";
}

可以看到在第5行的地方,使用了 li (load immediate, 此為pseudo instruction,也就是多條指令合併成一條指令,方便使用)指令,會將 SYS_${name} 的值放入 a7 暫存器中,也就是對應到將 System call 的編號放入 a7 暫存器中。

接著在第6行的地方,執行了 ecall,進入到 kernel 執行對應的 System call。

Kernel organization

剛剛上面我們提到可以通過 ecall 來進入 Kernel 執行 System call,Kernel 會檢查 System call 的參數是否會觸發一些異常的行為,導致一些 BUG 被觸發。而 kernel space 有時候會被稱為 TCB (Trusted Computing Base)。

而在作業系統設計時我們可能會面臨到一個問題,什麼樣的程式要在 Kernel Mode 底下執行,什麼程式要在 User Mode 底下執行?

Monolithic Kernel Design

其中一個做法為讓整個作業系統都在 kernel mode 底下執行,例如在 xv6 中,所有作業系統的服務都是在 kernel mode 底下執行,而這種kernel 的設計模式稱為 Monolithic Kernel Design。

  • 在 Monolithic Kernel Design 底下,由於作業系統運作在 kernel mode 底下,擁有對於所有硬體設備完整的存取權限,設計的時候便不需要注意各個元件之間的權限問題,此外,作業系統不同部分也可以更方便的進行合作,共享記憶體等等,也可以擁有更好的性能,如 linux。
  • 缺點為由於整個作業系統都在 kernel mode 底下,作業系統有著大量的程式碼,任何程式碼的 BUG 因為在 kernel mode 底下,都有可能成為重大的 BUG。

Micro Kernel Design

為了避免大量的程式碼運作在 kernel mode 底下造成的一些安全性風險,有另外一種設計模式為 Micro Kernel Design,希望在 kernel space 底下執行的程式碼量越少越好,因此將作業系統劃分成許多塊模組,只有其中一塊在 kernel space 底下執行,剩下在 user space 執行,這樣在 user space 上面的程式在執行時遇到錯誤不至於造成太大的危害。MINIX 3 就是使用 Micro Kernel 的設計。

而許多程式都在 user space 中執行,當我們需要一些 System call,與檔案系統互動時,例如 exec(),我們需要一些方式。Shell 會通過 kernel 所提供了 IPC (Inter-Process Communication) 送出一則訊息給 kernel,kernel 查看此訊息發現是給檔案系統的,再將訊息交由檔案系統處理。檔案系統執行完 exec() 後也會通過 IPC 回傳訊息給 Shell。而這裡我們可以看到,我們從 User space 通過 IPC 到 Kernel space,接著從 Kernel 通過 IPC 再進入到 User space,跳轉過程是 Monolithic Kernel Design 將近兩倍,因此 Micro Kernel Design 會有一些性能上的損失。且因為相較於 Monolithic Kernel Design,無法有效的共享記憶體 (Page cache),較難獲得更好的性能。

xv6 為 Monolithic Kernel Design 的設計。


< Difference Between Microkernel and Monolithic Kernel >

reference

計算機指令及架構
osdev.org RISC-V
xv6-riscv
Operating System Concepts, 9/e
RISC-V xv6 Book


上一篇
Day-05 I/O 重導向, pipe 概念
下一篇
Day-07 Linker Script File: kernel.ld
系列文
與作業系統的第一類接觸 : 探索 xv631
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言