トロンフォーラム

第10回 割込み管理機能

割込み管理機能

今回は、T-Kernelの割込み管理機能について説明します。

前回までは主にタスクが主体となって行う処理を中心にT-Kernelの機能を説明してきました。それに対して割込みは、タスクとは独立して実行される処理です。そこで、T-Kernelにおける割込みの利用方法に加えて、実行時のコンテキストの違いから生じる動作の違い、割込みハンドラの作成方法や動作の詳細を説明します。

割込みとは?

割込みとは、その名の通り、実行中の処理を中断して(割り込んで)別の処理を実行することです。割込みは、主に周辺機器が状態の変化をCPUに通知する時や、CPU例外をプログラムで処理する時に用いられます。例えば、キーボードを叩くとキーに対応した文字が画面に表示されますが、この「キーが叩かれたこと」は割込みを用いてCPUに通知されることが多いです。

図1 割込み

図1 割込み

割込みの利点は何でしょう?割込みを利用すると、CPU資源を効率的に使用しながら応答性を向上することができます。割込みを用いない場合は、CPUが定期的に周辺機器の状態を調べる必要があります。これをポーリングと呼びます。ポーリングでは最悪の場合、周期時間だけ遅れてから周辺機器の状態変化を知ることになります。そのため、高い応答性を得るには十分に短い時間間隔でポーリングを行わなければなりません。しかし、ポーリングの時間間隔を短くすると、その分だけCPU処理が増加してCPU負荷が高くなります。一方、割込みを用いると、CPUは周辺機器が割込みを要求するまでは他の処理を実行できます。もし他に実行すべき処理がなければCPUを休ませることもできます。割込み要求がきたら実行中の処理を中断して割込みに対応した処理を行うので高い応答性も実現できます。

しかし、割込みも利点ばかりではありません。実行中の処理を中断して別の処理を行うということは、その別の処理が完了した後に元の処理へ復帰する必要があります。そのためには、割込み発生後に実行中の処理を中断する時、再びその処理へ復帰できるように現在の状態を適切に保存しなければなりません。元の処理へ復帰する時も、保存された情報から適切に状態を戻さなければなりません。このように割込み処理には若干のオーバーヘッドがあるため、極めて高頻度で割込みが発生するような状況であれば、ポーリングで定期的に状態変化を調べる方が効率的な場合もあります。

図2 割込みとポーリング

図2 割込みとポーリング

T-Kernelの割込み管理機能

割込みが発生すると、実行中の処理が中断され、その割込みに対応したコールバックルーチンが実行されます。このコールバックルーチンを割込みハンドラと呼びます。T-Kernelの割込み管理機能は、周辺機器からの外部割込みとCPU例外を対象に割込みハンドラの定義などの操作を行う機能です。T-Kernelでは割込み管理用に以下のシステムコールを提供します。

  • tk_def_int
    割込みハンドラ定義
  • tk_ret_int
    割込みハンドラから復帰

tk_def_intで登録する割込みハンドラは、高級言語(C言語)またはアセンブリ言語で記述することができます。

高級言語で記述する場合、カーネルの高級言語対応ルーチンを経由して割込みハンドラが起動されます。高級言語対応ルーチンは実行中の状態の保存と復元を行います。具体的には、レジスタを退避したり復帰したりすることにより実行中の状態を保持します。割込みハンドラは、C言語関数からのリターンによって終了します。その後、カーネルの高級言語対応ルーチンはtk_ret_intを呼び出します。

一方、アセンブリ言語で記述する場合、原則として、割込みハンドラの起動時にはカーネルが介入しません。実装によってはプログラムによる処理が含まれる場合もありますが、割込み発生時には、ハードウェアの割込み処理機能により、tk_def_intで定義した割込みハンドラが直接起動されます。割込みハンドラの先頭と最後では、割込みハンドラで使用するレジスタの退避と復帰を行う必要があります。割込みハンドラは、tk_ret_intまたはCPUの割込みリターン命令(またはそれに相当する手段)によって終了します。

図3 高級言語とアセンブリ言語

図3 高級言語とアセンブリ言語

また、システムコール以外にも、ライブラリ関数またはC言語のマクロとして提供されるAPIがあります。

  • CPU割込み制御
    DI
    : すべての外部割込み禁止
    EI
    : すべての外部割込み許可
    isDI
    : すべての外部割込み禁止状態の取得
  • 割込みコントローラ制御
    DINTNO
    : 割込みベクタから割込みハンドラ番号へ変換
    EnableInt
    : 割込み許可
    DisableInt
    : 割込み禁止
    ClearInt
    : 割込み発生のクリア
    EndOfInt
    : 割込みコントローラにEOI発行
    CheckInt
    : 割込み発生の検査
    SetIntMode
    : 割込みモード設定

ただし、割込みに関する機能はハードウェアに強く依存するため、これらのライブラリ関数やマクロが実装されていない場合もあります。詳細は各実装仕様書を参考にしてください。実際、T-Kernel 2.0仕様書にも以下のような記述があります。

割込み関係はハードウェア依存度が高く、システムごとに異なっているため共通化することが難しい。下記を標準仕様として定めるが、システムによってはこの通りに実装することが難しい場合がある。できる限り標準仕様に合わせた実装を求めるが、実装不可能なものは実装しなくてもよい。標準仕様とは別の機能を追加することも許されるが、その場合は関数名などは標準仕様と異なるものでなければならない。ただし、DI(), EI(), isDI() は標準仕様にしたがって、必ず実装しなければならない。
割込みハンドラ実行時のコンテキスト

割込みハンドラはタスクの進行とは全く別の要因で動作が始まり、どのタスクにも属さないコンテキストで処理が実行されます。言い換えれば、割込みハンドラは割込みが発生した時に実行中のタスクとは無関係のコンテキストで処理されます。一方、タスクが処理を実行している間は常に実行中のタスクに属するコンテキストで処理されます。T-Kernelでは、前者の割込みなどのタスクには属さないコンテキストをタスク独立部と呼び、後者のタスクに属するコンテキストをタスク部と呼びます。

タスク独立部でもタスク部と同じ形式でシステムコールを発行することは可能です。タスク独立部の特徴は、タスク独立部に入る直前に実行中だったタスクを特定することに意味がないことです。そのため、「自タスク」の概念が存在しません。したがって、タスク独立部からは、待ち状態に入るシステムコールや、暗黙で自タスクを指定するシステムコールを発行することはできません。

また、タスク独立部では現在実行中のタスクを特定できないので、タスクの切り換え(ディスパッチ)も起きません。ディスパッチが必要になると、それはタスク独立部を抜けるまで遅らされます。これを遅延ディスパッチ(delayed dispatching)の原則と呼びます。このようにすることにより、タスクの処理よりも割込みの処理を優先して実行することができます。

図4 遅延ディスパッチ

図4 遅延ディスパッチ

今度は割込みがネストして発生する場合を考えてみましょう。例えば、図5は、タスクAの実行中に割込みXが発生し、その割込みハンドラ中でさらに高優先度の割込みYが発生した状態を示しています。この場合、割込みハンドラBのtk_wup_tsk発行直後にディスパッチを起こすと、以降の割込みハンドラの処理がタスクの処理よりも後回しになります。また、仮に(1)の割込みYからのリターン時に即座にディスパッチを起こしてタスクBを起動すると、割込みXの(2)~(3)の部分の実行がタスクBよりも後回しになり、タスクAが実行状態になった時にはじめて(2)~(3)が実行されることになります。これでは、低優先度の割込みXのハンドラが、高優先度の割込みばかりではなく、それによって起動されたタスクBにもプリエンプトされる危険を持つことになります。したがって、割込みハンドラがタスクに優先して実行されるという保証がなくなり、割込みハンドラが書けなくなってしまいます。これを避けるため、割込みがネストした場合は、全ての割込みハンドラの処理が完了してから、実行可能なタスクの実行が再開されます。

図5 割込みのネストと遅延ディスパッチ

図5 割込みのネストと遅延ディスパッチ

上記の内容を整理すると、割込みハンドラの処理には以下のような制限がつきます。

  • 暗黙に自タスクを指定するシステムコールは発行できません。
  • 自タスクを待ち状態にするシステムコールは発行できません。
  • 遅延ディスパッチが適用され、割込みハンドラの処理が終了するまでタスクディスパッチは発生しません。

このような制約があることに加えて、一般に割込みハンドラの実行中は同じ(あるいはより低い)優先度を持つ他の割込みも禁止されるため、基本的に割込みハンドラでは複雑な処理はしません。複雑な処理が必要な場合は、タスクを利用してそこで必要な処理を行うようにプログラムを設計する必要があります。

割込みを用いたプッシュスイッチ押下の検出

それでは、割込みを用いたサンプルプログラムを見ていきましょう。このサンプルプログラムでは、T-Kernel 2.0の機能を利用して、T-Engineリファレンスボードのプッシュスイッチの押下を割込みで検出します。

プッシュスイッチはSW1, SW2, SW3, SW4の合計4種類で、それぞれ異なる割込みを発生させます。サンプルプログラムでは、プッシュスイッチが押されたらどれが押されたかコンソールに出力します。この「コンソールに文字を出力する」処理は、出力が完了するまでに多くの時間を要します。割込みハンドラの中でこのような長い処理を実行すると、その間は他の割込みが禁止され、タスクのディスパッチも遅延しますので、そのようなプログラム設計は適切ではありません。そこで今回は、コンソールに文字を出力するタスクを割込みハンドラとは別に生成し、割込みハンドラではイベントフラグを使ってそのタスクにプッシュスイッチの押下を通知します。

※ 以下のサンプルプログラムはこちらからダウンロードできます。

まずは割込みハンドラを定義します。SW1の割込みハンドラのコード例は以下の通りです。

この割込みハンドラは、押下されたプッシュボタンに対応するフラグパターンをイベントフラグに設定し、ClearIntで割込み発生をクリアします。SW2, SW3, SW4 にもinthdr_sw1と同様の関数を定義します。

ところで、inthdr_sw1では最後にClearIntで割込み発生をクリアしていますが、ClearIntで割込み発生をクリアしない場合はどのようになるでしょうか?割込みコントローラは、割込みが発生している状態を保ち続けるので、割込みハンドラから戻ろうとしても、再度同じ割込みハンドラが呼ばれ続けます。その結果、タスクが呼び出されることはなく、コンソールには何も表示されません。なお、T-EngineリファレンスボードではClearIntを呼ぶ必要がありますが、システムによってはClearIntを呼ぶ必要がないこともあります。ハードウェア仕様書や実装仕様書の内容を確認して、システムに合うように記述してください。

続いて、tk_def_intを用いて割込みハンドラを定義します。

IV_GPIOは、GPIO割込みの割込みベクタを求めるT-Engineリファレンスボード固有のマクロです。SW1, SW2, SW3,SW4はそれぞれGPIO8, GPIO7, GPIO6, GPIO4に対応しています。また、割込みハンドラはC言語(高級言語)で記述されているので、tk_def_intにTA_HLNG属性を指定します。

それから、割込みに関する設定を行います。

まず、SetIntModeで割込みモードを設定します。SetIntModeで設定可能な項目は実装依存です。T-Engineリファレンスボードでは以下の項目を指定することができます。

プッシュスイッチの論理は、押し下げると1に、離すと0になるので、立ち上がりエッジを検出できるように割込みモードを設定します。立ち上がりエッジに対応する項目は、IM_EDGE(エッジ割込み)とIM_HI(立ち上がりエッジ)の論理和です。それから>ClearIntで割込み発生をクリアして、最後にEnableIntで割込みを許可します。

Return Top