RISC-Vのlinux kernelにおけるcontext switch
備忘録、riscvのコンテキストスイッチ部のレジスタの保存/復元処理を見ていきます。
arch/ricv/include/asm/switch_to.h
#define switch_to(prev, next, last) \
do { \
struct task_struct *__prev = (prev); \
struct task_struct *__next = (next); \
if (has_fpu()) \
__switch_to_fpu(__prev, __next); \
if (has_vector()) \
__switch_to_vector(__prev, __next); \
((last) = __switch_to(__prev, __next)); \
} while (0)
このマクロがsched/core.cから呼ばれます。
__switch_to_fpuはfpuのレジスタを保存する関数です。
もしfpuのstateがdirtyで保存する必要があれば__fstate_save()が呼ばれます。
SYM_FUNC_START(__fstate_save)
li a2, TASK_THREAD_F0
add a0, a0, a2
li t1, SR_FS
csrs CSR_STATUS, t1
frcsr t0
fsd f0, TASK_THREAD_F0_F0(a0)
fsd f1, TASK_THREAD_F1_F0(a0)
fsd f2, TASK_THREAD_F2_F0(a0)
...
fsd f31, TASK_THREAD_F31_F0(a0)
sw t0, TASK_THREAD_FCSR_F0(a0)
csrc CSR_STATUS, t1
ret
SYM_FUNC_END(__fstate_save)
TASK_THREAD_F0, F1...は
arch/riscv/kernel/asm-offsets.cにおいて定義されています。
void asm_offsets(void) {
...
OFFSET(TASK_THREAD_F0, task_struct, thread.fstate.f[0]);
OFFSET(TASK_THREAD_F1, task_struct, thread.fstate.f[1]);
...
}
OFFSETマクロの定義は以下の通り。
#define DEFINE(sym, val) \
asm volatile("\n.ascii \"->" #sym " %0 " #val "\"" : : "i" (val))
...
#define OFFSET(sym, str, mem) \
DEFINE(sym, offsetof(struct str, mem))
これはTASK_THREAD_F0をstruct task_structにおけるメンバthread.fstate.f[0]のオフセット情報をバイナリに埋め込む処理です。
例えば
OFFSET(TASK_THREAD_F0, task_struct, thread.fstate.f[0]);
は、
->TASK_THREAD_F0 2000 offsetof(struct task_struct, thread.fstate.f[0])
という文字列を埋め込むことになります。
そしてコンパイル時にこれらのバイト列をKbuildが解釈してarch/riscv/include/generated/asm-offsets.hを生成します。
...
#define TASK_TI_CPU_NUM 32 /* offsetof(struct task_struct, thread_info.cpu) */
#define TASK_THREAD_F0 2800 /* offsetof(struct task_struct, thread.fstate.f[0]) */
#define TASK_THREAD_F1 2808 /* offsetof(struct task_struct, thread.fstate.f[1]) */
...
もう一度__fstate_save()を見ます。
csrsはcontrol and status registerをsetするという処理で、CSR_STATUSはセットしたいCSRの番号を、t1(=SR_FS)はセットしたい値を表します。RISC-V priviledged manual,p17を見てみると、SR_FS(=0x300)のCSRはmstatusというものです。SR_FS(=0x00006000)によってmstatusのFS[1:0]を1にセットします。おそらくSet Register Floating Statusの意。
frcsr t0でFCSRという浮動小数点計算のCSRの値をt0に読み込みます。
fsdは浮動小数点の値をsetし、幅はdoubleであることを表します。TASK_THREAD_F0_Fn(a0)はF0からFnまでのオフセットをF0の(task_structからの)オフセットに足している。つまり、struct task_structのレジスタ情報保存用のフィールドに今のfpuの値を格納しています。
最後にsw(store word)命令でFCSRの値を保存し、mstateのFSビットをクリアしています。
dirtyフラグをクリアして、同様に必要があればnextのfpuをロードします。
vectorが有効であれば同様に処理します。
その次の__switch_toが本体。
SYM_FUNC_START(__switch_to)
/* Save context into prev->thread */
li a4, TASK_THREAD_RA
add a3, a0, a4
add a4, a1, a4
REG_S ra, TASK_THREAD_RA_RA(a3)
REG_S sp, TASK_THREAD_SP_RA(a3)
REG_S s0, TASK_THREAD_S0_RA(a3)
REG_S s1, TASK_THREAD_S1_RA(a3)
REG_S s2, TASK_THREAD_S2_RA(a3)
REG_S s3, TASK_THREAD_S3_RA(a3)
REG_S s4, TASK_THREAD_S4_RA(a3)
REG_S s5, TASK_THREAD_S5_RA(a3)
REG_S s6, TASK_THREAD_S6_RA(a3)
REG_S s7, TASK_THREAD_S7_RA(a3)
REG_S s8, TASK_THREAD_S8_RA(a3)
REG_S s9, TASK_THREAD_S9_RA(a3)
REG_S s10, TASK_THREAD_S10_RA(a3)
REG_S s11, TASK_THREAD_S11_RA(a3)
/* Save the kernel shadow call stack pointer */
scs_save_current
/* Restore context from next->thread */
REG_L ra, TASK_THREAD_RA_RA(a4)
REG_L sp, TASK_THREAD_SP_RA(a4)
REG_L s0, TASK_THREAD_S0_RA(a4)
REG_L s1, TASK_THREAD_S1_RA(a4)
REG_L s2, TASK_THREAD_S2_RA(a4)
REG_L s3, TASK_THREAD_S3_RA(a4)
REG_L s4, TASK_THREAD_S4_RA(a4)
REG_L s5, TASK_THREAD_S5_RA(a4)
REG_L s6, TASK_THREAD_S6_RA(a4)
REG_L s7, TASK_THREAD_S7_RA(a4)
REG_L s8, TASK_THREAD_S8_RA(a4)
REG_L s9, TASK_THREAD_S9_RA(a4)
REG_L s10, TASK_THREAD_S10_RA(a4)
REG_L s11, TASK_THREAD_S11_RA(a4)
/* The offset of thread_info in task_struct is zero. */
move tp, a1
/* Switch to the next shadow call stack */
scs_load_current
ret
SYM_FUNC_END(__switch_to)
a0, a1には引数でstruct task_struct *prev, *nextが渡されます。
している処理は殆ど__fstate_save(), __fstate_restore()と同じで、struct task_structの各レジスタに今の値を格納、nextの値をロードするという処理です。scs_save_current, scs_load_currentはtp(スレッドポインタ)をセーブ、ロードします。
move tp, a1はスレッドポインタを新しいものに更新しています。
気が向いたらメモリマップのswitch処理についても書こうと思います。arch/riscv/mm/context.cのswitch_mm(struct mm_struct *prev, struct mm_struct *next, struct task_struct *task)などの関数が行っていて、同じくスケジューラやforkシステムコールなどから呼ばれます。
Discussion