- 「メインフレーム・コンピューター」で遊ぼう - http://www.arteceed.net -

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などの逐次化命令)などを利用することで可能です。