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

第09回 割込みハンドラの入口処理の役割

千秋楽,結びの一番.注目の場所での注目の一番だったので,数年ぶりにテレビで大相撲を観戦しました.内に秘めた闘志と,吹きあがる闘志とのぶつかりあいで,とても美しく心打たれる一番でした.大相撲もおもしろくなりそうです.

さて今回は割込みハンドラの入口の処理について解説します.そして次回は,割込みハンドラの出口処理について解説します.
第5回の中で,割込みが発生するとSHプロセッサが行うことについて解説しました.今回はそのプロセッサの処理以降,ASPカーネルが行う割込みの入口処理について詳しく解説していきます.【図9-1】




【図9-1】

まず,割込みが発生すると,SHプロセッサが自動的に行うことを確認します.(第5回 復習)

<割込み発生時のプロセッサ処理の流れ>
・ PCとSRをそれぞれSPC,SSRに退避
・ SRのBLを1にセット.以降すべての例外をブロック
・ SRのMDを1にセット.特権モードへ(ASPカーネルでは,タスクや割込みハンドラは特権モードで動作している)
・ SRのRBを1にセット,レジスタバンク1を使用
・ 例外要因をINTEVTとINTEVT2の該当するビットに書き込む.(ASPでは,INTEVT2を使用します.)
・ VBR+0x600から例外処理ルーチンを実行

これらの処理を行った上で,VBR+0x600番地に置かれた例外処理ルーチンを実行します(→割込み入口処理).

今回と次回は,以下の例で解説します.




優先度の高いタスクAは,slp_tsk()発行によって起床待ち状態になり,タスクA より優先度の低いタスクBが実行中となっています.そこで割込みが発生し,割込みハンドラから,iwup_tsk(TASKA)によって,タスクAが起こされる場合について見ていきます【図9-2】.
割込み処理の中で呼ばれたサービスコールによって,ディスパッチが必要となった場合でも,割込みハンドラ処理中には,ディスパッチはおこりません(→遅延ディスパッチ).よって,ディスパッチが必要な場合は,割込み処理の出口でディスパッチャに分岐します.



【図9-2 今回の例におけるタイミング図】
※プロセッサおよびカーネルによる処理時間の表記は省略しています
では,VBR+0x600番地に置かれた例外処理ルーチンを見ていきます.
まず,概要を解説します.割込みが発生すると上記のように,SHプロセッサが自動的に【割込み発生時のプロセッサ処理の流れ】を実行します.ASPカーネルは多重割り込みをサポートしています.多重割込みが発生すると,同じようにプロセッサが自動的に上記項目を実行します.したがって,割込みの入り口処理ではまず,多重割込みが発生して上書きされては困るレジスタを保存します.次に汎用レジスタをスタックに保存します.そして,割込み発生時のコンテキストがタスクの場合は,タスク用のスタックを使用しているので,非タスクコンテキスト用のスタックへ切り替えます.切り替えずにそのまま使うとスタックオーバーフローをおこす可能性があります.次に,割込み要因の割込み優先度マスクをSR(ステータスレジスタ)に設定することで,以降その割込み優先度より高い割込みしか受け付けないようにします.そして同時に,SRのBLビットがクリアされ割込みを受け付けます.最後に割込みハンドラを実行します.




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

540     .org    0x0600         /* _BASE_VBR + 0x0600番地に配置 */
541 .align 2
542 .global _interrupt_vec
543 _interrupt_vec:
544 stc.l spc,@-r15 /* 多重割込みが入ると消えてしまうので */
545 sts.l pr,@-r15 /* spc,pr,ssr,gbr,macl,mach */
546 stc.l ssr,@-r15
547 stc.l gbr,@-r15
548 sts.l macl,@-r15
549 sts.l mach,@-r15
550 stc.l r0_bank,@-r15 /* ,r0〜r7をスタックに保存 */
551 stc.l r1_bank,@-r15
552 stc.l r2_bank,@-r15
553 stc.l r3_bank,@-r15
554 stc.l r4_bank,@-r15
555 stc.l r5_bank,@-r15
556 stc.l r6_bank,@-r15
557 stc.l r7_bank,@-r15
558 tst r7,r7   /* 割込み発生時のコンテキストを判定*/
559 bf/s _interrupt_vec_1   /* 例外/割込みハンドラならジャンプ*/
560 add #0x01,r7 /* 割込みのネスト回数をインクリメント*/
561 mov r15,r1 /* スタックを入れ替え元のスタックポイ*/
562 mov.l _kernel_istkpt_int,r2   /* ンタを保存 */
563 mov.l @r2,r15
564 mov.l r1,@-r15
565 _interrupt_vec_1:
566 mov.l _intevt_int,r0 /* 例外要因レジスタを取得 */
567 mov.l @r0,r4
568 index_intno r4 /* オフセットを求める */
569 mov.l _int_iipm_tbl_int,r0 /* 割込み優先度マスクの値を取得 */
570 mov.l @(r0,r4),r5
571 mov.l _inh_tbl_int,r0 /* 割込みハンドラのアドレスを取得*/
572 mov.l @(r0,r4),r2
573 ldc r2,r2_bank /* バンク0に切り替えるためコピー*/
574 ldc r5,sr /* これ以降割込みを受け付ける。*/
575 jsr @r2 /* 割込みハンドラへ*/
576 nop


STEP1:レジスタ(SPC,PR,SSR,GBR,MACL,MACH)を,スタックに保存

544 stc.l spc,@-r15
545 sts.l pr,@-r15
546 stc.l ssr,@-r15
547 stc.l gbr,@-r15
548 sts.l macl,@-r15
549 sts.l mach,@-r15

多重割込みが入るとプロセッサによって自動的に上書きされてしまうSPC,SSRをスタック上に保存します.また,コーラーセーブドレジスタもスタック上に保存します.

第5回復習
・R0〜R7:呼び出された関数では,破壊してもよいスクラッチレジスタ(コーラーセーブドレジスタ).呼び出し元で保存する
・R8〜R13; 呼び出された側で保存(コーリーセーブドレジスタ)

SPC(退避プログラムカウンタ),PR(プロシージャレジスタ),SSR(退避ステータスレジスタ),GBR(グローバルベースレジスタ),MACL,MACH(積和下位,上位レジスタ)の順にスタックに積みます.このときのR15は割込み発生時のコンテキストが使用していたスタックポインタになります.今回の例では,タスクBになります.

STEP2:R0〜R7をスタックに保存

550 stc.l r0_bank,@-r15
551 stc.l r1_bank,@-r15
552 stc.l r2_bank,@-r15
553 stc.l r3_bank,@-r15
554 stc.l r4_bank,@-r15
555 stc.l r5_bank,@-r15
556 stc.l r6_bank,@-r15
557 stc.l r7_bank,@-r15

次に,コーラーセーブドレジスタ(呼び出し元で保存するレジスタ)を保存します.

第8回で解説した,タスクからサービスコールが呼ばれてディスパッチする場合は,コーラーセーブドレジスタは保存せずに,コーリーセーブドレジスタを保存しました.なぜなら,dispatch()で呼び出されたからです.今回は,次に割込みハンドラ(C言語関数)を呼び出すわけですから,コーラーセーブドレジスタを保存します.
R0〜R7はバンク構成になっていて,割込み発生時は,プロセッサによってBANK1に切り替わっています.割込み発生前はBANK0のレジスタを使用していますので,ここでは,r0_bankとしてBANK0のレジスタにアクセスし,保存しています.

STEP3:割込み発生時のコンテキストを判定

558 tst r7,r7
559 bf/s _interrupt_vec_1
560 add #0x01,r7


コンテキストの判定方法
汎用レジスタは,R0〜R15までの計16本用意されています.この16本の汎用レジスタのうちR0〜R7の8本は,BANK0とBANK1の2枚のレジスタバンク構成となっています
ASPカーネルでは,このバンク構成を割込みの入口で使用します.通常,例外処理では作業領域を得るために汎用レジスタの退避/復帰処理を行いますが,SHではレジスタバンクを用いることで,退避/復帰作業をハードウェアで行うため,処理にかかるオーバーヘッドが軽減されます.(詳細は第5回で解説済み)
さらに,ASPカーネルでは,BANK1のR7をコンテキスト判定用に使用しています.割込み処理の入り口で,+1し,出口で−1します.これによって,BANK1のR7が0であれば,実行中のコンテキストはタスク,1以上であれば,非タスクコンテキストが実行中ということになります,さらに,この値は多重割込み発生時のネストカウンタでもあります.
つまり,割込み発生時にBANK1のR7を調べて,0ならタスクに対して割込みが入った,1以上なら多重割込みが発生したということがわかります.

tst  Rm,Rn  ⇒ Rm & Rn 結果が0のとき,1→T 0でないとき,0→T
bf/s          ⇒ <遅延分岐(後で解説)>T=0のときジャンプ,T=1のとき,nop(次の行へ)

ここではすでに,割込みの発生によりプロセッサによってバンクが切り替わり,BANK1となっています.
558,559行目:R7どうしの論理積の結果が0でなければ,つまり非タスクコンテキストに対して割込みが入った(多重割込み)のであれば,_interrupt_vec_1にジャンプします.

560行目

add Rm, Rn ⇒ Rn + Rm → Rn


R7を1プラスして,割込みネストカウンタをUPします.

※補足 遅延分岐,遅延スロット
SH3は5段パイプラインを搭載しています.ある命令を実行しているときには,すでに2命令先の命令を読みだしています.しかし,分岐命令のようにPCが変化するような命令を発行した場合,分岐命令を実行する段階で後続する命令が無効となり,プロセッサのスループットが低下します.遅延分岐とは,分岐命令直後の命令を無効にせず,実行を継続します.このような,直後の命令を常に実行するような分岐命令を遅延分岐命令と呼び,条件によらず常に実行される命令が入る位置を遅延スロットと呼んでいます.
今回の例ですと,559行目の,bf/sは遅延分岐命令,560行目のadd・・・は遅延スロットです.
つまり,559行目で分岐する前に,560目のadd・・・が実行されます.

STEP4:非タスク用のスタックポインタに切り替える

561 mov r15,r1
562 mov.l _kernel_istkpt_int,r2
563 mov.l @r2,r15
564 mov.l r1,@-r15


ここでは,スタックポインタの切り替えを行います.
多重割込みの場合は,すでに非タスクコンテキスト用のスタックになっているので,切り替えの必要はないので,STEP4は飛ばします.この場合は,割込まれた割込み処理が使用していたスタックの上を,割り込んだ割込み処理が続けて使用します.
【図9-3】は,タスク実行時に割込まれた場合の図です.STEP3までのスタックポインタは,タスクBのスタックポインタでした.ここでは,非タスクコンテキスト用のスタックポインタをR15に格納することで,スタックポインタをタスク用から,非タスク用に切り替えます(GNUコンパイラは,R15をスタックポインタに割り当てます)._kernel_istkpt_intは,非タスクコンテキスト用のスタック領域のアドレスを格納する変数のアドレスを表します.



【図9-3 スタックポインタの切り替え】

561行目:現在実行中のタスクBのスタックポインタをR1にいったん退避します.
562〜563行目:非タスク用のスタックポインタを,R15に登録することでスタックポインタを切り替えます.
564行目:切り替える前のタスク用(タスクB)のスタックポインタの値を,非タスク用のスタック上に保存します.

STEP5:割込み要因の取得

566 mov.l _intevt_int,r0
567 mov.l @r0,r4

例外要因レジスタにプロセッサが設定した,例外要因番号をR4に取得します._intevt_intは,例外要因レジスタINTEVT2のアドレスを表します.

568 index_intno r4

index_intnoは,5ビット右シフトするマクロで,別のファイルに定義しています.どうしてそのようなマクロを使用しているかといいますと,例外要因は,900,920などという大きくかつまばらな数となっています.コンフィギュレータが静的APIを解析し,割込み要因番号順に,割込み優先度マスクテーブル(int_iipm_tbl)と,割込みハンドラのアドレステーブル(inh_tbl)を作成します.この2つのテーブルint_iipm_tbl,inh_tblは,コンフィギュレータによって自動生成された,kernel_cfg.c の中で定義されています.JSPカーネルでは生成していませんでしたが,ASPカーネルは生成するようになりました.
このテーブルから該当割込みの,割込み優先度マスクと割込みハンドラのアドレスを引いてきます.しかし,900,920という例外要因番号をそのままインデックスとして使用するには大きすぎ無駄があります.この例外要因番号の,下5ビットはすべて0なので,5ビット右シフトしてインデックス番号として使用しやすいようにしています.この処理によって,R4には例外要因に対応するインデックスが格納されます.

STEP6::割込み優先度マスクの値を取得

569 mov.l _int_iipm_tbl_int,r0
570 mov.l @(r0,r4),r5

受け付けた割込みより優先度の高い割込みのみ受け付けるように,割込みハンドラの優先度をSRのI3〜I0(割込みマスクビット)に設定する必要があります.その値を,先ほど設定したR4をインデックスとして,int_iipm_tblから取得します.int_iipm_tblは前述の,コンフィギュレータが生成した割込み要因ごとの,割込み優先度マスクが入っているテーブルです.
569行目:_int_iipm_tbl_intは,テーブルint_iipm_tblのアドレスです.そのアドレスをR0に格納しています.
570行目:int_iipm_tblからインデックスがR4の値を,R5に取得します.

STEP7:割込みハンドラのアドレスを取得

571 mov.l _inh_tbl_int,r0
572 mov.l @(r0,r4),r2

割込みハンドラのアドレスをR4をインデックスとして,テーブルinh_tblから取得します.inh_tblは前述の,コンフィギュレータが生成した割込み要因ごとに,割込みハンドラのアドレスが入っているテーブルです.

571行目:_inh_tbl_intは,テーブルinh_tblのアドレスです.そのアドレスをR0に格納しています.
572行目:inh_tblから,インデックスがR4の値を,R2に取得します.

573行目: ldc r2,r2_bank

現在,BANK1なので,割込みハンドラのアドレス(R2)を,BANK0のR2にコピーします.ジャンプするための準備です.

STEP8::割込み優先度マスクの値をSRにセット,BLビットをクリア

574 ldc r5,sr

ここで,先ほどR5に取得した割込み優先度をSRに設定します.この処理では,割込み優先度がSRのI3〜I0(割込みマスクビット)に設定されるだけでなく,同時にRB(レジスタバンクビット)が0,BL(ブロックビット)も0となります.RBが0となることにより,バンクがBANK0に切り替わり,これ以降はBANK0を使用します.BLが0となることで,これ以降割込みを受け付けます.

STEP9:割込みハンドラへジャンプ

575 jsr @r2
573行目でR2に割込みハンドラのアドレスを格納しましたので,575行目で割込みハンドラへジャンプします.jsrでジャンプすると,戻ってくるときは次の行に処理が進みます.つまり割込みハンドラの処理が終わると,次の行(_ret_int)に戻ります.これ以降は,次回解説します.