スパイス  組み込み制御装置の受注製作

Z80にマルチタスク機能を
平成26年 5月 1日

  もう一つのマルチタスク実現方法
 コルーチンを使うことで大半の処理を並列に動作させることが出来ます。z80 CPUの処理能力を考えると、これで十分ではないかという気もします(逆にいうと最低限のRTOSレベルの実装はz80には重過ぎると感じます)。例えば2000行程度のプログラムのオブジェクトサイズは概ね15〜20kB程度でしょう。とすれば10,000行のプログラムはz80の対象範囲外です。CPU自体の性能も今時のCPUとの比較では見劣りますので、あまり大きなプログラムは考えない方がいい。
 とはいえ、コルーチンによるプログラムもそれなりに癖のある書き方を要求されます。特にローカル変数の管理は面倒の一言です。時々static宣言を書き忘れて、理解不能の動作に出くわします。
 そこでもう一つのマルチタスク機能として最低限であるタスクの切り替え機能のみを実装してみます。RTOSのようなタスクの優先順位付けやタスク間での同期・通信などの機能を実装しなければ、それほど複雑にもならないし何よりもCPUへの負荷が少なくて済みます。
 このタスク切り替えをタイマ割り込み処理内で行えば、タイムスライスによるマルチタスクが実現します。タイマ割り込みを使用しないで、特定の処理終了毎(CO_yield()の位置)に記述すれば、コルーチンと同じ動作になります。

 タスクコンテキストの保存
 まず、タスク切り替えに必要となるタスクコンテキストの説明が必要です。これはCPU内部(RAM内の保存データではない)に保存されている情報のことです。具体的にはz80ならユーザーに解放されているレジスタ値の全てと割り込みフラグ(IFF1, IFF2)が相当します。これらの情報をある時点で一斉に保存し、その後他のタスク処理を実行した後で、再び元の値に戻すことが出来るなら、CPUは見かけ上、割り込み動作の前後のように何事も無かったかのように動作を継続できます。その意味ではマルチタスクは割り込み動作の機能拡張版と見なすことも出来ます。

 そこで割り込み処理の記述を雛形としてタスク切り替え処理を記述してみます。割り込み処理での雛形は下記になります。CPU内部のレジスタをSPに退避させ、割り込み処理を実行後これを回復して、更に割り込みフラグの回復を行っています。
    push AF
    push BC
    push DE
    push HL
    push IX
    push IY
    (割り込み処理)
    poop IY
    poop IX
    poop HL
    poop DE
    poop BC
    poop AF
    ei
    reti
 ei命令で割り込みフラグが回復できるのは、割り込みが受け付けられた時点での割り込みフラグは必ず'1'(ei)だからです。割り込みフラグが'0'の時に割り込みを受け付けたとすると、それはCPUのバグです(実はこのことは他の面で微妙な側面を持っています)。これでSPを除く全てのタスクコンテキストが保存・復帰されていることが分かります。タスク切り替えでは割り込みフラグの値は決め打ちできないのですが、タスク切り替え中に他の割り込みが入ってくるのは明らかに不都合です。このためタスク切り替え処理の開始時点で割り込みを禁止します。禁止以前の割り込みフラグの値をセーブするのは少し面倒です。というのはこのフラグを直接読み出す方法が無いためで出来なくは無いのですが、ここでは(プログラムを書いている人にはその時点での割り込みフラグの値は分かっている筈なので)一旦ユーザー管理ということにしておきます。

 残るはSP値の管理です。
    ここで少し脱線します
 ここでの疑問は割り込み処理では管理不要のSP値を何故タスク切り替えでは管理しないといけないのかということになります。簡単に言うと、タスク呼び出しの順番を自由にしたいためです。割り込み処理では割り込みが多重にネストした状態でも、割り込を起こした関数の処理が終わった後は必ず割り込みを受け付けた関数に戻るというルールになっています。
    関数A---割り込みB---多重割り込みC
の順に割り込みが入った場合、処理が終了する順番は必ず
    多重割り込みC, 割り込みB, 関数A
の順になります。
これはSPがLIFO(LastIn FisrstOut)になっており、この順番での呼び出し・戻りが保障されている限り不都合が起きないためです。
 一般的なマルチタスクでは、複数のタスクを優先順位や一定の順番に従って切り替えます。この順番は先のLIFOとは異なります。その時点でのSP値、もっと厳密にはSPが指し示すアドレス上のRAMには、その処理に関連した多くの情報(ローカル変数や関数への引数、関数呼び出しの履歴など)が詰まっています。この情報に何の制約もつけずに、なおかつどのタイミングでタスクを切り替えされても正しくアクセスできることを保証するためにはSP領域を各タスク毎に個別に持つ必要があります。違う言い方をするとSPだけは、その値のみが意味を持つのではなく、そのSPが指し示すRAM上のデータを含めて初めて有意義な情報となります。このため、個々のタスク毎にメモリ領域を確保する必要があります。


目次 前へ 次へ