MS-DOSはシングルタスクなOSで、DOSでマルチタスクを求めるのであればDR-DOSなどの選択があります。わざわざMS-DOSを選択する必要性は無いわけです。
以前にMS-DOSで動作する業務用アプリケーションをC言語で作成する機会がありました。その際、ちょっとした並列処理をさせるのにも、面倒な記述をして煩雑なソースを書かなければなりませんでした。後にWIN32でのマルチスレッドというものに触れ、MS-DOSでも関数単位で記述して並列実行できないものかと思ったものです。
ある時、ちょっとしたひらめきから擬似マルチスレッドを実現するモジュールを試作してみました。そのまま眠らせておくのももったいないので、ココに載せておきます。試作レベルで中途半端ですが。
目次
きっかけ
通常のプログラムの実行はこのような流れになります。
Task2()実行中にMain()へ戻る方法がC言語には用意されています。setjmp/longjmp関数です(使い方などはここでは割愛)。要するに関数の呼び出し順序を無視してジャンプすることができます。
この途中で強制的に中断してジャンプする、という機能はマルチタスクOSが行っているタスクの切替処理と似ています。上手く利用すれば擬似マルチタスクが実現できるのでは?と思ったのがきっかけです。
構想
マルチスレッドについて考えます。図のようにMain()からTask1()スレッドを生成すると、交互に処理が行われ、並列に処理が行われているように見えます。
(1)は関数呼び出し、(2)はsetjmp/longjmpで実現できますが、(3)を実現することは出来ません。Task1()を中断したときの情報を保存していないからです。
Task1()の実行時の情報はスタックに積まれています。setjmp/longjmpではその情報を破棄することで元の場所へ戻ることを実現しているので、setjmp/longjmpは利用できません。
よって並列実行を実現するためには、Task1()のためのスタックエリアと、setjmp/longjmpにスタックエリアの切替を行う機能を持った関数が必要になります。
詳細仕様
setjmpおよびlongjmpは、スタックポインタを直接扱っているためC言語の機能では作成できません。インラインアセンブラを使用します。CPU依存、コンパイラ依存、メモリモデル依存は避けられません。今回はTC4.0を使いました。
生成するタスク用のスタックエリアは、mallocで取得することにします。
タスク切替はタイマ割込みで自動的に行うことも考えました。しかし、DOS用のコンパイラということもありマルチスレッド用ライブラリなども存在しません。
そこでタイマ割込みでは時間の計測のみを行うことにし、タスク切替可能なタイミングがユーザが明示する方式にしました。ユーザが明示した箇所では、タスクの実行時間を見て一定時間経過しているならタスク切替を行います。簡易タイムスライス方式です。
ダウンロード
という訳で作成したのがコレです。
プログラム説明
rtc.c/rtc.h は、タイマ割込み部分です。RTCタイマを利用してます。PC-98系に移植する場合はココを変えます。ってそんな人いないか。
msetjmp.c/msetjmp.h は、今回のメイン部分です。タスクの生成・切替などを行ってます。プライオリティカウンタとは、割当てられるCPU実行時間です。
test.c が、マルチスレッド機能を利用したサンプルです。
void sample(coid __far *var)
という関数を複数起動し、メイン部分と合わせて並列実行させています。"_sw_"というマクロがタスク切替タイミングを示す部分です。共有リソースをアクセス中などに勝手にタスク切替が発生しないようになってます。
実行すると、"A"と"B"と"C"と"-"が、混ざった状態で表示されます。プライオリティカウンタの値によって一度に表示される文字数が異なるのが分かると思います。(と言っても実行できる環境を持ってる人ももう少ないでしょうね)
注意など
このプログラムは十分なテストを行っていません。原理試作して放置してあったものを、もったいないので公開しただけです。このモジュールを利用して何か作られても構いませんが、自己責任でお願いします。
ちなみにサンプル作成中、デバッガでまともにトレース出来ませんでした。スタックポインタを書き換えまくりのせいでしょう。
DOS窓で動作するかも怪しいです。