目次: Zephyr
前回はRISC-Vの2つあるコンテキストスイッチのうち、明示的なコンテキストスイッチを実装しました。今回はもう一方のプリエンプションを実装します。
従来と新形式のコンテキストスイッチで大きく異なるのは、下記の要素です。
明示的コンテキストスイッチ | 従来 | 新形式 |
---|---|---|
割り込まれた処理の返り値 | 設定必要(thread->arch.swap_return_value) | 設定不要(do_swap() がやってくれる) |
切り替え元スレッド | _kernel.cpu[0].current | a1レジスタ(引数old_thread->switch_handle) |
切り替え先スレッド | _kernel.ready_q.cache | a0レジスタ(引数new_thread->switch_handle) |
プリエンプション | 従来 | 新形式 |
---|---|---|
割り込まれた処理の返り値 | 設定必要(thread->arch.swap_return_value) | 設定不要(do_swap() がやってくれる) |
切り替え元スレッド | _kernel.cpu[0].current | _kernel.cpu[n].current(※) |
切り替え先スレッド | _kernel.ready_q.cache | z_get_next_switch_handle() の返り値 |
(※)初めは切り替え元スレッドですが、z_get_next_switch_handle() を呼ぶと、切り替え先のスレッドに変わります。
明示的プリエンプションと共通の部分のため、改めて直す必要はないです。
かなり処理が変わるため、#ifdefだとごちゃごちゃしてしまいます。スレッド取得の専用マクロを作ります。
// zephyr/arch/riscv/core/isr.S
/*
* xcpu: pointer of _kernel.cpus[n]
* xold: (result) old thread
* xnew: (result) next thread to schedule
*
* after this function a0 is broken
*/
.macro z_riscv_get_next_switch_handle xcpu, xold, xnew
#ifdef CONFIG_USE_SWITCH
addi sp, sp, -RV_REGSIZE*2 //★新たな処理★
RV_OP_STOREREG ra, RV_REGSIZE(sp)
addi a0, sp, 0 //★スタックの先頭へのポインタを第一引数old_threadとする★
jal ra, z_arch_get_next_switch_handle //★(2) この関数内で _currentがnew_threadに設定される★
addi xnew, a0, 0 //★a0が返り値、切り替え先のスレッドが入っている★
RV_OP_LOADREG xold, 0(sp) //★スタック先頭に切り替え元のスレッドが入っている★
RV_OP_LOADREG ra, RV_REGSIZE(sp)
addi sp, sp, RV_REGSIZE*2
#else
/* Get pointer to _kernel.current */ //★従来処理★
RV_OP_LOADREG xold, _kernel_offset_to_current(xcpu) //★切り替え元スレッド★
RV_OP_LOADREG xnew, _kernel_offset_to_ready_q_cache(xcpu) //★切り替え先スレッド★
#endif
.endm
// zephyr/arch/riscv/core/thread.c
#ifdef CONFIG_USE_SWITCH
void *z_arch_get_next_switch_handle(struct k_thread **old_thread)
{
*old_thread = _current; //★スタックの先頭に現在のスレッド(= 切り替え元のスレッド)を保存★
return z_get_next_switch_handle(*old_thread);
}
#endif
わざわざスタックのポインタを経由して書き込むなんてややこしいことをせず、RV_OP_LOADREG xold, _kernel_offset_to_current(xcpu) で良いのでは?と思うかもしれませんが、z_arch_get_next_switch_handle() の呼び出しでどのレジスタが壊れるかわかりませんから、結局 xoldをスタックに退避する必要があります。
明示的コンテキストスイッチと処理を共有しているため、ちょっとわかりにくいですが、プリエンプションの中心となる処理はこの辺りです。
// zephyr/arch/riscv/core/isr.S
#ifdef CONFIG_PREEMPT_ENABLED
- /*
- * Check if we need to perform a reschedule
- */
-
- /* Get pointer to _kernel.current */
- RV_OP_LOADREG t2, _kernel_offset_to_current(t1)
-
/*
* Check if next thread to schedule is current thread.
* If yes do not perform a reschedule
*/
- RV_OP_LOADREG t3, _kernel_offset_to_ready_q_cache(t1)
+ z_riscv_get_next_switch_handle t1, t2, t3
beq t3, t2, no_reschedule
+
+#ifdef CONFIG_USE_SWITCH
+ /* Set old thread to t1 */
+ addi t1, t2, 0 //★(1) t1レジスタにold_threadを設定する★
+#endif
+
前回決めたとおり、合流地点(reschedule)に辿り着く前に切り替え元(old_thread)、切り替え先スレッド(new_thread)を取得し _currentにnew_threadを設定し, t1レジスタにold_threadを設定します。
処理 (1) でnew_threadを _currentに設定していて、処理 (2) でold_threadをt1レジスタに設定してから、rescheduleに到達します。プリエンプション処理のすぐ後にrescheduleラベルがあるので、ジャンプは不要です。
とても長くなってしまいましたが、新しい形式のコンテキストスイッチを実装できました。苦労の割に動作の見た目は何も変わりませんが、本命のSMP対応に活用するためなので我慢です。
< | 2020 | > | ||||
<< | < | 10 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | - | 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
合計:
本日:
管理者: Katsuhiro Suzuki(katsuhiro( a t )katsuster.net)
This is Simple Diary 1.0
Copyright(C) Katsuhiro Suzuki 2006-2023.
Powered by PHP 8.2.15.
using GD bundled (2.1.0 compatible)(png support.)