特權架構
處理器在架構上一般都會有幾種特權模式,比如x86架構有“ring0~ring3”4種級別,一般操作系統內核和驅動運行在ring0級別,也就是最高級別,而普通的應用程序運行在ring3級別,也就是權限最低的級別;再比如arm架構有7種處理器模式,操作系統一般運行在Supervisor模式,而應用程序運行在User模式。
那么為什么處理器架構在設計時需要設計好幾種級別不同的模式呢?原因是為了不同作用的程序考慮的。比如一般操作系統享有最高級別的權限,可以訪問系統所有硬件,執行所有特權指令,例如設置MMU頁表等,那么設置MMU頁表這種操作能讓普通的應用程序來操作嗎,當然是不行的,所以操作系統和普通應用這兩種程序肯定是需要運行在不同級別的權限模式下的,如果普通程序強行去執行特權指令操作,要么沒有任何反應要么系統產生異常,這樣就從硬件上保證了安全性。
同樣的,RISCV架構下有三種特權級別,分別是Machine、Supervisor和User,簡稱M模式、S模式和U模式。M模式權限最高,在這個級別下的程序可以訪問一切硬件和執行所有特權指令;S模式一般用於運行操作系統,可以設置MMU使用虛擬地址;U模式一般是普通應用程序使用,權限最低。
M模式使用物理地址進行訪問,不經過MMU,但是有類似arm下cortex-m中的MPU功能;S模式可以通過設置MMU來使用虛擬地址訪問內存,所以像Linux這類操作系統都運行在S模式下。那么有人要問了,為啥RISCV架構特權模式設計成這樣,直接把M模式和S模式合二為一不行嗎?這個得從RISCV架構誕生背景來看了,RISCV架構誕生於2010年左右,這時不管是x86還是arm架構都發展得算是比較成熟了,所以RISCV架構設計時就定位了從微控制器到大型超級計算機都可以使用這個架構。在微控制器上使用的RISCV架構一般只有M模式,或者使用M和U兩種模式,類似於cortex-m架構的定位;而在帶MMU的芯片上,RISCV架構一般都使用M、S和U三種模式,這樣通過“拼積木”的方式就可以讓RISCV架構適用於各種場景了。
在arm下的應用程序通過“swi”指令可以將處理器從低特權級別切換到高特權級別,一般像Linux下的系統調用都是通過這種方式來使用的。類似的,在RISCV中,通過“ecall”指令可以從低特權切換到高特權,在U模式下執行就切換到S模式,在S模式下調用就切換到M模式。另外在RISCV中,默認產生中斷和異常時,處理器自動切換到M模式處理,可以通過中斷托管設置將一些中斷和異常直接交給S模式處理。RISCV的架構設計就決定了必須要有程序運行在M模式下,來為S模式提供一些基礎的服務,RISCV為此定義了SBI(Supervisor Binary Interface)接口規范,讓運行在S模式下的操作系統在不同的RISCV處理器上都可以使用標准的SBI接口來使用相應的功能,這個其實就有點類似於x86下的BIOS概念了,詳細的RISCV下中斷和異常處理以及SBI規范在后續章節會講解,這里只需要知道就可以。
中斷和異常處理
在RISCV架構設計中,有一系列的控制和狀態寄存器( Control and Status Registers)簡稱CSR,在三種特權級別下都有其對應的CSR,比如m模式下的命名都為mxxxx,s模式下的都為sxxxx等等。這些寄存器的作用類似於arm架構中的那些cp15寄存器,用於設置異常向量表、設置頁表基址、獲取異常信息等等。這些寄存器大多數都需要通過“csr”這類指令來進行訪問,也有一部分寄存器是采用mmio映射的,可以使用普通訪存指令訪問,比如timer相關的寄存器(后面會講)。這些寄存器比較重要的就是和中斷異常相關的,下面我們來一起看看這些寄存器。
1. M模式下重要的CSR
M模式下的比較重要的寄存器如下所示,當然除了下圖列出之外的寄存器,m模式下還有其他的一些寄存器,具體的請參考RISCV特權架構官方文檔。
上圖中的寄存器被分為四類,其中和Trap相關的寄存器比較重要,用於中斷和異常處理:
- 信息類:主要用於獲取當前芯片id和cpu核id等信息。
- Trap設置:用於設置中斷和異常相關寄存器。
- Trap處理:用於處理中斷和異常相關寄存器。
- 內存保護:作用類似於conterx-m中的mpu功能。
下面我們來着重介紹前三類寄存器。
1.1 Machine Information Registers
mvendorid、 marchid 和 mimpid 可以獲取芯片制造商、架構和實現相關信息,最重要的還是 mhartid 這個寄存器,RISCV中每個cpu核都被稱為一個hart,通過mhartid可以獲取當前cpu核的id號。
1.2 Machine Trap Setup
在RISCV中,中斷(interrupt)和異常(exception)被統稱為trap。在arm中我們知道中斷和異常是通過中斷向量表中不同入口調用不同的處理函數處理的,但是在riscv中,所有中斷和異常一般都是使用的同一個處理入口。在x86和arm下都存在中斷向量表的概念,用於定義不同異常和中斷的處理入口,但是在riscv下,一般是不存在中斷向量表這個概念的,只存在trap處理入口這個概念。為了表述上的方便,后續的章節都將trap處理入口稱為中斷入口,但是要明白這個入口不僅僅是處理中斷的,同時也是處理異常的入口。中斷入口在m模式和s模式下都有專門的寄存器需要設置,在本小節我們只看m模式下的相關寄存器,在使用中斷和異常處理之前需要進行一些設置,下面就來看看這些寄存器如何設置。
1.2.1 mtvec
可以看出mtvec需要中斷入口地址是4字節對齊的,因為最低兩個bit是用於設置中斷模式的,其模式定義如下:
- Direct模式:所有的中斷和異常使用同一個中斷入口地址,一般都會設置為這種模式。
- Vectored模式:所有異常使用同一個入口地址,但是不同的中斷使用不同的入口地址。
1.2.2 mstatus
這個寄存器顧名思義是用來控制cpu核當前的一些狀態信息的,比如全局中斷使能等,寄存器的格式如下:
- 紅框內的位域用來控制全局中斷的使能,SIE控制S模式下全局中斷,MIE控制M模式下全局中斷。這個有點像arm里cpsr中的F位,只是在RISCV架構下還分為S模式和M模式來控制,像但是不完全像。
- 綠框里的位域用來記錄發生中斷之前MIE和SIE的值。SPIE記錄的是SIE的值,MPIE記錄的是MIE的值。
- 藍色框內位域用來記錄當特權級別由低到高發生變化時(比如執行ecall指令),之前的特權級別。當變化后的特權級別是S模式時,SPP表示變化之前的特權級別是S模式還是U模式,所以只需要1位就可以表示;當變化后的特權級別是M模式時,MPP表示變化之前是S模式還是U模式還是M模式,由於有三種情況,所以需要使用2位來表示。
- 注意:當發生中斷時,SIE和MIE被硬件自動設置為0,用來屏蔽中斷,這個行為和大部分架構都一樣,同時MPIE和SPIE被硬件自動設置為MIE和SIE的值,如果特權級別還發生改變的話,之前的特權級別是記錄在SPP或者MPP中的。當從中斷中返回時,SIE和MIE被自動設置為MPIE和SPIE的值,同時MPIE和SPIE被自動設置為1,特權級別恢復為MPP或者SPP記錄的級別,然后MPP或者SPP被設置為U模式。
1.2.3 mie
在RISCV下,將中斷(interrupt)又細分為三種類型:定時中斷(timer)、核間中斷(soft)、中斷控制器中斷(external)。定時中斷可以用於產生系統的tick,核間中斷用於不同cpu核之間通信,中斷控制器則負責所有外設中斷。這個設計和arm下有點不一樣,在arm多核下,架構中的定時器中斷、核間中斷和外設中斷都是統一由中斷控制器管理的,而在RISCV中定時器和核間中斷是分離出來的,這兩個中斷被稱為CLINT(Core Local Interrupt),而管理其他外設中斷的中斷控制器則被稱為PLIC(Platform-Level Interrupt Controller)。每個核都有自己的定時器和產生核間中斷的寄存器可以設置,這些寄存器的訪問不同於其他的控制狀態寄存器,采用的是MMIO映射方式訪問,比如下圖所示為SIFIVE FU540的CLINT寄存器表:
圖中的msip用於產生m模式下的核間中斷,mtime可以讀取出當前計數器的值,mtimecmp用於設置比較值,當mtime的值增加到mtimecmp的值時就可以產生中斷。這些寄存器的具體用法在后續的裸機程序編寫章節會講解,這里只需要簡單了解即可。
上述講解的三種中斷類型在m模式和s模式下都有相應的中斷使能位設置,這是通過mie寄存器實現的,mie寄存器的格式如下:
- MSIE、MTIE、MEIE這三個位域分別控制m模式下核間中斷、定時中斷、中斷控制器中斷的使能狀態。
- SSIE、STIE、SEIE這三個位域分別控制s模式下核間中斷、定時中斷、中斷控制器中斷的使能狀態。
1.2.4 medeleg 和 mideleg
RISCV下默認所有中斷和異常都是在m模式下處理的,但是有些時候我們需要將中斷和異常直接交給s模式處理,這就是RISCV中的中斷托管機制。通過mideleg寄存器,可以將三種中斷交給s模式處理,通過medeleg寄存器,可以將異常交給s模式處理。下面來具體看看這些寄存器格式。
當我們想把中斷交給s模式處理時,我們可以設置mideleg寄存器,這個寄存器格式如下:
- bit[1]用於控制是否將核間中斷交給s模式處理。
- bit[5]用於控制是否將定時中斷交給s模式處理。
- bit[9]用於控制是否將中斷控制器管理的中斷交給s模式處理。
注意對於核間中斷和定時中斷而言,即使使能了mideleg中對應的bit位,當產生相應中斷時,還是先進入m模式進行處理,然后可以通過設置mip寄存器(下一小節講解),在退出m模式中斷時就可以進入s模式的中斷處理函數中處理。
當我們想把異常交給s模式處理時,我們可以設置medelrg寄存器,這個寄存器格式如下:
可以看出來有很多異常是可以設置到s模式下處理的,但是實際使用時並不是所有異常都要交給s模式處理的,比如bit[9]代表的異常還是要交給m模式處理,因為像獲取芯片id、cpu核id、設置timer等操作只能在m模式下進行,所以s模式通過SBI接口(后面會講)使用“ecall”切換到m模式調用不同的服務,所以bit[9]代表的異常必須被m模式處理而不能交給s模式處理。
1.3 Machine Trap Handling
當產生中斷或者異常時,會有一些信息保存在相應的寄存器中,下面我們就一起來看看這些寄存器。
1.3.1 mepc
在arm架構中,當發生中斷或異常時,硬件自動將要返回的地址保存在lr寄存器中。類似的,在RISCV下產生中斷或異常時,硬件自動將返回地址保存在mepc寄存器中,當在中斷處理中返回時,硬件自動將mepc中的地址賦值給pc運行。
要注意的時,在RISCV架構中,當產生的時異常時,mepc中保存的是產生異常那條指令的地址,而不是其下一條指令地址,這么設計的原因是希望產生異常時,軟件開發人員對相應異常做出處理,當處理完之后再次給一個運行之前產生異常指令的機會,比如缺頁異常就是通過這種機制來運行的。當不需要再次運行產生異常那條指令時,需要在中斷處理時手動將mepc的值加4,這樣中斷返回時就是運行產生異常那條指令的下一條指令。當產生的是中斷時,mepc直接保存的就是被中斷指令的下一條指令的地址,所以需要做修正。
1.3.2 mcause和mtval
當產生中斷和異常時,mcause寄存器中會記錄當前產生的中斷或者異常類型,而mtval則針對某些異常會記錄一些輔助信息。我們來看看mcause寄存器的格式:
寄存器的最高位用來表示產生的是中斷還是異常,1表示中斷0表示異常。剩下的位域表示中斷或者異常的具體類型,如下所示:
可以看出來中斷有6種類型,分別表示m和s模式下的定時、核間、中斷控制器這三種中斷,而異常的類型就比較多了。
1.3.3 mip
這個寄存器可以表明當前是否產生了某種中斷,其格式如下所示。
- MSIP表示m模式核間中斷,此位只讀,其狀態反應的是CLINT中對應的核間中斷設置寄存器最低位的狀態,設置CLINT核間中斷設置寄存器最低位為1則產生核間中斷,置0則清除核間中斷。
- MTIP表示m模式定時中斷,此位只讀,其狀態通過設置CLINT中對應的mtimecmp寄存器來清零。
- MEIP表示m模式中斷控制器中斷,此位只讀,其狀態通過具體的中斷控制器寄存器設置來清零。
- SSIP表示s模式核間中斷,此位在s模式只讀(s模式下有sip寄存器,下面會講),在m模式下可讀寫,通過設置此位,可以進入s模式核間中斷處理。
- STIP表示s模式定時中斷,此位在m模式下可讀寫,通過設置此位,可以進入s模式定時中斷處理。
- SEIP表示s模式中斷控制器中斷,此位在m模式下可讀寫,通過設置此位,可以進入s模式中斷控制器中斷處理。
2. S模式下重要的CSR
S模式下的CSR寄存器大部分都和M模式下的類似,只不過是可以在s模式下進行訪問而已。因為在m模式可以訪問其他模式下的寄存器,在其他模式下只能訪問他自己模式下的csr寄存器。S模式下的一些csr寄存器如下所示。
可以看出大部分的寄存器都和m模式下的類似,作用也是一樣的,這里就不再贅述了。我們這里看一個m模式下沒有的寄存器satp,這個寄存器是s模式下用來設置頁表基址的,其格式如下。
- PPN位域用於填寫頁表在內存中的物理基址。
- ASID可以先不關心,當作都為0。
- MODE位域用來選擇是否開啟頁表,如果是64位還用來選擇虛擬地址翻譯的位數,如下:
如果是0 表示禁用頁表翻譯功能,64位架構下一般虛擬地址選用sv39。