リアルタイムOSの内部構造を見てみよう!

第11回 割込みハンドラの出口処理の役割(後半)

3連休を利用して雪山に行ってきました.スキー(スノボー世代ではありません・・・)とそり,温泉を楽しんできました.空気が冷たくて本当に静かだと,意識がどんどん内側に向いていきます.お正月のビーチでは1文字も進まなかったこの原稿も,ここなら進みそうです.

前回から,割込みハンドラ出口処理についての解説をしています.割込みハンドラからの戻り方には3つのパターンがありました.

タイプ‖申迭笋蟾みで,割込み先の割込みハンドラに戻る 
タイプ◆С箙み先のタスクに戻る
タイプ:ディスパッチが保留されていたので,タスク切り替えが行われ別のタスクに戻る.



【図11-1 割込み出口処理概要】

前回は,割込みハンドラから呼ばれたサービスコールによって,ディスパッチが必要になった場合の例(タイプ)を用いて,_ret_int,_ret_int_2の解説をしました.

今回は,前回説明しなかった,_ret_int_1,_ret_int_r,_ret_int_4の解説をします.
この処理は,【図11-1】で示したように,タイプ‖申迭笋蟾みで,割込み先の割込みハンドラに戻る場合と,タイプ割込み先タスクに戻る場合(ディスパッチしない)に,行います.ここでは,タイプ△領磴鬚發箸法_ret_int_1を解説します.
あるタスク(タスクCとします)が実行中に割込みが発生し,割込みハンドラが実行され,割込みハンドラ終了後,タスクCに処理がもどる場合について見ていきます. 割込みハンドラ起動中は,割込みハンドラがCPUを使用していますが,タスクCは実行状態のまま状態は変わりません.【図11-2】に示した赤丸のタイミングで,割込み入口処理,出口処理が行われます.



【図11-2 割込み入口,出口処理のタイミング】

割込みが発生してからの流れを,大枠で理解するために【図11-3】で解説します.



【図11-3 タイプ⊇萢概要】

割込み出口処理で行うことは,割込み入口処理でASPカーネルが行ったことと逆の処理を行い,もとの状態に戻すことです.【図11-3】に,割込み入口処理で行った処理と対応する割込み出口の処理を青い点線で対応付けています.割込み入口処理(_interruput_vec)では,保存が必要なレジスタ(コーラーセーブドレジスタ)をタスクに保存し,スタックポインタも非タスクコンテキスト用に切り替えましたので,割込み出口処理では,スタックポインタをタスク用に切り替えて,_interruput_vecで保存したレジスタ(コーラーセーブドレジスタ)を復帰するという処理が必要です.スタックポインタをタスク用に切り替えるというところまでは,_ret_intで行います.ここまでは前回解説しました.今回は残りの処理(保存したレジスタの復帰)を行う,_ret_int_1の解説をおこないます.

_interrupt_vec:で保存したレジスタを,保存した順番とは逆順に復帰し,RTE命令で戻ります.

STEP1;lock_flagをFALSEへ
STEP2:汎用レジスタの復帰
STEP3:その他のレジスタの復帰
STEP4:RTE命令で戻る

続いて,ソースコードを読んでいきます.

597 _ret_int_1:
604 mov.l _lock_flag_ret, r0
605 xor r1,r1
606 mov.l r1,@r0
607 mov.l @r15+,r7 /* 割り込み元に戻る */
608 mov.l @r15+,r6
609 mov.l @r15+,r5
610 mov.l @r15+,r4
611 mov.l @r15+,r3
612 mov.l @r15+,r2
613 mov.l @r15+,r1
614 mov.l _mask_md_bl_ret,r0
615 ldc r0,sr /* BL を1に */
616 mov.l @r15+,r0
617 lds.l @r15+,mach
618 lds.l @r15+,macl
619 ldc.l @r15+,gbr
620 ldc.l @r15+,ssr
621 lds.l @r15+,pr
622 ldc.l @r15+,spc
623 rte
624 nop


STEP1:lock_flagをFALSEへ
604行目:lock_flag_retは,lock_flagのアドレスを表します.

605行目:
xor Rm,Rn ⇒ RmとRnのXOR演算結果→Rn

R1どうしのXOR演算をすることで,R1を0にしています.,

606行目:lock_flagを0にします.

ここで,lock_flagをFALSEにする理由
第1回で解説しましたが,ASPカーネルの設計方針の一つに,高信頼性・安全なシステム構築を支援する,というものがあります.μITRONおよび,JSPカーネルでは,アプリケーションが約束事を守らなければ,動作の保証はしません.というスタンスでした.それに対して,ASPカーネルでは,万が一,アプリケーションが約束事に反する処理をしても,妥当なオーバーヘッドで救済できる誤用は救済する.という方針にしています.割込みハンドラ中で,割込み禁止にしたなら,割り込みハンドラが終了する前に,割込みハンドラ内で,割込み許可としなければならないという約束事があります.ASPカーネルでは,この約束事をアプリケーションが守っていなければ,カーネルが割込み許可にする.という救済措置をとっています.割込み許可にするためには,lock_flagをFALSEにして,SR(ステータスレジスタ)のI3-I0の,割込み優先度マスクを割込み発生前に戻すという処理が必要です.よって,カーネル内でまず,lock_flagをFALSEにします.SRの値は,RTE命令で元に戻るときに,SHプロセッサが元に戻します.


STEP2汎用レジスタの復帰
STEP3:その他のレジスタの復帰

割込み入口処理(_interrupt_vect)でスタック上に保存したレジスタ(第9回で解説)を,積んだ順とは逆順にレジスタに復帰します.

STEP4:rte命令で復帰

RTE命令
例外処理から復帰する命令です.PCとSRの値をSPCとSSRから回復させます.プログラムは回復したPCの値で指定されるアドレスから続行します.よって割込み処理によって中断されていた位置から処理が再開します.
<_ret_int_r>
次に,_ret_int_rの解説をします.前回は以下の例で_ret_int_2まで解説しました.

優先度の高いタスクAは,slp_tsk()発行によって起床待ち状態になり(【図11-4,5】の 法ぅ織好A より優先度の低いタスクBが実行中となっています.そこで割込みが発生し,割込みハンドラから,iwup_tsk(TASKA)によって,タスクAが起こされる場合について見ていきました.割込み出口処理では,この割込みハンドラからの戻り先は,タスクであること,割込みハンドラ中でタスクBからタスクAへのディスパッチが必要となったが保留されていること,を判断し,対応する処理を行いました.タスクBは_ret_intでコンテキストを保存し(【図11-4,5】の◆房孫垈椎従態となり,タスクAは実行状態となりました(【図11-4,5】の).次にタスクAがslp_tsk()を発行したことによって,タスクBがコンテキストを復帰し実行状態になる処理を見ていきます.ポイントはタスクBが_ret_intによってコンテキストを保存している.という所です.



【図11-4 各タスクがコンテキストを保存/復帰するタイミング】

【図11-4】で示したタイミング 銑い如こ謄織好がコンテキストを保存/復帰するルートを【図11-5】に示します.

null

【図11-5 ディスパッチャ処理ルート】

タスクAがslp_tsk()を発行し,タスク切り替えが必要となったのでディスパチャを起動し,_dispatchでコンテキストを保存します(【図11-4,5】の 法ゥ妊スパッチャコアでタスク切り替え後,タスクBは_ret_int_rでコンテキストを復帰します(【図11-4,5】のぁ法
【図11-6】に,タスクBに注目して,タスクBが_ret_intでコンテキストを保存して,その後実行状態になり,ret_int_rでコンテキストを復帰する場合について概要を示します,



【図11-6 タスクBに注目した処理概要】

ここから,_ret_int_rの解説を行います.
ここは,割込み出口処理でもあり,ディスパッチャ出口処理でもあります.第8回で解説した,dispatch_rと似た処理を行います.
ディスパッチ入口処理(_ret_int_2)で保存した,コーリーセーブドレジスタ(R8〜R14)を保存順とは逆順で復帰します.

684 _ret_int_r:
685 mov.l @r15+,r8
686 mov.l @r15+,r9
687 mov.l @r15+,r10
688 mov.l @r15+,r11
689 mov.l @r15+,r12
690 mov.l @r15+,r13
691 mov.l @r15+,r14


<_ret_int_4>
ここでは,割込みハンドラの入口(_interrupt_vec)で保存したレジスタを復帰します.

STEP1:lock_flagをFALSE
STEP2:コーラーセーブドレジスタ(R0〜R7)を復帰
STEP3:SRのBLビットを1へ
STEP4:その他,保存したレジスタを復帰
STEP5:RTE命令

続いて,ソースコードを見ていきます.

712 _ret_int_4: 
713 mov.l _lock_flag_ret, r2 /* lock_flagをFALSEに */
714 xor r0,r0
715 mov.l r0,@r2
716 mov.l @r15+,r7 /* spc,pr,ssr,スクラッチレジスタを復帰 */
717 mov.l @r15+,r6
718 mov.l @r15+,r5
719 mov.l @r15+,r4
720 mov.l @r15+,r3
721 mov.l @r15+,r2
722 mov.l @r15+,r1
723 mov.l _mask_md_bl_ret,r0
724 ldc r0,sr /* BLを1に */
725 mov.l @r15+,r0
726 lds.l @r15+,mach
727 lds.l @r15+,macl
728 ldc.l @r15+,gbr
729 ldc.l @r15+,ssr
730 lds.l @r15+,pr
731 ldc.l @r15+,spc
732 rte
733 nop


STEP1:lock_flagをFALSE
713行目:lock_flag_retは,lock_flagのアドレスを表します.
714行目:R0を0にします.
715行目:lock_flagをFALSEにします.

STEP2:スクラッチレジスタ(R0〜R7)を復帰
716〜722行目:R7からR0の順で復帰します.

STEP3:SRのBLビットを1へ
723行目:_mask_md_bl_retは,SRに登録する値(0x50000000)を表します.この値は,MD(処理モードビット)を1にして特権モード,BL(ブロックビット)を1にして例外の発生を抑止する設定値です.
724行目:上記の値を,SRに登録します.

STEP4:その他,保存したレジスタを復帰
726〜731行目:SPC(退避プログラムカウンタ),PR(プロシージャレジスタ),SSR(退避ステータスレジスタ),GBR(グローバルベースレジスタ),MACL,MACH(積和下位,上位レジスタ)の順に保存したので,ここでは逆順に復帰します.

STEP5:RTE命令
RTE命令は,SPCをPCへ,SSRをSRに復帰してからジャンプします.731行目で復帰したSPCが戻り先になります.つまり,割込みハンドラによって中断されていたところです.

※参考 タスクからサービスコールが呼ばれてディスパッチを行った場合(第8回),RTS命令で戻りました.RTS命令は,PCをPRから復帰し,復帰したPCの示すアドレスから処理を続行します.

第8回から今回まで,ディスパッチャの処理と割込み入口,出口の処理について見てきました.一見複雑に見えますが,各処理で保存が必要なものを保存し,復帰するときは保存したものを順番に復帰するという流れが基本になります.