はじめに
xv6-riscvは、本ブログでも何度か記事にしたことのあるMITの教育用のOSであるxv6(x86 version)のRISC-V versionである(2019/9/11に登場したっぽい)。x86(32bit)のとは違い、RV64(64bit)のOSである。
本記事では、RISC-Vのprivilegeとmode遷移について、xv6-riscvを例にして解説していきたい。x86の仕組みと結構違う1ので触れる価値があると思った。なお、本記事はxv6のx86 versionについては知らなくても問題ないようにしているが、もしそちらの方に興味のある方はこちらにまとめてあるので参考にしてほしい。
Reference
- [1] https://github.com/mit-pdos/xv6-riscvhttps://github.com/mit-pdos/xv6-riscv/tree/riscv source code(今の所の最新はこちら)
[2] https://pdos.csail.mit.edu/6.828/2019/xv6/book-riscv-rev0.pdf 解説
[3] https://riscv.org/specifications/privileged-isa/ Volume II: Privileged Architecture(June 8, 2019)
[4] https://riscv.org/specifications/ Volume I(June 8, 2019): Unprivileged ISA ret疑似命令とかを参照している
[5] RISC-V原典 オープンアーキテクチャのススメ 主にch.10のRV32/64特権アーキテクチャを参考にした
今回は、特にReference[3]のPrivileged Architectureをしっかり読み解くことになる。
準備
RISC-Vのprivilegeについて述べる前に、registerについて全体像を簡単にまとめる。その中でもCSR(Control and Status Register)はprivilegeと切っても切り離せない関係にある。
general register(integer and floating-point registers)
これはrax, rbxとかと似たような感じ。xv6-riscvではcpuid(Hart ID; hardware thread ID)の値をtp registerに起動直後にsetしている。
これらの他にpc(program counter)というregisterもある2。これはx86でいう%eip registerであるが、riscvではinstruction pointerのことをprogram counterと呼ぶ。
CSR
RISC-Vでは上記のreigsterの他にCSR(Control/Status Register)というregister達が用意されている(Reference[3]のpriviledged architecture ch.2に詳しい説明がある)。CSRをRead/Writeするための特別なinstruction(csrw
/csrr
)によって、これらのregisterを操作でき、paging設定、interrupt, privilege等の設定ができる( e.g) csrr t1, mstatus
)。
# xv6-riscvに登場する主なCSR達 - CSR: Control and Status Register(see ch.2) - M-mode register - mstatus: - SXL: bit35-34; Supervisor XLEN - UXL: bit33-32; User XLEN - MPP: bit11-10(Machine Privious Priviledge) - mpec: Machine Exception Program Counter(set `main` function pointer) - medeleg: machine exception delegation register(delegated to S-mode) - mideleg: machine interrupt delegation register(delegated to S-mode) - mscratch: Machine Scratch Register(set address pointed at `scratch` variable) - mtvec: Machine Trap-Vector Base-Address Register(set `timervec` function pointer) - mie: machine interrupt enable - S-mode register: - satp: Supervisor address translation and protection(similar to %cr3(Page Directory Base Register) in x86) - MODE: bit63-60 - Page-based 39-bit virtual addressing: 4b1000(in xv6-riscv) - Page-based 48-bit virtual addressing: 4b1001 - ASID(Address Space IDentifier): ?? - PPN(Physical Page Number): (page base directory address) >> 12 - stvec: Supervisor Trap Vector Base Address Register - BASE: base address - MODE: bit1-0; direct(0) - sip: Supervisor Interrupt-Pending register - address: bit63-10; - flags: bit9-0 - sie: Supervisor interrupt-enable register.(used in intr_on/intr_off function) - sepc: Supervisor Exception Program Counter
SXLやUXLは2bit指定だが、以下のように設定できる3:
Note) 本記事ではあるCSRのあるビット(例えば、mstatusのMPP bits)を mstatus.MPP
と表記することにする。
Note) 実はcsrr rd, csr
とcsrw csr, rs
はそれぞれ、csrrs rd, csr, zero
とcsrrw zero, csr, rs
のpseudoinstruction4である。詳細はReference[4]の9.1 CSR Instructionsを参照のこと。
概要
x86では、実質的にring-3(user mode)とring-0(kernel mode)しか用いられていないが、xv6-riscvの場合、privilegeの高い順にM-mode(machine mode) > S-mode(Supervisor mode) > U-mode(User mode)が存在している5:
結論から言うと、xv6-riscvではざっくり、以下のように遷移する(後ほど詳しく解説する):
power reset直後はM-mode(machine mode)である6。
3つのmodeが存在する理由
trap7について触れる前になぜM-mode(machine mode), S-mode(supervisor mode), U-mode(User mode)という3つのprivilege modeが存在するのだろうか。x86では実質的にring-0(kernel mode)とring-3(user mode)という2つのprivilege modeしか用いられていないので、これは大きな違いである。
理由は以下のようである(主に、Reference[5]のch.10.4を参考にした)。
単純な組込みシステムではM-mode(Machine mode)のみサポートされているが、信頼できないコードの実行に関してはM-modeよりも制限されたモードで動作してほしい。その用途でU-mode(User mode)が用意されている。M-modeとU-modeを実装するプロセッサはPMP(Physical Memory Protection)と呼ばれる機能を備える。具体的にはpmpcfg0~pmpcfg15というCSRを用いて物理メモリの保護ができる。
しかしPMPはあくまで物理メモリ上の保護機能なので、ある程度大きなデータでも物理メモリ上に連続して配置されている必要があり、物理メモリの断片化が発生してしまう。現代のOSではpaging機能によりこの問題を解消しているが、riscvでもpaging機能がサポートされてほしい。
そこでRISC-Vでは、上記の2種類のmodeの他に、S-mode(Supervisor mode)というmodeも用意してpagingをサポートすることにしている。具体的には、satpというCSRを用いてpage directory tableのaddressが指定できる。
xv6-riscvではいくつかの場合(boot時の最初期とlocal interruptが起こった直後)を除いて、基本的にS-modeとU-mode上で動作する。詳しくはprivilegeの遷移例
の章で述べたい。
各モードのCSRのbitの解釈について
Reference[3]を注意深く読むと、mstatusとsstatus CSRの両方に同じ内容のbitがあることに気づくだろう(下図だと、SIE(bit1), UIE(bit0), SPIE(bit5), UPIE(bit6)など)。
Reference[3]のch.4.1には次のように記載されている:
The supervisor should only view CSR state that should be visible to a supervisor-level operating system. In particular, there is no information about the existence (or non-existence) of higher privilege levels (hypervisor or machine) visible in the CSRs accessible by the supervisor.
The sstatus register is a subset of the mstatus register. In a straightforward implementation, reading or writing any field in sstatus is equivalent to reading or writing the homonymous field in mstatus.
S-modeの場合、例えば、sstatusを介してSIE(Supervisor-mode Interrupt Enable)やUIE(User-mode Interrupt Enable)がRead/Write可能であるが、より権限の高いMIE(M-mode Interrupt Enable)はRead/Writeできない8。
また、sstatusとmstatusの同名のbit(SIEとかUIEとか)は同じものとみなして良い。例えば、sstatus.SIEに1を書き込む
<=>mstatus.SIE=1を書き込む
と同じ。
mie/sieとかmip/sipに関しても、上記のmstatus/sstausで述べたことと同様のことが言える。
Note) 以下では断りなく、sstatus.SIEのように(sstatusかmstatusかどちらでも同じなのであまりこだわることなく)記述するが、mstatus.SIEとsstatus.SIEはお互いに同一視できることに留意すること。
trap
trapはexceptionとinterruptの総称である。遷移の種類としてReference[3]では次の様に2種類に分類されている:
horizontal traps:
S-mode -> S-mode
( e.g.): 入れ子のtrap)とかM-mode -> M-mode
9に遷移するtrapのことvertical traps:
U-mode -> S-mode
とかU-mode -> M-mode
、S-mode -> M-mode
に遷移するtrapのこと
horizontal trapsの際は事前にInterrupt EnableがONになっている(M-modeの場合、mstatus.MIE=ON, S-modeの場合、sstatus.SIE=ONとなっている)ことが発動条件であるが、vertical traps(例えば、U-trap -> S-trap
)の場合はsstatus.SIEのbitが0か1かに関わらず trapが発動する10 (sstatus.SIEのbitは無視される)。
ちなみに、trapによってS-mode -> U-mode
の様にless-privileged modeに遷移することはない11。
medeleg/mideleg instruction
実はRISC-Vでは、デフォルトでexception/interruptのeventが全てM-modeに遷移する12ようになっている。これは、簡易な組み込み機器(M-modeとU-modeのみサポートする機器)に対応するためである。しかしながらOSではページング機構をもつS-modeに遷移できれば十分である13。
そこで、riscvではmedeleg
(machine exception delegate)とmidgleg
(machine interrupt delegate)という2つのCSRが用意されている。各ビットはどのinterruptをS-modeに移譲する(delegate)かを表す。これにより、interrupt/exceptionが生じた際にS-modeに遷移できる。
xv6-riscvでは https://github.com/mit-pdos/xv6-riscv/blob/9ead904afef8d060c2cc5cee6bd8e8d223de8c40/kernel/start.c#L36-L38 のように、Reserved bit以外はすべて1に設定されている。
Note) 実はxv6-riscvの場合、local timer interruptだけはtrapによってM-modeに遷移する(S-modeでなく)のであるが、ややこしいので次章で詳しく述べたい。
mret, sret instruction
ここまではtrapが生じたとき、horizontalかvertical(more-privileged)にprivilege modeが遷移すると言うお話だった。しかし、このままでは遷移先のmodeのままで前のmodeに戻れないので困る。
そこで、RISC-Vではmret/sret(machine/supervisor return) instructionによって、より低い(or同じ)interruptに遷移することができる14。どこに戻れるかはmstatus.MPP(2bit; mretの場合)またはsstatus.SPP(sretの場合)に以下の値(下図)のどれかが入っている(またはsoftware側で更新もできる):
Note1) mretはM-modeにいるときにしか実行できない15。sretは仕様上はM-mode or S-modeの両方で実行できるが、普通はS-modeでしか用いないと思われる。
Note2) 遷移先のmodeの指定はmstatus.MPP/sstatus.SPPにて設定できるが、どのinstruction(program counter)に飛ぶかはmepc(mretの場合), sepc(sret) CSRが管理している。これらのCSRも自身で指定することができる。これらは次章で具体的に扱う。
Note3) RISC-Vにはret
もあるが、これはpseudoinstruction(疑似命令)と言われるもので、jalr x0, 0(x1)
(つまり、jalr zero, 0(ra)
, ra: return address)のsyntax sugarなので、privilege modeの遷移との関係はない16
software generated interrupt
前節までは、interruptが外部環境により発生するとしていた(それが一般的)が、sip.SSIP(S-mode Software Interrupt Pending)をsoftware的な意味でONにすることで、interruptを都合の良いタイミングで発生させることができる。遷移先のmodeにてOFFにする必要がある17。例えば、xv6-riscvでは
[M-mode] https://github.com/mit-pdos/xv6-riscv/blob/9ead904afef8d060c2cc5cee6bd8e8d223de8c40/kernel/kernelvec.S#L114 にてsip.SSIP(bit1)をONにする
[S-mode] https://github.com/mit-pdos/xv6-riscv/blob/9ead904afef8d060c2cc5cee6bd8e8d223de8c40/kernel/trap.c#L206 にてsip.SSIPをOFFにする
となっている。
xv6-riscvを例にしたprivilegeの遷移例
概要が長かったが、本章ではxv6-riscvのソースコードを例にして、trapが生じたときのprivilegeの遷移パターンを前章の内容を踏まえた上で追っていく。
Note) x86では、interrupt/exception発生直後はレジスタの内容(ss, esp, eip, elfags, cs, error code)がメモリ上のstackに格納される18が、RISC-Vの場合、全てCSRにsave/restoreされる。おおよそ以下の対応となる。sp registerはsscratch(in S-mode)/mscratch(in M-mode)により手動でsave/restoreする。
x86 | RISC-V(遷移先がS-mode) | RISC-V(遷移先がM-mode) | remarks |
---|---|---|---|
%ss(stack) | - | - | RISC-Vではsegmentationはない |
%esp(stack) | x | x | RISC-Vの場合、手動でセットする |
%eflags.IF(stack) | sstatus.SPIE | mstatus.MPIE | Machine/Supervisor Privious Interrupt Enable |
%cs(stack) | sstatus.SPP | mstatus.MPP | Machine/Supervisor Privious Privilege |
%eip(stack) | sepc | mepc | Machine/Supervisor Exception Program Counter |
error code(stack) | scause | mcause | Machine/Supervisor Cause Register |
x86ではinterruptの際の%esp(stack pointer)はTSS(Task-State Segment)の%esp0のaddressに自動的にsetされるが、RISC-Vの場合、手動でセットする必要がある:
U-mode -> S-modeの場合: https://github.com/mit-pdos/xv6-riscv/blob/9ead904afef8d060c2cc5cee6bd8e8d223de8c40/kernel/trampoline.S#L68
S-mode -> S-modeの場合: https://github.com/mit-pdos/xv6-riscv/blob/9ead904afef8d060c2cc5cee6bd8e8d223de8c40/kernel/kernelvec.S#L12 のようにstack pointerをずらす(x86と同様にRISC-Vでもstack pointerは上位->下位の方向にgrow-downする)。
U-mode -> S-mode
これは、ecall
instruction実行時19やexception, U-mode(user mode)中のinterruptでのvertical trapsである。前章で述べたとおり、デフォルトではtrapが生じると全てM-modeに遷移するのだが、xv6-riscvではmedeleg
とmideleg
に0xffffとセットすることによりS-modeに遷移するのだった(ただし、後述するようにlocal timer interruptだけM-modeに遷移する)。
本節では、trapが発生した場合の各種レジスタの状態遷移を追っていく20:
- U-modeにてtrapが発生21した。trapの節で述べたようにsstatus.SIEのbit値に関わらず発生する。
- scauseにtrapの発生原因が入る。例えばecallの場合、bit31(Interrupt)=0, Exception code(bit3-0)=4b1000=8となる
- sepcに1で発生した箇所のprogram counterが入る22
- stvalにexception-specific valueが入る(例えば、page-fault exceptionだったら、page faultが発生したvirtual addressが格納される)
- sstatus.SPP(S-mode Privious Privilege) <~ U-mode(00)に設定
- sstatus.SPIE <~ sstatus.SIE(Software Interrupt Enable) [SIEをsave]
- sstatus.SIE <~ 0 [always]
- S-modeに遷移
- pc <~ stvecの関数アドレス(uservec関数)にセットされる
- [trap handlerの処理; software処理開始]
- integer and floating-point registers達をsscratch CSRに退避し、S-modeで使うべきregisterをrestoreする(uservec関数)
- 次のtrapに備えて、proc構造体sscratchから9のinteger and floating-point registers達をrestoreする(userret関数)
- sret実行
- sstatus.SIE <- sstatus.SPIE(=1)
- U-modeに遷移する
- sstatus.SPIE <~ 1 [always]
- sstatus.SPP <~ 00(U-mode) [always]
- pc(program counter) <~ sepc CSR
- 2の
scause
は以下の値を取りうる:
2~9まではhardwareが自動的に行う処理である。SIEがOFFになる&S-modeに遷移するため、trap handlerはS-modeにいる間は発生しない(horizontal trapsでは
*IE
がONになっている必要があるのだった。"trap"の節を参照のこと)。10~12まではRISC-Vでは、integer and floating-point registers(いわゆるgeneral register)をsscratchに手動でsaveしていく23。10の処理中にsstatus のbitを書き換えることも可能であるが、本記事では省略するのでusertrap関数を参考にされたい。
13の
sret
instructionではsstatus.SPPに格納されたmode(今回はU-mode)に戻る。13~はhardware側の処理であり、最終的にもとのU-modeのprogram counter(ecall
のtrapの場合はhandlerにて+4されてる)に復帰する。14~18まではhardwareが自動的に行う処理である。
S-mode -> S-mode
前節と同様に追っていく:
- S-modeにてinterruptが発生した。trapの節で述べたようにsstatus.SIE=1である必要がある。
- scauseにbit31(Interrupt)=1, 所定のtrap code(bit3-0)が入る
- sepcに1で発生した箇所のprogram counterが入る
- stvalにexception-specific valueが入る
- sstatus.SPP(S-mode Privious Privilege) <~ S-mode(1)に設定
- sstatus.SPIE <~ sstatus.SIE(Software Interrupt Enable) [SIEをsave]
- sstatus.SIE <~ 0 [always]
- S-modeに遷移
- pc <~ stvecの関数アドレス(kernelvec関数)にセットされる
- [trap handlerの処理; software処理]
- integer and floating-point registers達をsp registerに避難する。
- 9のinteger and floating-point registers達をrestoreする(userret関数)
- sret実行
- sstatus.SIE <- sstatus.SPIE(=1) [SIEをrestore]
- S-modeに遷移する(5でsstatus.SPP=1としているため)
- sstatus.SPIE <~ 1 [always]
- sstatus.SPP <~ 00(U-mode) [always]
- pc(program counter) <~ sepc CSR
1のinterrupt発生はsstatus.SIE=1である条件がいる
5のSPPは遷移元のmodeが入るのでS-mode(1)が入る。
9のstvec CSRはhttps://github.com/mit-pdos/xv6-riscv/blob/9ead904afef8d060c2cc5cee6bd8e8d223de8c40/kernel/trap.c#L46 にて予めセットされている。
17はSPPはU-modeの0に設定されるため、もし18のあと、何らかのtrap handlerの処理をしたあとsretを実行すれば、U-modeに遷移する。
Note) 実際の動きとしては、U-mode -[ecall]-> S-mode -[may interrupt]-> S-mode -[sret]-> S-mode -[sret]-> U-modeのように入れ子でmodeが遷移する。ecall
直後のS-modeにおいて、sepcの中身をメモリに退避させる必要があったりするが、これについては次の記事で述べたいと思う。
U-mode -> M-mode
これは、User modeにおいて、local timer interruptが発生したときにこの様に遷移する。U-mode -> S-mode
の節で述べたようにS-modeに遷移しない。これは、Reference[3]のch.3.1.9の次の記述によるものと考えられる:
An interrupt i will be taken if bit i is set in both mip and mie, and if interrupts are globally enabled. By default, M-mode interrupts are globally enabled if the hart’s current privilege mode is less than M, or if the current privilege mode is M and the MIE bit in the mstatus register is set. If bit i in mideleg is set, however, interrupts are considered to be globally enabled if the hart’s current privilege mode equals the delegated privilege mode (S or U) and that mode’s interrupt enable bit(SIE or UIE in mstatus) is set, or if the current privilege mode is less than the delegated privilege mode.
一方でxv6-riscvではboot時(M-mode時)に以下のように設定されている:
// timerinit w_mstatus(r_mstatus() | MSTATUS_MIE); // #define MSTATUS_MIE (1L << 3) // machine-mode interrupt enable. // enable machine-mode timer interrupts. w_mie(r_mie() | MIE_MTIE); // timer interrupt-enable bit, named MTIE(M-mode)
xv6-riscvではmideleg.MTIP(bit7)=1であるので、delegated privilege mode
がS-modeである。このことに注意すれば、現在のmodeがS-modeかU-modeかによって場合分けできる:
current privilege mode
がU-modeの場合、if the current privilege mode is less than the delegated privilege mode.
の条件を自然に満たすので、M-mode interrupts are globally enabled
となり、M-modeに移行する。current privilege mode
がS-modeの場合、delegated privilege mode
とcurrent privilege mode
が一致するので、mode’s interrupt enable bit
(mstatus or sstatusのSIE24)がONであるという条件をもってM-modeに遷移することができる。xv6-riscvではecall
instructionによるS-modeした後のtrap handlerにてsstatus.SIEをONとする箇所があるので、そのときにS-mode -> M-mode
に遷移するようだ。
上の理由から、U-mode -> M-mode
へ遷移できることがわかったので、各フラグの状態変化を追っていこう。
- U-modeにてlocal timer interruptが発生した。mstatus.MIEの値に関係なくinterruptによってM-modeに遷移する
- mcauseにbit31=1(interrupt), 所定のtrap code(bit3-0)=4(User timer interrupt)が入る
- mepcに1で発生した箇所のprogram counterが入る
- mtvalにexception-specific valueが入る
- mstatus.MPP(S-mode Privious Privilege) <~ U-mode(0)に設定
- mstatus.MPIE <~ mstatus.MIE(Software Interrupt Enable) [MIEをrestore]
- mstatus.MIE <~ 0 [always]
- M-modeに遷移。
- pc <~ mtvec(timervec)にセットされる
- [trap handlerの処理(software処理)] timervec関数を開始
- mscratch CSRの中身を取り出したり、新しい値に更新したりする
- sip.SSIP <~ 1にセット。SSIPはSupervisor Software Interrupt Pendingのこと。
- mret実行
- mstatus.MIE <- mstatus.MPIE(=1) [MIEをrestore]
- U-modeに遷移する(5でmstatus.MPP=0としているため)
- mstatus.MPIE <~ 1 [always]
- mstatus.MPP <~ 00(U-mode) [always]
- pc(program counter) <~ mepc CSR
- 12でSSIP=1にセットされているので、interruptが発生し、S-modeに遷移する
- 以下
U-mode => S-mode
の節と同様。
仕様書を確認する限り、この様になると思われる。
- 2のmcauseは以下の値を取りうる。
- 12でpending bit(sip.SSIP)をセットしている。12の時点ではM-modeなので、この時点でinterruptは生じない。19にてU-modeに遷移するときにinterruptが発生し、S-modeへ遷移する。つまり、hardwareからみるとM-odeからU-modeにー瞬移るもののpending bitがONの影響ですぐにS-modeに移る。
S-mode -> M-mode
前節と似たような感じになるので省略(TODO: いずれ埋めたい)
M-mode -> M-mode
このhorizontal trapsはxv6-riscvでは実は生じない。boot以外でM-modeにいる場合とはlocal timer interruptが生じてM-modeに遷移したときだが、前前節の7. mstatus.MIE <~ 0
よりmstatus.MIEが0にセットされるからだ(MIEが0のとき、M->M
のhorizontal trapsは生じないことを思い出す):
in U-mode or S-mode - mstatus.MPIE <- mstatus.MIE(=1) [save MIE to MPIE]
- mstatus.MIE <- 0
- mstatus.MPP <- 0b00(prev: U-mode) or 0b01(prev: S-mode)
前述の通り、MIEはmret実行時に復元される
U-mode -> U-mode
U-mode -> U-mode
への遷移(horizontal traps)はmedeleg/mideleg(machine)とsedeleg/sideleg(supervised)のbitをセットする必要があり25、おそらく特殊な環境でしか使われない様に見えるので、本記事では省略する。xv6-riscvではこの形式のtrap遷移は生じない。
終わりに
xv6-riscvにおけるprivilegeについて述べた。RISC-VではM-mode(Machine mode)とS-mode(Supervisor mode)とU-mode(User mode)の3種類のprivilege modeが存在するので状態遷移がその分複雑になっている。本記事では、どのmodeからどのmodeに遷移できるかと、遷移したときにCSRのbitがどの様に状態変化するのかをまとめた。
RISC-Vの仕様の中ではかなりややこしい部分と思われるので、xv6-riscvのtrap周りのコードリーディングの参考になれば幸いである。
補足
以下のtweetにthread形式に簡単にxv6-riscvの相違点とか特徴をまとめているので、よかったら見てね〜
xv6のriscv version / 1件のコメント https://t.co/bWUsmzOpuw “https://t.co/a7B9PsZroR” (1 user) https://t.co/bW8cYVAicO
— Kenta Nakajima (@knknkn26918) September 23, 2019
ざっくり読んだ感想としては、以下のような感じ。
[xv6-riscvのkernel/の感想]
— Kenta Nakajima (@knknkn26918) September 23, 2019
+ risc-vやはり素直。特にCSR(Control/Status Register)周りがスッキリ。privilegeはやや癖あり
+ filesystem, paging, schedulingはx86と同じ
+ peripheral deviceはqemuの機能に依存?
初学者はhttps://t.co/2JgZOe4fKEが充実してないため、まだx86版の方が良いかも。
Note) pagingは同じと書いてあるが、仕組みが大体同じということです(xv6-riscvでは3-level pagingだが、x86では2-level paging)。
xv6-riscvでは3-level paging(64bit)。
— Kenta Nakajima (@knknkn26918) September 23, 2019
satp registerはx86の%cr3(PDBR)に相当するが、MSB4bitでaddressing modeを指定する必要がある(https://t.co/FnhI3MD4HX)。(mode: 4b1000: Sv39; Page-based 39bit virtual addressing) pic.twitter.com/UM6zQsUfv5
-
xv6-riscvがx86 versionともう一つ大きく違う所はもう一つある。それはperipheral deviceやI/Oの取り扱いだ。riscvではqemuの
-device virtio-blk-device
というオプションによって、virtioによってdisk I/Oを実現していたり(x86ではI/O portでのdiskをPIO modeにてread/writeしてる)、memory mapped I/OでUARTを取り扱っている。さらに、I/O APICの代わりにqemu上で動くPLICを用いているようだ。ここはqemuの仕様書等を逐一確認する必要があると思われるので、次の機会に回したい。↩ -
例えば、ch.2.1に"There is one additional unprivileged register: the program counter pc holds the address of the current instruction.“とある↩
-
RV64の場合↩
-
Reference[4]のTable 26.3を参照。↩
-
xv6-riscvではU(User), S(Supervisor), M(Machine)すべてのmodeが用いられている(が、riscv対応のhardwareによっては、M onlyだったり、MU onlyだったりする)。↩
-
Reference[3]に
M-mode is used for low-level access to a hardware platform and is the first mode entered at reset.
とある。もしくは、ch.3.3のResetを参照のこと。↩ -
RISC-Vの仕様書には"trap"という語の厳密な定義は書いていない様に見えるが、文脈からexceptionおよびinterruptのことと考えられる。x86だと狭義のtrapの意味がexceptionだけであったりするので( e.g) https://github.com/mit-pdos/xv6-public/blob/1d19081efbb9517d07c7e6c1a8393c6343ba7817/mmu.h#L161) 少し注意が必要かもしれない。↩
-
mstatusのMIEはbit3の位置にあるが、sstatusのbit3はWPRI(Reserved Writes Preserve Values, Reads Ignore Values)となっている。これは、Readは向こうで書き込みできないように保護されてるよ、と言う意味。↩
-
xv6-riscvではこの遷移は起こらない。なぜならM-modeでは(boot時の僅かな時間を除いて)常にMIE=OFFになっているから。仕組みは後述したい。↩
-
Reference[3]のch.3.1.6.1より、"Interrupts for higher-privilege modes, y>x, are always globally enabled regardless of the setting of the higherprivilege mode’s global y IE bit"とある。↩
-
Reference[3]のch.3.1.8に"Traps never transition from a more-privileged mode to a less-privileged mode.“とある。または、Reference[3]のch.3.1.6.1の"Interrupts for lower-privilege modes, w<x, are always globally disabled regardless of the setting of the lower-privilege mode’s global w IE bit."と書いてある。↩
-
ch.3.1.8に"all traps at any privilege level are handled in machine mode"とある。↩
-
Reference[3]のch.3.1.8には"To increase performance, implementations can provide individual read/write bits within medeleg and mideleg to indicate that certain exceptions and interrupts should be processed directly by a lower privilege level.“とあるとおりパフォーマンス向上のため、と書いてある。↩
-
x86ではiretが似たような役割を果たす。ただし、本来の
iret
の意味合いはmodeの遷移とは厳密に異なる。これに関しては、http://jamesmolloy.co.uk/tutorial_html/10.-User%20Mode.html が参考になると思う。↩ -
“An x RET instruction can be executed in privilege mode x or higher, where executing a lower-privilege x RET instruction will pop the relevant lower-privilege interrupt enable and privilege mode stack."とある。↩
-
Reference[4]のTable 26.3を参照のこと↩
-
trapでS-modeに遷移した直後はsstatus.SIE=0となっているため、sstatus.SIE=0である限り、sip.SSIPは遅れてOFFにすることができる。もし、sstatus.SIE=1となった場合、sip.SSIP=1のままだと、preemptionが生じる可能性があるのでダメ。↩
-
詳細はhttp://cstmize.hatenablog.jp/entry/2019/03/20/xv6%E3%81%AE%E5%A0%B4%E5%90%88%E3%81%AEException%E3%81%A8Interrupt%E3%81%AE%E6%8C%99%E5%8B%95 の"Exception発生直後のhardware(x86)の対応"を参考にしてください。↩
-
Reference[3]のch.3.2.1に"ECALL generates a different exception for each originating privilege mode so that environment call exceptions can be selectively delegated. A typical use case for Unix-like operating systems is to delegate to S-mode the environment-call-from-U-mode exception"とある。xv6-riscvではmedelegのbit8(Environment call from U-mode)がONになっているので、U-mode -> S-modeに遷移する。↩
-
このtrap時のhardwareの振る舞いに関しては、Reference[3]のch.3.1.8に"When a trap is delegated to a less-privileged mode x, the x cause register is written with the trap cause; the x epc register is written with the virtual address of the instruction that took the trap; the x tval register is written with an exception-specific datum; the xPP field of mstatus is written with the active privilege mode at the time of the trap; the xPIE field of mstatus is written with the value of the x IE field at the time of the trap; and the x IE field of mstatus is cleared. The mcause and mepc registers and the MPP and MPIE fields of mstatus are not written.“とあるので、これを参考にした。sret実行によるhardware処理の流れは、ch.3.1.6.1に"When executing an x RET instruction, supposing xPP holds the value y, x IE is set to xPIE; the privilege mode is changed to y; xPIE is set to 1; and xPP is set to U"とあったので、ここを参照した。↩
-
ecallの場合、user spaceで例えばfork()を実行すると、entry(“fork”)で予め展開されているコードより https://github.com/mit-pdos/xv6-riscv/blob/9ead904afef8d060c2cc5cee6bd8e8d223de8c40/user/usys.pl#L14 を実行する。↩
-
ecallの場合、ch.3.2.1の、"ECALL and EBREAK cause the receiving privilege mode’s epc register to be set to the address of the ECALL or EBREAK instruction itself, not the address of the following instruction.“とある通り、ecallの手前のprogram counterが格納される。そのため、https://github.com/mit-pdos/xv6-riscv/blob/9ead904afef8d060c2cc5cee6bd8e8d223de8c40/kernel/trap.c#L61 にてprogram counter+4としている。↩
-
kernel modeのstackは https://github.com/mit-pdos/xv6-riscv/blob/9ead904afef8d060c2cc5cee6bd8e8d223de8c40/kernel/trampoline.S#L68 にて取得する。↩
-
Reference[3]のch.4.1.1では"The sstatus register is a subset of the mstatus register. In a straightforward implementation, reading or writing any field in sstatus is equivalent to reading or writing the homonymous field in mstatus.“とあるので、sstatusのbitは対応するmstatusのbitと同一視できる。↩
-
Reference[3]のch.3.1.8に"In systems with all three privilege modes (M/S/U), setting a bit in medeleg or mideleg will delegate the corresponding trap in S-mode or U-mode to the S-mode trap handler. If U-mode traps are supported, S-mode may in turn set corresponding bits in the sedeleg and sideleg registers to delegate traps that occur in U-mode to the U-mode trap handler.“と書いてある。↩