μT-Kernelの上で動くタスクのプログラミングを行う場合には、タスク状態遷移図を見て、各タスクの状態の変化を追っていけばよい。しかし、割込みハンドラや拡張SVCハンドラなど、タスクよりカーネルに近いレベルのプログラミングもユーザが行う。この場合は、非タスク部、すなわちタスク以外の部分を実行している間のシステム状態についても考慮しておかないと、正しく動作するシステムを開発できない。ここでは、μT-Kernelのシステム状態について説明を行う。
システム状態は、図6のように分類される。
図6で示されている状態のうち、「過渡的な状態」は、カーネル実行中(システムコール実行中)の状態に相当する。ユーザから見ると、ユーザのアプリケーションプログラムから発行したそれぞれのシステムコールが不可分に実行されるということが重要なのであり、システムコール実行中の内部状態はユーザからは見えない。カーネル実行中の状態を「過渡的な状態」と考え、その内部をブラックボックス的に扱うのは、こういった理由による。
しかし、次の場合には、過渡的な状態の途中の段階がユーザからも見えることがある。
メモリの獲得・解放を伴うシステムコールで、メモリの獲得・解放を行っている間。(μT-Kernel/SMのシステムメモリ管理機能を呼び出している間)
このような、過渡的な状態にあるタスクに対して、タスクの強制終了(tk_ter_tsk)を行った場合の動作は保証されない。また、タスクの強制待ち(tk_sus_tsk)も過渡的な状態のまま停止することになり、それによりデッドロック等を引き起こす可能性がある。
したがって、tk_ter_tsk, tk_sus_tsk は原則として使用できない。これらを使用するのは、デバッガなどOSの一部といえるような機能に限るべきである。
「非タスク部」ではあるが、特定のタスク(「要求タスク」と呼ぶ)から依頼された処理を実行していると見なされる部分を「準タスク部」と呼ぶ。たとえば、拡張SVCハンドラは「準タスク部」として実行される。「準タスク部」の中では自タスクを特定でき、要求タスクが自タスクとなる。また、タスク部と同じようにタスクの状態遷移を定義することができ、準タスク部から待ち状態に入るシステムコールも発行可能である。これらの点において、準タスク部は、要求タスクから呼び出されたサブルーチンと同じような振る舞いをする。ただし、「準タスク部」はOS拡張部の位置付けであり、プロセッサの動作モードやスタック空間はタスク部と異なる。すなわち、タスク部から準タスク部に入る際には、プロセッサの動作モードやスタック空間の切り替えが起こる。この点は、タスク部の中で関数やサブルーチンを呼び出す場合とは異なっている。
一方、「非タスク部」のうち、タスク部や準タスク部の処理の進行とは全く別の要因で動作を始めるのが「タスク独立部」である。具体的には、外部割込みによって起動される割込みハンドラや、指定時間の経過によって起動されるタイムイベントハンドラ(周期ハンドラおよびアラームハンドラ)などが「タスク独立部」として実行される。外部割込みも、指定時間の経過も、その時点でたまたま実行中だったタスクとは無関係の要因である点に注意されたい。
結局、「非タスク部」は、「過渡的な状態」「準タスク部」「タスク独立部」の3つに分類できる。これ以外の状態が、タスクのプログラムを実行している状態、すなわち「タスク部実行中」の状態である。
タスク独立部(割込みハンドラやタイムイベントハンドラ)の特徴は、タスク独立部に入る直前に実行中だったタスクを特定することが無意味であり、「自タスク」の概念が存在しないことである。したがって、タスク独立部からは、待ち状態に入るシステムコールや、暗黙で自タスクを指定するシステムコールを発行することはできない。また、タスク独立部では現在実行中のタスクが特定できないので、タスクの切り換え(ディスパッチ)は起らない。ディスパッチが必要になっても、それはタスク独立部を抜けるまで遅らされる。これを遅延ディスパッチ(delayed dispatching)の原則と呼ぶ。
もし、タスク独立部である割込みハンドラの中でディスパッチを行うと、割込みハンドラの残りの部分の実行が、そこで起動されるタスクよりも後回しになるため、割込みがネストしたような場合に問題が起こる。この様子を図7に示す。
図7は、タスクAの実行中に割込みXが発生し、その割込みハンドラ中でさらに高優先度の割込みYが発生した状態を示している。この場合、(1)[1]の割込みYからのリターン時に即座にディスパッチを起こしてタスクBを起動すると、割込みXの(2)~(3)の部分の実行がタスクBよりも後回しになり、タスクAが実行状態になった時にはじめて(2)~(3)が実行されることになる。これでは、低優先度の割込みXのハンドラが、高優先度の割込みばかりではなく、それによって起動されたタスクBにもプリエンプトされる危険を持つことになる。したがって、割込みハンドラがタスクに優先して実行されるという保証がなくなり、割込みハンドラが書けなくなってしまう。遅延ディスパッチの原則を設けているのは、こういった理由による。
それに対して、準タスク部の特徴は、準タスク部に入る直前に実行中だったタスク(要求タスク)を特定することが可能であること、タスク部と同じようにタスクの状態が定義されており、準タスク部の中で待ち状態に入ることも可能なことである。したがって、準タスク部の中では、通常のタスク実行中の状態と同じようにディスパッチングが起きる。その結果、OS拡張部などの準タスク部は、非タスク部であるにもかかわらず、常にタスク部に優先して実行されるとは限らない。これは、割込みハンドラがすべてのタスクに優先して実行されるのとは対照的である。
次の二つの例は、タスク独立部と準タスク部の違いを示すものである。
タスクA(優先度8=低)の実行中に割込みがかかり、その割込みハンドラ(タスク独立部である)の中でタスクB(優先度2=高)に対する tk_wup_tsk が実行された。しかし、遅延ディスパッチの原則により、ここではまだディスパッチが起きず、tk_wup_tsk 実行後はまず割込みハンドラの残りの部分が実行される。割込みハンドラの後の tk_ret_int によって、はじめてディスパッチングが起り、タスクBが実行される。
タスクA(優先度8=低)の中で拡張SVCが実行され、その拡張SVCハンドラ(準タスク部とする)の中のタスクB(優先度2=高)に対する tk_wup_tsk が実行された。この場合は遅延ディスパッチの原則が適用されないので、tk_wup_tsk の中でディスパッチングが行われ、タスクAが準タスク部内での実行可能状態に、タスクBが実行状態になる。したがって、拡張SVCハンドラの残りの部分よりもタスクBの方が先に実行される。拡張SVCハンドラの残りの部分は、再びディスパッチングが起ってタスクAが実行状態となった後で実行される。
[1] | (1) でディスパッチを行うと、割込みXのハンドラの残りの部分((2)~(3))の実行が後回しになってしまう。 |