06.3タスクの生成(ATTACH/DETACHとIDENTIFY)-1
MVS(z/OS)はマルチタスクのオペレーティング・システムです。一般のアプリケーションでも複数のタスクに処理を振り分けることで、より実行効率のよいプログラム構造を持つこともできます。同時に複数のイベント(処理要求)が発生したり、非同期に発生するイベントを処理するプログラムではタスクを分割するプログラムデザインは珍しいものではありません。しかしバッチ処理でのマルチタスク構造のプログラムはまれで、通常はオンライン処理プログラムなど、サーバー的な機能を持つプログラムにおいて主に採用されます。
どういう時にマルチタスクを使うか
- ファンクションでタスクを分ける
- トランザクション量でタスクを分ける
実現したい処理がオンラインだから、サーバーのサービスだから、ということで何でもかんでもマルチタスクにするわけではありません。I/O待ち(通信処理のI/Oも含む)の間に他の処理ができるから、ということでタスクを分けることは決して間違いではありませんが、トランザクション量が増えても集中しても、誤りなく正確にマルチタスクのプログラムを制御することは決して簡単ではありません。まずは本当に単一のタスクで制御できないのかを十分に検討して、その上で必要ならマルチタスクでのデザインを考える方がいいでしょう。
大まかなプログラムの設計として、機能か量かでタスクを分ける考え方があります。ファンクションで分ける、というのは、オンライン処理などでは端末(クライアント)の接続/切断、送信、受信、ファイルやデータベースのアクセスや更新など、一連の処理をいくつかのパートに分けて、それぞれの処理を担うプログラムをタスクとして独立させる方法です。このようなデザインは一見わかりやすく各タスクに与えられた機能そのものを実現するプログラムも作りやすいのですが、全体を誤りなく正確に動かすための、タスク間の同期を取ったり、データを受け渡す制御処理が複雑になります。この部分を甘くしたり、抜かしてしまうと、信頼性を大きく落とすことになりかねません。
筆者の経験ではファンクションでタスクを分けるぐらいなら、単一タスクのままWAITやEVENTSでマルチウェイトを行い、イベント(非同期待ち合わせのI/Oなど)の完了した順に必要な処理を行い、待ち合わせの必要な処理は要求だけ投げて、その後は他のイベントの処理へ回る、という構造(イベント駆動型)の方がすっきりすると考えています。むろんこれは私自身の考え方で、実際に機能でタスクを分ける考え方を採用している例も多いです。私もプログラムを覚えたての頃は、ファンクションでタスクを分けることは自然なことと考えていました。随分と前のことですが富士通のOS開発部門の方と話す機会があったときに、「VTAMはシングルタスクでやってるよ」ということを聞いてから、あれだけの大量のデータをあんなに速い速度で処理するシステムがシングルタスクのプログラム構造で作れるのか、と感心してから、いろいろ私なりに考えてみたものです。
しかし”一連の処理は単一のタスクで処理をする”という設計を行っても、1つのタスクでは大量のトランザクションが集中すれば処理が追いつかなくことも考えられます。サービスを提供するタスクが1つなので、サービスを受けるための待ち行列は長くなってしまいます。このような場合は”一連の処理”を完結できる単一のタスクを、トランザクション集中量などに応じて増やすことでマルチタスク化する方法があります。この方法なら1つのトランザクションの処理は完結するまで同じタスクが処理しますが、そのタスクを10個用意すれば、同時に10のトランザクションを並行して処理できます。現在ではプロセッサーに4つ、8つといった複数のCPUが実装されていることが多いので、OSによるCPUディスパッチの切り替えによる多重処理に加えCPU数に応じての同時実行も可能になります。
タスクの生成と実行(ATTACH)および消去(DETACH)
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- メインプログラム : MVI EOTECB,0 CLEAR SYNCHRONOUS ECB ATTACH EP=SUBTASK1, EXECUTE MOD=SUBTASK1 UNDER + PARAM=PARM1, NEW TASK WITH PARM=PARM1+ ETXR=EOTEXIT サブタスクへ渡すパラメーターはPARAMキーワードで指定する。 CALLマクロと同じで、アドレスパラメーターリストが生成される。 特定の値をパラメーターとして渡すような場合は、GR1に直接その 値を設定して、PARAMパラメーターを省略すればよい。 ST R1,TCBADDR SAVE ATTACHED TASK TCB ADDRESS ATTACH後、GR1には生成されたサブタスクのTCBアドレスが返る。 WAIT ECB=EOTECB WAIT UNTIL ATTACHED TASK DONE サブタスクの完了を待ち合わせるためのWAITマクロの発行。 ECBにPOSTするのは、ETXRパラメーターで指定したサブタスク 終了出口ルーチンになる。 もちろん非同期処理を行うためにタスクを分けているのだから、 WAITする前に他の処理を行ってかまわない。そのようにして 処理を並行させなければタスクを分ける意味がない。 : : EOTECB DC F'0' ATTACHED TASK COMPLETION ECB PARM1 DC CL50'THIS IS SUBTASK PARAMETER STRING' : : サブタスク終了出口ルーチン EOTEXIT DS 0H USING *,RF DEFINE TEMP BASE STM RE,RC,12(RD) SAVE CALLER REGISTERS L RC,=A(MAINENTR) ESTABLISH OUR BASE ADDRESS DROP RF FORGET TEMP BASE SPACE , ST R1,TCBADDR SAVE TCB ADDRESS FOR DETACH L R0,TCBCMP-TCB(,R1) LOAD TCB COMPLETION CODE POST EOTECB,(0) POST IT TO MAIN TASK 終了したタスクの完了コードはTCBCMPフィールドを調べればわかる。 このサンプルでは完了コードをPOSTマクロのPOSTコードとして 使用している。 DETACH TCBADDR DETACH ENDED TASK 終了したサブタスクのTCBをパージするためにDETACHマクロを 発行する。DETACHではTCBアドレスそのものをパラメーターとして 指定できない。TCBアドレスが格納されたポインターフィールドの アドレスを渡すことになる。 このサンプルではサブタスク終了出口ルーチンでDETACHを 発行しているが、メインルーチンで発行してもかまわない。 DETACHを発行するまではTCBは存在しているので、メインルーチンでも 参照可能である。 SPACE , LM RE,RC,12(RD) LOAD CALLER REGISTERS BR RE RETURN TO CALLER(DISPATCHER) SPACE , TCBADDR DC A(0) TCB ADDRESS FIELD : : 制御ブロック(TCB)マッピング IKJTCB , TCB
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- ATTACHされるサブタスクプログラム SUBTASK1 CSECT , DEFINE CODE SECTION USING *,RC DEFINE BASE REGISTER STM 14,12,12(13) SAVE CALLER REGISTERS LA 12,0(,15) GR12 -> OUR 1ST BASE ADDRESS LR 15,13 SAVE CALLER SAVEAREA CNOP 0,4 INSURE FULL WORD BOUNDARY BAL 13,*+4+72 AROUND OUR SAVEAREA DC 18F'-1' OUR GPR SAVEAREA ST 15,4(,13) SAVE CALLER SAVEAREA POINTER ST 13,8(,15) SET BACK CHAIN FOR LINK TRACE : : 必要な処理を実装し、ごく普通に作ればよい。 : GR1にはATTACHマクロのPARAMキーワードで指定された : パラメーターのアドレスが入る。 : パラメーターはアドレスリストの形式になっている。 : このサンプルの場合、以下のように渡される。 : GR1 → +---------------------------+ : +0 I メインプログラムの I : I PARM1フィールドのアドレス I : +---------------------------+ : : プログラムが最後の命令を実行して(BR 14等)OSに : 制御を戻すと、メインプログラム側のタスク終了出口ルーチン : が実行される。 :
マルチタスク・プログラミングのサンプルというよりは、新たなタスクを生成し、実行が終わったタスクを消去するAPIの使用サンプルです。
タスクを生成して実行するにはATTACHマクロを使います。ATTACHも基本的にはLINKマクロによるプログラムの呼び出しと同様の手順です。ただし実行単位であるタスクが変わるので、呼び出したプログラムが終了する前に呼び出し元であるATTACH発行元に制御が戻ってきます。ATTACH後は呼び出したプログラムと呼び出されたプログラムは異なるタスクによって同時に実行されることになる点がLINKと大きく異なります。
キーワードEP(あるいはEPLOC)は呼び出すプログラムのロードモジュール・メンバー名、PARAMは呼び出すプログラムへ渡すパラメーターの指定です。さらに呼び出したプログラムが終了した際の通知方法を指定します。通知方法には非同期出口ルーチンの起動とECBへのPOSTの2種類があります。どちらを選択するかはプログラムデザインで決まります。このサンプルでは非同期出口ルーチンの起動を指定しています。実行したプログラムが終了すると、OSはETXRキーワードで指定したプログラムルーチンをメインプログラムとは非同期に起動します。ATTACHされたタスクが終了したタイミングでOSはETXRキーワードで指定されたプログラムを呼び出して実行します。ATTACHを発行したプログラムの実行とは無関係に、メインプログラムの実行を一時停止してメインプログラムのタスクに割り込んで実行するので「非同期出口ルーチン」と呼ばれます。その他にも細かなパラメーターがありますが一般のアプリケーションプログラムであれば省略値でも十分です。
ATTACHから制御が戻るとGR1には生成されたサブタスクのTCBアドレス(OSがタスクを制御するためのコントロールブロック)が返ります。ATTACHマクロを使用しないで直接ATTACH SVCをする際に誤ったパラメーター・リストを指定するなどを除けば、ATTACH処理自体が失敗することはほとんどありません。EPまたはEPLOCで指定したプログラム名が誤っているなどの場合は、ATTACHが失敗するのではなく、生成されたサブタスクがS806等でABENDします。サブタスク起動時のABENDをトラップしてリカバリー処理を行う場合は、ESTAIキーワードを使用してESTAE同様のエラーリカバリールーチンを用意することもできます。しかしS806ABENDをトラップするだけならわざわざESTAIを使わなくとも、ATTACH前にLOADやCSVQUERYマクロを利用することでモジュールが存在するかどうかのエラーは事前にチェックできます。サブタスク起動後であればサブタスク側でESTAEマクロを使ってリカバリー環境を構築できます。
ATTACHされたタスクが終了したら、DETACHマクロによってタスクを消去します。タスクの消去とはTCBの消去でもあります。タスクの終了が非同期出口ルーチンまたはECBにPOSTされてもTCBはまだ残っており、メインプログラム側ではそのTCBを参照してタスクの終了状態を調べることができます。必要な情報を収集したり、リソースをクリーンアップした後、DETACHマクロによって終了タスクのTCBを消去することを行います。
ATTACHされたタスク(子タスク)のプログラムとATTACHしたタスク(親タスク)のプログラムは非同期に動きます。異なるタスクのプログラム間でデータの受け渡したり、処理の整合性を正しく取るためには、タイミング合わせや排他制御などのコントロールが必要になります。これらはWAIT/POST、ENQ/DEQ(あるいはCS/CDSなどの逐次化命令)などを利用することで可能です。