ObjectPascalを見た時は、少しがっかりしました。C++言語そのままPascalを拡張していて、これでは節操のない後追い。 もっとポリシーを持って、まったく別のアプローチをした方が良いと思いました。本当に、これならEiffelの拡張でもした方がいいと思ったぐらいでした。
純粋Pascalで考えてみたい気持ちもありますが、環境はObjectPascalを個人環境で持っているので、後者のほうを使用します。また、FreePascalを使うことも検討はしています。
(純粋Pascal TurboPascal3.0同等の言語設計になっているもの)
Pascal言語は、別途考えることがあるので、別の機会にする。
Pascal言語はアルゴルズムで振り返ってみようと思う。アルゴリズムはPascal言語で勉強した。
以前は、Pascalで記述されている技術書があった。それを、いま、FreePascalなどでコーディングしてみようかと考えた。
そして、アルゴリズムそのものについても考えてみたい。
Modula-2で考えていたことを少しだけ書いてみる。
Modula-2のコルーチン
CMagazine 1991/6月号より記事の一部を転用する。このプログラムをコーディングして実行してみたかったが、それをすることはなかった。
フリーのModula-2があることも知っているけど、いつも後回しになる。
そのように懐かしく感じる。
PROCEDURE Scheduler;
VAR
TempId : ElementType;
SchedulerCalls,
Current,
Previous : INTEGER;
BEGIN
SchedulerCalls := 0;
Current := 1;
Previous := 1;
LOOP
INC(SchedulerCalls);
(* デッドロックの検出 *)
IF AllBlocked() THEN
Panic(5);
END;
IF (SchedulerCalls > MAXRUNTIME ) THEN
TRANSFER(SchedProc, Mainproc) (* これで終わり */
ELSE
IF NOT RemoveCSQueue(ReadyQueue, TempId)
THEN Panic(0);
END;
Previous := Current;
Current := TempId.ProcessId;
ProcessTable[Current].State := RUNNING;
(* 後で表示するためにTRANSFERの前にアクティビティをセーブする *)
RecordScheduling(Current, Mutex);
(* このプロセスに制御が渡されるようにする *)
IOTRANSFER(SchedProc,ProcessTable[Current].Context,08H);
(* 次の割り込みでここに戻ってくる *)
(* コルーチンが終わったら再スケジューリングしない *)
IF ProcessTable[Current].State <> DONE
TEHN
IF ProcessTable[Current].MustBeBlocked
THEN
IF InsertCSQueue(BlockedQueue,ProcessTable[Current].PID)
THEN
ProcessTable[Current].State := BLOCKED;
ELSE
Panic(2); (* これは絶対に起きてはいけない *)
END;
ELSE (* ブロックされてはならない *)
IF InsertCSQueue(ReadyQueue,ProcessTable[Current].PID)
THEN
ProcessTable[Current].State := READY;
ELSE
Panic(3); (* これは絶対に起きてはいけない *)
END;
END;
END; (* DONEではない *)
END;
(* TRANSFERの間に生じた変化を記録する *)
RecordScheduling(Current, Mutex);
END; (* < MAXRUNTIME *)
END; (* loop *)
END Scheduler;
どのような理由だったか、とにかくマルチタスク環境に興味があった。OSとしてでなく、コンピュータ言語としてマルチタスクアプリを実行してみたかった。
CPUの処理能力が低かったので、いずれマルチCPUになる。
マルチタスクをしないといけない。自分なりの未来予測・・それが理由だった。しかし、個人の開発環境ではマルチCPUとマルチタスクができるものはなく、MS-DOSを使い続けていて、それ以外のOSに接することはなかった。
大型コンピュータのOS、あるいはUNIX-WSには遠く及ばない、小さいコンピュータシステムのOSだった。だからMS-DOS 5.0がマルチタスクとは言えないまでもタスク切り替えができると、雑誌の記事で目にしたときは、何かやってみようと思った。ただ、ほぼ同時期にWindows3.1が発売されていたのでノンプリエンティブ(non-pre-emption)であっても、こちらの方が擬似的にマルチタスクができていた。16bitのWinAPIにもミューテックス(Mutex)があった。MS-DOS 5.0では、マルチタスクのシステムプログラミングをすることはなかった。
Windows3.1になってもマルチタスクでなかったため、とても身近な存在でなかったが、Modula-2を書籍の内容で習得することにした。MS-DOSでマルチタスクを実行するコンピュータ言語はこれしかなかった。でも、コンパイラ環境を手に入れる術はなく机上での知識にしかならなかった。
BEGIN
(* 相互排他的な計数セマフォを初期化する *)
Mutex := 1;
SysErrorNo := 0;
Scheduling.Count := 0; (* 記録される事象の数 *)
SharedVariable := 0.0;
(* システムキューを初期化 *)
InitCSQueue(ReadyQueue);
InitCSQueue(BlockedQueue);
(* すべてのコルーチンを初期化 *)
StartUp;
(* そしてスケジューラへ *)
NEWPROCESS(Scheduler,ADR(SchedSpace),SIZE(SchedSpace),Schedproc);
(* 開始・・・ *)
TRANSFER(MainProc,SchedProc);
(* 実行後にここに戻る *)
DisplayEvents; (* 結果を表示 *)
END Concurrent.
CMagazine記事から転載
Modula-2が採用している方法は、計数セマフォ(counting semaphore)という概念に基づいています。計数セマフォは、エドガー・ダイクストラの定義では、一つの特定の事象を持つプロセスの数を記録している一つの変数と、二つの相補的な操作V(s)とP(s)です。
List3に、そのルーチンの擬似コードを示します。この二つの操作は、いずれも不可分の操作として実装されることが重要です。これらのいずれかを開始したプロセスは先制制御されてはなりません。この二つの操作でディスエーブル(disable)にしているのはそのためです。
生産者-消費者の例への解としてプロセスの同期は、セマフォをつくり、それを 1 に初期化しそれぞれの危険領域を P と V のコールによって隔離することで実現する。
このModula-2のサンプルリストは、MS-DOSの割り込みを使ってコンテキスト(Context)の切り替えを実行している。MS-DOSへの割り込みは、IOTRANSFER( ・・,・・,08C)がライブラリで用意されている。
List3
P(s:Semaphore);
BEGIN
s := s - 1;
IF s < 0
Sleep();
END;
V(s:Semaphore);
BEGIN
s := s + 1;
IF s <= 0
WakeSleeper();
END;
MODULE CoroutineRei;
FROM SYSTEM IMPORT
WORD,ADR,SIZE,PROCESS,NEWPROCESS,TRANSFER,DOSCALL;
FROM
Terminal IMPORT WriteString,WriteLn;
CONST gyooatarikosuu = 15;
VAR i,j : CARDINAL;
Con,Prn,Main : PROCESS;
A,B : ARRAY[1..200] OF WORD;
PROCEDURE WriteLn;
BEGIN
LOOP
WriteString('CON:');
INC(i);
IF i > gyooatarikosuu THEN
WriteLn; i := 0;
END;
TRANSFER(Con, Prn)
END
END WriteCon;
PROCEDURE WritePrn;
BEGIN
LOOP
DOSCALL(5,'P'); DOSCALL(5,'R');
DOSCALL(5,'N'); DOSCALL(5,':');
INC(j);
IF j > gyooatarikosuu THEN
DOSCALL(5,13); DOSCALL(5,10); j := 0;
END;
TRANSFER(Prn,Con)
END
END WritePrn;
BEGIN
i := 0; j := 0;
NEWPROCESS(WriteCon, ADR(A), SIZE(A), Con);
NEWPROCESS(WritePrn, ADR(B), SIZE(B), Prn);
TRANSFER(Main,Con)
END CoroutineRei.
「Modula-2」文法入門
CQ出版社
中村和郎著
(抜粋) 12 コルーチン
手続きyの実体(スタックポインタやプログラム・カウンタ、スタックの番地などがしまわれるレコード)が生成される。そして、yの実行が終わると、制御は呼び出したxに戻り、yの実体は消滅する。その後、再びyが呼び出されても、前のものとはまったく別物の実体が生成される。
コルーチンの実体の型名は、Modula-2において標準的に決められており、PROCESSという型名になる。Con, Prn, Main が PROCESS型の変数であり、コルーチンの実体になる。
NEWPROCESSという手続きは、コルーチンの実体を生成、初期化を行うためのもので、
定義
PROCEDURE NEWPROCESS(ProcessBody : PROC;
WorkspaceAddr : ADDRESS;
WorkspaceSize : CARDINAL;
VAR Process : PROCESS );
第一引数は、第四引数で指定したコルーチンが実行すべき手続きを指定する。この手続きはPROC型、すなわち、仮引き数をもたない手続きでなければならない。第二引数、第三引数では、コルーチンが使用する作業領域の先頭アドレスと大きさを指定する。コルーチンConが実行する手続きはWriteConであり、コルーチンPrnが実行する手続きは、WritePrnになる。
TRNSFERという手続きは、制御を移すためのもので、
定義
PROCEDURE TRANSFER(VAR FromProcess : PROCESS;
VAR ToProcess : PROCESS );
現在実行しているコルーチンの実体を第一引数にしまい、第二引数で指定したコルーチンを制御に移す。
制御の推移は、Main → Con → Prn → Con → Prn → ・・・ となる。
割り込み処理用のIOTRANSFER
定義
PROCEDURE IOTRANSFER(VAR Old, NEW : PROCESS;
Vec : CARDINAL );
現在実行しているコルーチンの本体を第一引数の中ににしまい制御を第二引数のコルーチンに移す。第三引数は割り込みベクトル番号で、この割り込みが発生した場合は、自動的にTRANSFER(New, Old);が実行されたかのように制御が戻ってくる。これを使えば、割り込み処理を Modula-2 のみを使っても記述できる。