コグノスケ


link 未来から過去へ表示(*)  link 過去から未来へ表示

link もっと前
2020年10月3日 >>> 2020年10月3日
link もっと後

2020年10月3日

Zephyr OSで遊ぼう その16 - SMP対応の準備、コンテキストスイッチの実装、後編1、RISC-Vのコンテキストスイッチ

目次: Zephyr

前回は、AArch64の実装を調べました。いよいよ新しい方式のコンテキストスイッチを実装したいところですが、その前にもう一つだけRISC-Vの既存実装を調べます。

RISC-Vの2つのコンテキストスイッチ経路

RISC-V向け実装において、コンテキストスイッチが行われる条件は2つあります。1つはスリープしたときなどに呼ばれる明示的なコンテキストスイッチです。do_swap() を経由します。もう1つは割り込み発生時に行われるプリエンプションです。

明示的コンテキストスイッチ
do_swap() -> arch_switch() ラッパー関数 -> z_riscv_switch() -> ecall -> __irq_wrapper -> is_syscall -> reschedule -> no_reschedule -> mret
プリエンプション
(任意の場所) -> __irq_wrapper -> is_interrupt -> on_irq_stack -> 割り込みハンドラ(isr_timer() など) -> on_thread_stack -> reschedule -> no_reschedule -> mret

明示的コンテキストスイッチについては、以前(2020年9月29日の日記参照)実装したラッパー関数がスタート地点となります。コードを変更する前に、従来のコンテキストスイッチがどんな経路を通るか確認します。

arch_switch() ラッパー関数 -> z_riscv_switch() -> ecall -> __irq_wrapper -> is_syscall

// zephyr/arch/riscv/include/kernel_arch_func.h

static inline void arch_switch(void *switch_to, void **switched_from)
{
	z_riscv_switch(switch_to, switched_from);
}


// zephyr/arch/riscv/core/swap.S

/*
 * void z_riscv_switch(void *switch_to, void **switched_from)
 */
SECTION_FUNC(exception.other, z_riscv_switch)

	/* Make a system call to perform context switch */
	ecall    //★例外を発生させる★

	jalr x0, ra


// zephyr/arch/riscv/core/isr.S

/*
 * Handler called upon each exception/interrupt/fault
 * In this architecture, system call (ECALL) is used to perform context
 * switching or IRQ offloading (when enabled).
 */
SECTION_FUNC(exception.entry, __irq_wrapper)
	/* Allocate space on thread stack to save registers */
	addi sp, sp, -__z_arch_esf_t_SIZEOF

...

	/*
	 * Check if exception is the result of an interrupt or not.
	 * (SOC dependent). Following the RISC-V architecture spec, the MSB
	 * of the mcause register is used to indicate whether an exception
	 * is the result of an interrupt or an exception/fault. But for some
	 * SOCs (like pulpino or riscv-qemu), the MSB is never set to indicate
	 * interrupt. Hence, check for interrupt/exception via the __soc_is_irq
	 * function (that needs to be implemented by each SOC). The result is
	 * returned via register a0 (1: interrupt, 0 exception)
	 */
	jal ra, __soc_is_irq

	/* If a0 != 0, jump to is_interrupt */
	addi t1, x0, 0
	bnez a0, is_interrupt    //★割り込みの場合はこちらにジャンプする★

	/*
	 * If the exception is the result of an ECALL, check whether to
	 * perform a context-switch or an IRQ offload. Otherwise call _Fault
	 * to report the exception.
	 */
	csrr t0, mcause
	li t2, SOC_MCAUSE_EXP_MASK
	and t0, t0, t2
	li t1, SOC_MCAUSE_ECALL_EXP

	/*
	 * If mcause == SOC_MCAUSE_ECALL_EXP, handle system call,
	 * otherwise handle fault
	 */
	beq t0, t1, is_syscall    //★ecallの場合はこちらにジャンプする★

	/*
	 * Call _Fault to handle exception.
	 * Stack pointer is pointing to a z_arch_esf_t structure, pass it
	 * to _Fault (via register a0).
	 * If _Fault shall return, set return address to no_reschedule
	 * to restore stack.
	 */
	addi a0, sp, 0
	la ra, no_reschedule
	tail _Fault    //★いずれでもなければ停止させる★

...

Zephyr RISC-V向け実装では、割り込み・例外ハンドラは1つだけです。割り込みも例外も全て __irq_wrapperに飛んできますから、最初の方で要因をチェックして仕分けしています。RISC-Vの規格としては割り込み要因ごとに別の割り込みハンドラに飛べる形式(ベクタ形式)もありますが、Zephyrは使っていません。

明示的コンテキストスイッチの実行経路: is_syscall -> reschedule -> no_reschedule -> mret


// zephyr/arch/riscv/core/isr.S

is_syscall:
	/*
	 * A syscall is the result of an ecall instruction, in which case the
	 * MEPC will contain the address of the ecall instruction.
	 * Increment saved MEPC by 4 to prevent triggering the same ecall
	 * again upon exiting the ISR.
	 *
	 * It's safe to always increment by 4, even with compressed
	 * instructions, because the ecall instruction is always 4 bytes.
	 */
	RV_OP_LOADREG t0, __z_arch_esf_t_mepc_OFFSET(sp)
	addi t0, t0, 4
	RV_OP_STOREREG t0, __z_arch_esf_t_mepc_OFFSET(sp)

...

	/*
	 * Go to reschedule to handle context-switch
	 */
	j reschedule  //★コンテキストスイッチ★

...

reschedule:

...

	/* Get reference to _kernel */
	la t0, _kernel

	/* Get pointer to _kernel.current */
	RV_OP_LOADREG t1, _kernel_offset_to_current(t0)

	/*
	 * Save callee-saved registers of current thread
	 * prior to handle context-switching
	 */
	RV_OP_STOREREG s0, _thread_offset_to_s0(t1)
	RV_OP_STOREREG s1, _thread_offset_to_s1(t1)
...
	RV_OP_STOREREG s10, _thread_offset_to_s10(t1)
	RV_OP_STOREREG s11, _thread_offset_to_s11(t1)

...

	/*
	 * Save stack pointer of current thread and set the default return value
	 * of z_swap to _k_neg_eagain for the thread.
	 */
	RV_OP_STOREREG sp, _thread_offset_to_sp(t1)
	la t2, _k_neg_eagain
	lw t3, 0x00(t2)
	sw t3, _thread_offset_to_swap_return_value(t1)

	/* Get next thread to schedule. */
	RV_OP_LOADREG t1, _kernel_offset_to_ready_q_cache(t0)

	/*
	 * Set _kernel.current to new thread loaded in t1
	 */
	RV_OP_STOREREG t1, _kernel_offset_to_current(t0)

	/* Switch to new thread stack */
	RV_OP_LOADREG sp, _thread_offset_to_sp(t1)

	/* Restore callee-saved registers of new thread */
	RV_OP_LOADREG s0, _thread_offset_to_s0(t1)
	RV_OP_LOADREG s1, _thread_offset_to_s1(t1)
...
	RV_OP_LOADREG s10, _thread_offset_to_s10(t1)
	RV_OP_LOADREG s11, _thread_offset_to_s11(t1)

...

no_reschedule:

...

	/* Restore MEPC register */
	RV_OP_LOADREG t0, __z_arch_esf_t_mepc_OFFSET(sp)
	csrw mepc, t0

	/* Restore SOC-specific MSTATUS register */
	RV_OP_LOADREG t0, __z_arch_esf_t_mstatus_OFFSET(sp)
	csrw mstatus, t0

...

	/* Restore caller-saved registers from thread stack */
	RV_OP_LOADREG ra, __z_arch_esf_t_ra_OFFSET(sp)
	RV_OP_LOADREG gp, __z_arch_esf_t_gp_OFFSET(sp)
	RV_OP_LOADREG tp, __z_arch_esf_t_tp_OFFSET(sp)
	RV_OP_LOADREG t0, __z_arch_esf_t_t0_OFFSET(sp)
...
	RV_OP_LOADREG a6, __z_arch_esf_t_a6_OFFSET(sp)
	RV_OP_LOADREG a7, __z_arch_esf_t_a7_OFFSET(sp)

	/* Release stack space */
	addi sp, sp, __z_arch_esf_t_SIZEOF

	/* Call SOC_ERET to exit ISR */
	SOC_ERET

コメントが丁寧に書いてあって素晴らしいですね。コンテキストスイッチの手順はAArch64の実装とほぼ同じですが、AAarch64は明示的なコンテキストスイッチとプリエンプションが独立して実装されており、RISC-Vはrescheduleで両者が合流する点が違います。コンテキストスイッチの説明は先日(2020年10月1日の日記参照)の紙芝居が参考になるかと思います。

明示的なコンテキストスイッチとプリエンプションの部分が大体仕分けできました。いよいよ実装に挑みます。続きはまた。

編集者:すずき(2023/09/24 12:09)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
2020年10月3日 >>> 2020年10月3日
link もっと後

管理用メニュー

link 記事を新規作成

<2020>
<<<10>>>
----123
45678910
11121314151617
18192021222324
25262728293031

最近のコメント5件

  • link 24年4月22日
    hdkさん (04/24 08:36)
    「うちのHHFZ4310は15年突破しまし...」
  • link 24年4月22日
    すずきさん (04/24 00:37)
    「ちゃんと数えてないですけど蛍光管が10年...」
  • link 24年4月22日
    hdkさん (04/23 20:52)
    「おお... うちのHHFZ4310より後...」
  • link 20年6月19日
    すずきさん (04/06 22:54)
    「ディレクトリを予め作成しておけば良いです...」
  • link 20年6月19日
    斎藤さん (04/06 16:25)
    「「Preferencesというメニューか...」

最近の記事3件

  • link 24年2月7日
    すずき (04/24 02:52)
    「[複数の音声ファイルのラウドネスを統一したい] PCやデジタル音楽プレーヤーで音楽を聞いていると、曲によって音量の大小が激しく...」
  • link 24年4月22日
    すずき (04/23 20:13)
    「[仕事部屋の照明が壊れた] いきなり仕事部屋のシーリングライトが消えました。蛍光管の寿命にしては去年(2022年10月19日の...」
  • link 24年4月17日
    すずき (04/18 22:44)
    「[VSCodeとMarkdownとPlantUMLのローカルサーバー] 目次: LinuxVSCodeのPlantUML Ex...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDFファイル RSS 1.0

最終更新: 04/24 08:36