平成 25 年度 第 1 回スキルアップ研修 組込み OS 自作入門 7th ステップ割込み処理を実装する 2013 年 11 月 6 日担当 : 池田亮
第 2 部 OS の作成 残りもあと半分になりました 7thステップ 割込み処理を実装する 8thステップ スレッドを実装する 9thステップ 優先度スケジューリング 10thステップ OSのメモリ管理 11thステップ タスク間通信を実装する 12thステップ 外部割込みを実装する
7th ステップ割込み処理を実装する この節で学習すること H8/3069F の割込み処理 この節で作成するもの 割込みベースのコマンド応答プログラム キーワード 割込み 割込みベクタ 割込みハンドラ シリアル受信割込み
本日の流れ もくもく会の前 7.1 割込み処理 7.2 H8/3069F の割込み処理 もくもく会 7.3 ブート ローダーに割込みハンドラを実装する もくもく会の後 7.4 プログラムの実行 7.5 まとめ 7.3 ブート ローダーに割込みハンドラを実装する
もくもく会の前 7.1 割込み処理 7.2 H8/3069F の割込み処理
7.1 割込み処理 処理の流れ 割込み発生! 処理の流れ 割込みが発生し 割込みハンドラにジャンプする 割込みハンドラ 本来の処理に戻る 通常の場合 割込みが発生した場合
割込みハンドラ 割込み発生時に実行される処理のこと 割込みハンドラ (interrupt handler) と呼ばれたり 或いは コールバック (call back) とも呼ばれる
割込み処理の要点 割込み発生時に実行させたい処理を 割込みハンドラとし て登録しておく 割込みが発生した際には 現在実行中の処理を中断し 登 録しておいた割込みハンドラが実行される 割込みハンドラの実行後に 中断していた本来の処理に戻 る
7.1.1. 割込みで処理したいものは何か? どのようなときに使われるものなのか? 例として シリアルによる文字受信を考えてみる
シリアル ドライバ serial.c int serial_is_recv_enable(int index) { volatile struct h8_3069f_sci *sci = regs[index].sci; return (sci->ssr & H8_3069F_SCI_SSR_RDRF); } unsigned char serial_recv_byte(int index) { volatile struct h8_3069f_sci *sci = regs[index].sci; unsigned char c; } while (!serial_is_recv_enable(index)) { ; } c = sci->rdr; sci->ssr &= ~H8_3069F_SCI_SSR_RDRF; return c; この while ループで受信を待ち続ける
ビジー ループ serial_recv_byte() 中の while ループ SCI の SSR レジスタの RDRF ビットが 1 になった瞬間抜ける ( 逆に言うと 0 の間 処理の流れはずっと止まっている ) 確認事項 SCI(Serial Communication Interface) SSR(Serial Status Register) RDRF(Receive Data Register Full)
RDRF ビット 受信データが RDR に格納されているか示す 0: 格納されていない 1: 格納されている
main() 関数から辿ってみる int main(void) { } while (1) { } puts("kzload> "); gets(buf); return 0; unsigned char getc(void) { unsigned char c = serial_recv_byte(serial_default_device); } return c; int gets(unsigned char *buf) { } do { c = getc(); } while (c); return i - 1;
受信待ちしつつ別の処理がしたいとき ビジー ループの場合 別の処理を行いながら 定期的にSSRレジスタを見て RDRFビットの状態をチェックするような処理が必要になる シリアル受信割込みの場合 シリアルからの文字受信時の処理を割込みハンドラとして実装しておけば 普段は別の処理を行っていて 文字を受信したときにその処理を行うことができる ( 送信も同様 )
7.1.2 割込み要因と外部割込み 割込み要因 どんなときに割込みが発生するのか 割込みを起こす原因 となる現象のこと 通常の CPU では 複数の割込み要因を受け付けることがで きる 割込みの受け付けは 割込み要因ごとに ON/OFF でき 割 込みハンドラもそれに応じて複数用意できる
割込み要因の いくつかの例 割込み要因 ( 割込みを起こす原因となる現象 ) タイマ コントローラに設定した時刻が満了したシリアルから文字を受信したリセット ボタンが押されたゼロ除算が行われた不正なアドレスにアクセスしたシステム コール命令が実行されたトラップ命令が実行された 割込み要因の種類タイマ割込みシリアル受信割込みリセット割込みゼロ除算例外不正アドレス アクセス例外システム コール割込みトラップ割込み
CPU が割込みを受け付ける仕組み 電圧で検知 周辺 I/O は割込み出力線というピンを CPU は割込み受け 付けのピンを それぞれ持つ 出力ピンと入力ピンの間にかかっている電圧の高い低い (High/Low) で 割込み発生を検知する ( これを アサート される とか キックされる と言う ) 研修で使うマイコンボードの場合 High は 5V で Low は 0V
ピン配置図
正論理と負論理 正論理 普段 Low レベルで 割込み発生で High レベル 前頁の NMI 負論理 普段 Highレベルで 割込み発生でLowレベル前頁のIRQ0~IRQ5 負論理の信号は ピン名の上にラインがついたり ピン名の前に # がついたりする
割込み要因をざっくり分ける 外部割込み ( ハードウェア割込み ) 周辺 I/O に割込み要因が発生して 割込み線がアサートさ れることで発生する割込み 内部割込み ( ソフトウェア割込み ) CPU 内部の処理での不正発生などによって起きる割込み たとえば不正アドレス アクセスやゼロ除算など 割込みはプログラム側で わざと 発生させることも可能
7.1.3 割込みと例外 割込みの種類を次のように分けてまとめます 1. 外部ハードウェアによって割込み線がアサートされる ことで発生する割込み 2. 不正メモリ アクセスやゼロ除算などにより CPU 内部 で発生する割込み ( 例外 ) 3. システム コール命令やトラップ命令などによりプロ グラム側から わざと 発生させる割込み
7.1.4 割込み入力が 1 本の場合 割込み入力ピンが 4 本ある CPU と 1 本だけある CPU について 割込み受け付けの実現方法を考察
割込み入力ピンが 4 本ある CPU IRQ0 割込み出力 タイマ コントローラ CPU IRQ1 IRQ2 IRQ3 割込み出力 シリアル コントローラ
割込み入力ピンが 1 本だけある CPU 割込み出力 タイマ コントローラ CPU IRQ 割込み出力 シリアル コントローラ
まとめると 割込み入力ピンが複数本 周辺 I/O の割込みを個別に扱える他 割込みハンドラも 別々に登録することができる 割込み入力ピンが 1 本 CPUから見れば割込みは1つに見える ( 割込みベクタも1つ ) タイマ割込みとシリアル割込みで共通の割込みハンドラを実行 ( 割込みハンドラの中で どちらの割込みか調べるので面倒 )
7.1.5 割込みコントローラ 割込み出力 タイマ コントローラ CPU IRQ 割込みコントローラ 割込み出力 シリアル コントローラ
割込みコントローラ 主な機能 複数の割込みをOR 論理で結合してCPUに通知各割込みの有効 / 無効を設定割込み優先度の制御 その他 割込みコントローラの持つレジスタを見れば どのコント ローラから発生した割込みなのかを知ることができる
7.1.6 割込みベクタと割込みハンドラ 割込み発生時の動作 1. 割込み発生時には 特定のアドレスから実行する 2. 割込み発生時には どのアドレスから実行するか特定のアドレスに設定しておく 言い替えると 1. 割込み発生時には 特定のアドレスに強制ジャンプする 2. 割込み発生時には 特定のアドレスからジャンプ先アドレスを読み込んで そこにジャンプする
割込みハンドラ再び 割込み発生時に実行される処理のこと 割込みハンドラ (interrupt handler) と呼ばれたり 或いは コールバック (call back) とも呼ばれる ジャンプ先に配置される 割込みの処理を行うプログラム ISR(Interrupt Service Routine: 割込みサービスルーチ ン ) と呼ばれる場合もある
詳しく 1. の方法 割込みハンドラを特定のアドレスに配置 割込みの発生時には割込みハンドラが勝手に実行される この特定のアドレスには決まった数の命令しか記述できないため 実際の割込みハンドラは別アドレスに置いて 特定アドレスには そこにジャンプするための命令群を置くだけにすることも多い
さらに詳しく 2. の方法 割込みハンドラを適当な場所に配置し その配置されたア ドレスをメモリ上の特定のアドレスに書いておく このアドレスを書いた配列を割込みベクタ (interrupt vector) と呼び この方法をベクタ割込み方式と呼ぶ 割込みベクタをベクタ テーブルと呼ぶこともある
割込み処理の要点再び 割込み発生時に実行させたい処理を 割込みハンドラとし て登録しておく 1. 割込みハンドラを用意する 2. 割込みハンドラの先頭アドレスを割込みベクタに書き 込む
3rd ステップのメモリ マップ再び 0x000000 0x0000ff 0x07ffff 割込みベクタ 内蔵 ROM (512 KB) 0xffbf20 0xffff1f 0xffff20 0xffffe9 内蔵 RAM (16 KB) 内蔵 I/O レジスタ
割込みベクタの定義 vector.c #include "defines.h" extern void start(void); /* スタート アップ */ /* 割込みベクタの設定 */ /* リンカ スクリプトの定義により先頭番地に配置される */ void (*vectors[])(void) = { start, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, }; start() へのポインタを設定
7.1.7 割込み処理で行われること 情報の退避 割込みハンドラの終了時には 中断された処理に戻ってそのまま実行を再開しなければならないので そのための情報をCPUが割込み発生時に自動的に退避しておく必要がある 多くの CPU には 割込み復帰命令 (Return From Interrupt instruction: RFI instruction) があり 割込みからの復帰 に利用される
割込み復帰命令 情報の復帰 割込みハンドラの最後に割込み復帰命令が置かれる 割込み復帰命令で行われるのは 割込み発生時に退避して おいた情報の復帰 これにより中断時の状態に戻り 中断していた箇所から処 理を再開する
情報の退避と復帰で問題になること どこに退避するのか? スタック または 退避先の専用のレジスタ 退避が必要な情報は何か? プログラム カウンタ と モード レジスタ 一般的な CPU では 上記の値が自動保存される 情報を保存している隙に割り込まれないようアトミックに 保存処理を実行する必要がある
7.1 割込み処理再び 処理の流れ 割込み発生! 処理の流れ 割込みが発生し 割込みハンドラにジャンプする プログラム カウンタ ( 現在実行中のアドレスを保持するレジスタ ) 割込みハンドラ 本来の処理に戻る 通常の場合 割込みが発生した場合
モード レジスタ 多くの CPU に装備される特殊なレジスタ モード レジスタもしくはコントロール レジスタと呼ぶ モード レジスタが持つ情報の例 割込み禁止 特権モードとユーザー モード メモリ保護の有効 / 無効の切り替え 仮想メモリによるアドレス変換の有効 / 無効の切り替え
7.1.8 割込みハンドラの入口と出口 割込みハンドラの入口 汎用レジスタの値をどこかに保存しておく その他 プログラム中で利用しているレジスタの値も保存しておく 割込みハンドラの出口 汎用レジスタの値を保存先から復旧する その他 プログラム中で利用しているレジスタの値も復旧する
KOZOS の場合 すべての汎用レジスタを退避 考え方や説明を簡便 ( シンプル ) にするための工夫 理想としては 割込みハンドラの内部で利用する ( 値が書き換わる ) レジス タのみ退避する コンパイラによって使い分けられる汎用レジスタのうち 揮発性レジスタのみ保存すればよい
7.1.9 割込み要因のクリア 割込みハンドラで行うべき処理の内容 シリアル受信割込みの場合 シリアル コントローラのレジスタを参照し 受信した文字を受け取る シリアル コントローラのレジスタを操作し 割込みフラグを落とす ( 割込み要因のクリア ) 割込み要因のクリアを行わないと 割込み復帰命令を呼び出した直後 再度割込みがかかってしまう
7.2 H8/3069F の割込み処理 前節では 一般的な CPU 全般における割込みの概要につい て説明 ここからは H8/3069F の割込み処理について説明
7.2.1 H8/3069F の割込みベクタ H8 の割込み ベクタ割込み方式を採用しているので 割込み発生時には割込みベクタを参照し そこに登録された割込みハンドラへジャンプ 割込みベクタの位置 メモリの先頭アドレスである0x000000 以降の256バイト (0x000000~0x0000ff) 1つの割込みベクタは4バイト 割込み要因は64 個程度
3rd ステップのメモリ マップ再び 0x000000 0x0000ff 0x07ffff 割込みベクタ 内蔵 ROM (512 KB) 0xffbf20 0xffff1f 0xffff20 0xffffe9 内蔵 RAM (16 KB) 内蔵 I/O レジスタ
さらに詳しく 0x000000 0x000001 0x000002 0x000003 0x000004 0x000005 0x000006 0x000007 割込み番号 0 割込み番号 1 1Byte 1Byte 1Byte 1Byte 1 つの割込みベクタは ( アドレスであるため ) 4Byte 0x0000fc 0x0000fd 0x0000fe 0x0000ff 割込み番号 63
H8/3069F マニュアル 4.1.3 表 4.2
H8/3069F マニュアル 5.3.3 表 5.3
H8/3069F マニュアル 5.3.3 表 5.3
補足説明 リセット H8 では電源 ON も割込みのひとつとして扱われ リセッ トは電源 ON 時に実行される割込み NMI(Non-Maskable Interrupt) マスクできない ( 無効化できない ) 割込みで CPUのNMI ピンで受け付ける 無効化したくないような重要な ( 最優先の ) 割込みを割り当てる
補足説明続き SCI チャネル 1 RXI1 は受信完了時に発生する割込み TXI1 は送信 ( データエンプティ ) 完了時に発生する割込み
7.2.2 レジスタの退避 コンディションコードレジスタ (CCR) 一般的なCPUでいうところのモード レジスタに相当するものとして H8ではコンディションコードレジスタという 1バイトのレジスタを持つ CCR で持っている情報 割込みの有効 / 無効フラグ オーバーフロー フラグ キャリ フラグなど
割込み発生時のスタックの動作 割込み発生! 0xff00f8 0xff00f9 0xff00fa 0xff00fb 0xff00fc 0xff00fd 0xff00fe 0xff00ff 0xfffeff 0xffff00 ER7 = ER7-4 ER7 が指すアドレス スタックの底
7.2.3 割込みコントローラ H8 の割込みコントローラ H8/3069Fは割込みコントローラを内蔵しており H8/3069Fが持つNMIと外部割込みの入力ピン (IRQ0~ IRQ5) が接続されている 使い方 割込みコントローラのレジスタを設定することで 割込みに優先順位を持たせたり 割込みの有効 / 無効をレベル制御したりなどの細かい制御が行える
ブロック図
H8/3069F マニュアル 5.1.1 図 5.1
7.2.4 割込み禁止と割込み復帰命令 CCR による制御 H8ではCCRで割込みの有効 / 無効を設定できる CCRは1バイトのレジスタで 最上位ビット (Iビット) を 1にすることで割込みが無効になる 割込み復帰命令 rte rte を実行すると スタックからプログラムカウンタと CCR の値を復旧する
まとめると 1. 割込み発生時には まずスタック上 ( スタックポインタの指す先 ) に プログラム カウンタと CCR の値を保存する 2. その後 割込み要因に応じて割込みベクタを参照し 割込みハン ドラにジャンプする ( プログラム カウンタに割込みベクタの値を 代入する ) 3. 割込みハンドラが実行される ( ユーザ側で用意したハンドリング処 理を行う ) 4. 割込みハンドラの終了時には 割込み復帰命令 (rte) を呼ぶ これによりスタックからプログラム カウンタと CCR の値が復旧 されて元に戻り 中断していた箇所から実行が再開される
もくもく会 7.3 ブート ローダーに割込みハンドラ を実装する
ブート ローダー [ 今回追加するファイル ] ファイル名 ファイルの内容 リスト intr.h, intr.s 割込み処理の入口と出口 7.2, 7.3 interrupt.h, interrupt.c ソフトウェア 割込みベクタの処理 7.4, 7.5 [ 今回修正するファイル ] ファイル名 ファイルの内容 リスト ld.scr ソフトウェア 割込みベクタを追加 7.1 vector.c ソフトウェア 割込みベクタを設定 7.6 main.c ソフトウェア 割込みベクタの初期化を追加 7.7 Makefile 7.8
OS [ 今回追加するファイル ] ファイル名ファイルの内容リスト intr.h interrupt.h, interrupt.c ブート ローダーと同じ ブート ローダーと同じ [ 今回修正するファイル ] ファイル名 ファイルの内容 リスト ld.scr ソフトウェア 割込みベクタを追加 7.9 serial.h, serial.c 割込み関連のサービス関数を追加 7.10, 7.11 main.c シリアル受信割込みによる動作に変更 7.12 Makefile 7.13
手順の一例 $ cd src/ $ cp -R 06/ 07/ 6th ステップをコピーして $ cd 07/bootload/ ディレクトリを移動 $ cp serial.h intr.h 適当にファイルコピー $ cp serial.h interrupt.h $ cp serial.c interrupt.c $ emacs ld.scr 編集作業開始 $ make ブート ローダーもくもく会 $ make image 終わったら適当に make
しばらく黙々とコーディング 山田さんから注意 intr.s を intr.s としていたら エラー出ます 理由 アセンブラのファイルをアセンブルする場合 ファイル名が *.S の場合はプリ プロセスが行われますが *.sの場合は行われないためです
プリ プロセス プリ プロセス (pre-process) #includeによるファイル挿入 #ifdefによる条件コンパイル #defineによるキーワード変換 プリ プロセッサ (pre-processor) プリ プロセスを行うツールのこと
もくもく会の後 7.4 プログラムの実行 7.5 まとめ 7.3 ブート ローダーに割込みハンドラを実装する
7.4 プログラムの実行 実行手順 1. ブート ローダーとOSをビルド 2. ブート ローダーをマイコンボードに書き込む 3. ブート ローダーを起動する 4. loadコマンドを実行し OSをダウンロードする 5. OSのダウンロード後 runコマンドでosを起動する 動作の様子としては 6th ステップと同じになります
7.5 まとめ 本ステップでの進捗 割込み処理を実装した 割込み処理の重要性 割込みは組込みやOSなどのプログラミングを行う上で避けては通れない 非常に大切な知識 不明な点などあれば 繰り返し読んだり調べたりするなどして 十分に習得すべし
質疑応答など 7.3 ブート ローダーに割込みハンドラ を実装する
次回の開催予定 日時 12 月 4 日 ( 水 ) 13:00~ 担当 松元さん テーマ 8th ステップスレッドを実装する