06.1プログラムのローディングと実行(LOAD,LINKとXCTL)
プログラムのローディングと実行は、一般のプログラムでも身近な機能です。規模の大きなプログラムでは複数のモジュールに分割して開発・管理・保守が行われます。複数のモジュールで構成されるプログラムの処理に関するMVSの機能を解説します。
MVSにおけるプログラムの構造
単純構造プログラム
プログラムのCSECTが1つであっても複数であっても、すべてがバインダー(リンケージエディター)で結合されていて、1つのロードモジュールを構成する構造です。ソースモジュールは分割され個々にコンパイル(アセンブル)されたとしても、バインダーが1つにまとめ上げます。サブルーチンの呼び出しなど、外部アドレスの参照はバインダーが解決します。単純構造プログラムではロードモジュール内の各々のモジュール(サブルーチン)はBASR、BASSMなどCPUの分岐命令を使い直接呼び出すことができます。そのためモジュール間のリンケージ動作は最も効率が高くなります。
+---------------------------+ +00000 | MAINPROG | | ======== | | Main Module | | | | SUB1 DC V(SUBRTN1)-------+ | SUB2 DC V(SUBRTN2)----+ I | | I I +---------------------------+ I I +008A0 | SUBRTN1 <-----------------|--I--+ | ======= | I | Sub-Module1 | I | | I +---------------------------+ I +00AC8 | SUBRTN2 <-----------------|--+ | ======= | | Sub-Module2 | | | +---------------------------+
動的構造プログラム
複数の単純構造プログラムの組み合わせによって構成される構造です。1つ1つのモジュールはそれぞれが独立したロードモジュールとなります。もちろん複数CSECTで構成されるロードモジュールが含まれていてもかまいません。サブルーチンの呼び出し等に関する外部参照は解決されません。外部参照を解決するにはMVSのAPIを使ってプログラム自ら行うことになります。
しかし規模が大きなプログラムでは、モジュールが占める記憶域量を減らせたり(必要なモジュールだけが読み込まれる)、モジュール修正が行われたりしても、修正を施すモジュールだけの変更で済むため、モジュールの独立性をより高めることができます。
+---------------------------+ +06000 | MAINPROG | | ======== | | Main Module | | | | LOAD EP=SUBRTN1 <----------------------+ | LR RF,R0 | I | BASR RE,RF ----------------------+ I | | I I | LINK EP=SUBRTN2 --+ | I I | I | I I | I | I +---------------------------+ +---------------------I-----+ +--->| SUBRTN1 | I +08DD8 | ======= | I | Sub-Module1 | I | | I +---------------------------+ I I I +---------------------------+ +---->| SUBRTN2 | +08310 | ======= | | Sub-Module2 | | | +---------------------------+
動的リンク構造
富士通独自のプログラム構造です。プログラムは単純構造として作成されますが、リンクエディット時に未解決の外部参照は「DALTAB」と呼ばれるテーブルをポイントするように設定されます。プログラムがそのモジュールを呼び出すとDALTAB内のコードが実行されて、モジュールをメモリーにローディングして、その入口点アドレスをDALTABにセットします。2回目以降は設定されたモジュールの入口点に直接分岐することでモジュールを呼び出すことを行います。リンケージエディターが行っている外部参照の解決を実行時に動的に行うため、動的リンク構造と呼ばれます。単純構造のプログラムを動的リンク構造にするかどうかはリンケージエディターのパラメーター「DYNAMIC」で決まります。
富士通は動的リンク構造をOSでアシストしていますが、このようなリンケージ方法はさほどむずかしいものではなく動的構造のプログラムでも一般的に行われる手法です。呼び出すモジュールの入口点アドレスが0であれば、バインダーによる外部参照が解決されていないと見なし、プログラムをライブラリーからローディングして、その入口点アドレスをセットします。2回目以降は入口点アドレスが設定されているのでそのままBASR,BASSM命令などで呼び出します。一般のアプリケーション・プログラム自ら動的リンクによってサブモジュールを管理することはよく行われます。
+---------------------------+ +00000 | MAINPROG | | ======== | | Main Module | | | | SUB1 DC V(SUBRTN1) | | I | | I | | I | +------------------------+ | I | | I +---------------------------+ +-->| DALTAB A(SUBRTN1) <---------+ 1回目だけローディングされる | I | I 2回目はアドレスがわかっているので直接分岐する | I | I +--------------I------------+ I I I I I I I I +---------------------------+ +---------> | SUBRTN1 | +08DD8 | ======= | | Sub-Module1 | | | +---------------------------+
再入可能(リエントラント)プログラム
単純構造や動的構造に関係なくプログラム・デザインに関するもので、複数のタスクで同時に実行できるプログラムのことです。リエントラント(reentrant)構造プログラムとも呼ばれます。プログラムが処理に使うデータの内、更新を伴うものをプログラム内部に置いていない設計になっているものです。端的な例がレジスター保管域です。レジスター保管域をプログラム内に置いたプログラムが同時に複数のタスクで実行されると、タスク1がそのプログラムでサブルーチンAを呼び出し、そのサブルーチンが実行中に、タスク2でも同じプログラムが実行されサブルーチンAが呼び出されると、最初に呼び出されたタスク1のレジスター保管域は後から呼び出されたタスク2のサブルーチンAによって上書きされてしまいます。そうなるとタスク1で最初に呼び出されたサブルーチンAは元の呼び出し元に正しく戻れなくなってしまいます。
これは同じ領域にローディングされているプログラムを複数のタスクで共有使用する場合の問題です。同じプログラムのコピーをタスク毎に使えばこのような問題は解決しますが、メモリーの利用効率やパフォーマンスの観点で劣ってしまいます。特にあらゆるプログラムから頻繁に利用されるシステムの共通機能ルーチンのようなものでは顕著です。そこでこのような問題を解決するために、レジスター保管域など書き込みされるデータ領域をプログラムの外に置き、タスク毎にGETMAINして使用すればデータ領域は他のタスクによって壊されることがなくなります。プログラムの命令コード部分と書き込みされるデータ領域を分けた設計になっているプログラム・モジュールを再入可能プログラム、再入可能モジュールと呼びます。
なお再入可能プログラムのように複数のタスクで同時に使用することはできないものの、データ領域をプログラムの先頭で初期化するような処理を持ち、順番に1タスクずつなら同じ領域のプログラムを実行することができるものは逐次再使用可能(reusable)プログラムと呼びます。
非再入可能プログラム
同じGPRSAVE領域への書き込み(STM命令)を複数のタスクで行うため、競合のような同時書き込みでなくても、TASK1で保管→復元、TASK2で保管→復元のシーケンスがくずれ、TASK1で保管→TASK2で保管→TASK1で復元→TASK2で復元や、TASK1で保管→TASK2で保管→TASK2で復元→TASK1で復元となってしまうと、いずれもTASK1はTASK2のレジスター内容で復元されてしまいロジックに矛盾が生じる。
TASK-1 -----+ +----- TASK-2 I I I I I I +-------------------------+ +8280 | SUBRTN-A I I | | I I | | STM 14,12,12(13) | | I I | | I I | +-------------I----I------+ +8300 | MAIN I I | | I I | | V V | | GPRSAVE DC 18F'0' | | | +-------------------------+
再入可能プログラム
GPRSAVE領域は呼び出しの都度プログラムの外に確保されるため、複数のタスクで同時に呼び出されてもレジスター保管域は競合しない。TASK-1とTASK-2がどのような順序でプログラムを使用しても互いのデータ領域が侵されることはない。
TASK-1 -----+ TASK-2 -----+ I I I I I I I +-------------------------+ I I +8200 | MAIN | I V | | V +-------------+ | | +-------------+ +21000 | GPRSAVE |<--| GETMAIN LV=72... |-->| GPRSAVE | +21080 +------*------+ | | +------*------+ I +-------------------------+ I I +8380 | SUBRTN-A | I I | | I +------------------ STM 14,12,12(13)------------+ | | | | +-------------------------+
逐次再使用可能プログラム
プログラム内に書き込みデータ領域を持っていても、あるタスクがそのプログラムを実行中は、他のタスクでは呼び出すことができない。APIを使用してスーパーバイザー経由で呼び出せば、他のタスクでプログラムを使用中の時、呼び出し元へ戻るまでスーパーバイザー内で待ち状態になる。
TASK-1 -----+ +----- TASK-2 I I I I I (待ち状態) +-------------------------+ +8280 | SUBRTN-A I | | I | | STM 14,12,12(13) | | I | | I | +-------------I-----------+ +8300 | MAIN I | | I | | V | | GPRSAVE DC 18F'0' | | | +-------------------------+
再入可能プログラムはRENT、逐次再使用可能プログラムはREUSのオプションを、ロードモジュール作成時のバインダー(リンケージエディター)のパラメーターに指定します。このオプションでロードモジュールの属性を明示的に示さない限り、MVSはモジュールをRENT,REUSの取り扱いとしません。しかしバインダーもOSも本当にモジュールがRENT,REUSの動作に見合うロジックで書かれているかのチェックまではできません。
単純構造プログラムにおけるサブルーチンの呼び出し
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- LA R1,PLIST LOAD PLIST ADDRESS IN GR1 L RF,ASUB1 LOAD ENTRY ADDRESS IN GR15 BASR RE,RF CALL SUBRTN1 LTR RF,RF SUCCESSFUL ? BNZ DOERROR NO, DO ERROR PROCESSING : : ASUB1 DC V(SUBRTN1) SUB-ROUTINE1 ENTRY ADDRESS PLIST DS 0F ADDRESS PARAMETER LIST ADATA1 DC A(DATA1) POINTER TO DATA1 ADATA2 DC A(DATA2) POINTER TO DATA2 ADATA3 DC A(DATA3+X'80000000') POINTER TO DATA3 WITH + LAST WORD INDICATOR DATA1 DC F'1' DATA2 DC XL8'0011223344556677' DATA3 DC C'TOKYO-OSAKA-FUKUOKA-NAHA' CALL SUBRTN1, CALL SUBRTN1 + (DATA1,DATA2,DATA3),VL LTR RF,RF SUCCESSFUL ? BNZ DOERROR NO, DO ERROR PROCESSING : : DATA1 DC F'1' DATA2 DC XL8'0011223344556677' DATA3 DC C'TOKYO-OSAKA-FUKUOKA-NAHA' 呼び出される側のサブルーチン ============================ SUBRTN1 CSECT , USING *,RC DEFINE OUR BASE REGISTER SAVE (14,12),,* SAVE CALLER REGISTERS LR RC,RF ESTABLISH OUR BASE ADDRESS : : SLR RF,RF SET RETURN CODE = 0 RETURN (14,12),RC=(15) RETURN TO CALLER
単純構造プログラムではサブルーチン(他のモジュール)はCPU命令で直接呼び出すことができます。リンケージ規約に則ればGR15に呼び出すモジュールの入口点アドレス、GR14に戻りアドレスを格納して、入口点アドレスへ分岐します。BASR(BALR)命令をサンプルのようにコーディングすればいいのです。サンプルではGR1にパラメーター・アドレスをセットしています。パラメーターはアドレスパラメーター・リスト形式で渡していますが、呼び出す側と呼び出される側で合意すればどんな形式で渡してもかまいません。アドレスパラメーター・リスト方式はパラメーター数が少ないときは面倒ですが汎用的な方法なので、他の言語で作成されたプログラムとリンケージを取るような場合にも適しています。
2番目のサンプルは最初のサンプルとまったく同じことを、CALLマクロ命令を使って行うものです。呼び出すモジュールの入口点アドレスフィールドやパラメーターリストなどはCALLマクロの中で自動生成されます。CALLマクロはスーパーバイザーのサービスを呼び出すAPIではなく、機能に対応するCPU命令列をプログラマーに代わり生成するものです。COBOL言語を主に使うプログラマーには比較的理解しやすい方法です。
最初に呼び出すプログラムの入口点名(CSECT名)の名前または入口点アドレスを格納したレジスター番号(15)を指定し、続いてプログラムへ渡すパラメーターが格納されている領域の名前を()括弧でくくって指定します。VLはパラメーター・リスト上の最後のパラメーターを示すエントリーの先頭ビットを1にするパラメーターです。最終パラメーターのエントリーの先頭ビットを1にする必要があるかどうかは、プログラムの呼び出しインターフェースによって決まります。
3番目は呼び出されるプログラムで利用できる関連マクロを紹介します。SAVEマクロはプログラムの先頭で呼び出し元のレジスターを保管する命令を展開します。ただし自プログラムのレジスター保管域の定義や呼び出し元プログラムのレジスター保管域とのチェインを行う命令は生成されません。サンプルでは省略していますが、必要な処理は自ら行わねばなりません。
サンプルのように,,*を追加指定すると、CSECT名をプログラム中に埋め込みます。これはプログラム・ヘッダーあるいはeye catcherとも呼ばれ複数のCSECTで構成されるプログラムのダンプを見るときに、どのモジュールかを判別しやすくするために行われるものです。規模の大きなプログラムや商用プログラムでは、さまざまな情報を持つヘッダーが作られることも多く、プログラム・デザインの1つでもあります。
RETURNはSAVEマクロの逆で呼び出し元レジスターを保管域から復元して、呼び出し元へ復帰する命令を展開します。
CALL、SAVEおよびRETURNはサブルーチンを呼び出したり、プログラムのハウスキーピング処理を行うためのアシストマクロですが、必ずしも使わなければならないものではありません。特にSAVEとRETURNマクロは自分で直接命令を書いてもさほど違いはありませんが、一般的にはよく利用されていますから、使い方を知っていれば、他の人が書いたプログラムを追う場合などで役に立ちます。
動的構造プログラムにおけるモジュールのローディング
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- LOAD EP=ZSUBRTN1 LOAD MODULE=ZSUBRTN1 LR RF,R0 LOAD EP ADDR IN GR15 LA R1,PLIST LOAD PLIST ADDRESS IN GR1 BASR RE,RF CALL SUBRTN1 LTR RF,RF SUCCESSFUL ? BNZ DOERROR NO, DO ERROR PROCESSING : : DELETE EP=ZSUBRTN1 DELETE MODULE=ZSUBRTN1 ICM RF,B'1111',ASUB1 EXIST TARGET MODULE IN JPA ? BNZ CALLSUB1 YES, REUSE IT LOAD EPLOC=MODLNAME, NO, LOAD MODULE=ZSUBRTN1 + ERRET=LOADERR DO LOADERR WHEN LOAD SVC FAILED ST R0,ASUB1 GR0 <-- entry address lr rf,r0 load ep addr in gr15 callsub1 ds 0h la r1,plist plist address gr1 basr re,rf call subrtn1 ltr rf,rf successful ? bnz doerror no, do error processing : : * *----------------------------------* * * handle svc * *> HEX TRANSLATE TABLE
動的構造のプログラムでは呼び出すモジュールは各々が独立したメンバーになっているため、CPU命令だけでは呼び出すことができません。呼び出す前にLOADマクロを使ってあらかじめリージョン内にローディングしておきます。ローディングされたモジュールの入口点アドレスはGR0に返されます。モジュールがローディングされた後は、単純構造のプログラム同様にBASR命令やCALLマクロで呼び出すことができます。使用し終わったローディング済みモジュールはDELETEマクロでリージョン内から消去します。(再入可能あるいは再使用可能なモジュールは呼び出しの都度でなく、これ以上呼び出す必要がなくなった時点でDELETEすればよい)
サンプルではPLISTをCALLマクロのリスト形式を使って定義しています。DC A(name)命令の代わりに利用したものです。
2番目は動的リンクの例です。この動的リンクはアプリケーション自らがやっており、MSPがアシストする動的リンク構造ではありません。どのOSにも共通に利用できる方法です。LOADマクロを出す前にサブルーチンの入口点アドレスをテストします。0でなければすでにローディング済みなのでLOADは迂回します。0ならば初回なのでLOADマクロを発行してローディングします。モジュール名は名前を直接指定するのではなく、EPLOCパラメーターで名前を格納した領域アドレスを指定する方法を使っています。ERRETパラメーターはローディングに失敗した場合に実行されるルーチンのアドレスです。失敗するとここで指定したラベル箇所(アドレス)に飛び込んできます。ERRETを指定しないとローディングに失敗した場合プログラムはAEBNDします。ERRETルーチンに飛び込んできた時のGR1にはABENDコードが入っています。
動的構造プログラムにおけるサブルーチンの実行
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- LINK EP=ZSUBRTN1, LINK MODULE=ZSUBRTN1 + PARAM=(DATA1,DATA2,DATA3),VL=1 LTR RF,RF SUCCESSFUL ? BNZ DOERROR NO, DO ERROR PROCESSING : : LOAD EP=ZSUBRTN1 LOAD MODULE=ZSUBRTN1 LA R1,PLIST LOAD PLIST ADDRESS IN GR1 LINK EP=ZSUBRTN1 LINK MODULE=ZSUBRTN1 LTR RF,RF SUCCESSFUL ? BNZ DOERROR NO, DO ERROR PROCESSING : : DELETE EP=ZSUBRTN1 DELETE MODULE=ZSUBRTN1 LOAD EP=ZSUBRTN1 LOAD MODULE=ZSUBRTN1 LA R1,PLIST LOAD PLIST ADDRESS IN GR1 LINK EP=ZSUBRTN1 LINK MODULE=ZSUBRTN1 LTR RF,RF SUCCESSFUL ? BNZ DOERROR NO, DO ERROR PROCESSING : LINK EP=ZSUBRTN1 LINK MODULE=ZSUBRTN1 LTR RF,RF SUCCESSFUL ? BNZ DOERROR NO, DO ERROR PROCESSING : : DELETE EP=ZSUBRTN1 DELETE MODULE=ZSUBRTN1 MODLNAME DC CL8'ZSUBRTN1' SUB-ROUTINE1 MODULE NAME PLIST CALL ,(DATA1,DATA2,DATA3),VL, ADDRESS PARAMETER LIST + MF=L DATA1 DC F'1' DATA2 DC XL8'0011223344556677' DATA3 DC C'TOKYO-OSAKA-FUKUOKA-NAHA'
LINKマクロはモジュールをローディングして呼び出します。CALLマクロの動的構造プログラム版と言ってもいいでしょう。LOADマクロはロードモジュールをメモリーにローディングするだけですが、LINKマクロはローディング+呼び出し、となります。呼び出すプログラムの処理が終わるとロードモジュールはメモリーから消去されます。
PARAMパラメーターは呼び出すプログラムに渡すパラメーターを指定します。CALLマクロにおけるパラメーター渡しと同様の指定を行います。VL=1もCALLマクロのVLパラメーターと同じ意味です。
2番目の例はLOADマクロ発行後にLINKマクロを発行する例です。このLINKではその前にLOADを発行していますのでモジュールは改めてローディングされません。ロード済みモジュールが使われます。しかし3番目の例では注意が必要です。2回目のLINKマクロではモジュールが改めてローディングされてしまいます。そして呼び出したプログラムが終了し、LINKマクロの処理が終了すると改めてロードされたモジュールのみが消去されます。このような動きは呼び出すモジュールがRENT,REUSのどちらの属性も持たない場合に起きます。モジュールにRENT,REUSいずれかの属性があればローディング済みのモジュールは共用されます。またREUS属性のモジュールは複数のタスクで同時に実行できないので、複数のタスクからLINKが発行されると、先にLINKマクロを発行したタスクでのモジュールの処理が終了するまで、他のタスクのLINKはスーパーバイザー内で待たされます。この待ち合わせにはENQは使用されていませんので、REUS属性のモジュールをLINKマクロとBASR等による直接CALLを混在して呼び出すことは危険です。LINKマクロに統一する方が間違いありません。
REUSモジュールでなければLOADとCALLの組み合わせがパフォーマンスの面でも優れています。実戦では本当の意味での逐次再使用可能モジュールをプログラムとして作ることは稀です。バッチ処理のようにシングルタスクのプログラムならREUSもRENTも必要ありませんし。マルチタスクならば再入可能プログラムの方が一般的です。わざわざ同時並行での実行を目的にタスクを分けているのに排他制御されてしまうREUSモジュールを作ることは設計に矛盾が起きてしまいます。REUSモジュールはどちらかと言えば、プログラムそのものよりはプログラムで使用するテーブルデータなどを作るために使われる方が多いでしょう。名前がわかっていればLOADマクロによって入口点アドレスが求められます。REUSならば複数のタスクでLOADされても空間内での初回のLOAD時のみディスクからモジュールがロードされ、2回目以降は同じアドレスが通知されます。なお1つのロードモジュールをDELETEせずにLOADマクロを出せる上限回数は32767です。これは知っておくべき事です。
動的構造プログラムにおける他プログラムへの実行切り替え
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- L RD,4(,RD) LOAD CALLER SAVEAREA ADDRESS L R1,24(,RD) LOAD GR1 AT ENTERED US XCTL (2,12),EP=NEXTPGM2 XCTL TO MODULE=NEXTPGM2 CALL/LINK ========= +----------------+ | SUB1 | | : | +----------------+ | LINK EP=SUB2 ------> | SUB2 | | : | | : | | <-----------|---- RETURN | | | | : | MAIN <----|---- RETURN | | : | | | +----------------+ +----------------+ XCTL ==== +----------------+ | SUB1 | | : | +----------------+ | XCTL EP=SUB2 ------> | SUB2 | | : | | : | | | | : | +----------------+ | : | | : | MAIN <----------------------------|---- RETURN | | : | +----------------+
XCTLマクロはLINKと異なり、呼び出したプログラムが終了しても自分に戻ってきません。呼び出したプログラムが終了した後は、自分ではなく自分を呼び出した親モジュールに直接戻ります。サブルーチンの処理が長く複数のモジュールに分割する場合や、機能に応じて処理モジュールを分割する場合などに利用できます。XCTLを発行するとEP/EPLOCで指定したモジュールに制御が渡った時点で自分自身のモジュールは他のタスクで使用中でなければ消去されます。(r1,r2)パラメーターで自分が呼び出されたときのレジスターを復元することができます。r1は2以上、r2は12以下で連続した範囲のレジスターがXCTLマクロ発行時点のGR13が示すレジスター保管域から復元されます。GR1は復元できないのでXCTLマクロ発行前に必要な値をセットします。GR0は引き継ぐことができません(実際に大丈夫だとしても保証されているわけではない)。GR13は自分を呼び出した親モジュール(呼び出し元)の保管域をポイントしておきます。
プログラムの終了(SVC 3:EXIT SVC)
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- SLR RF,RF SET COMPLETION CODE = 0 SVC 3 EXIT TO OS DISPATCHER L RE,RC,12(RD) RESTORE CALLER GPRS LA RF,4 SET RETURN CODE = 4 SVC 3 RETURN TO CALLER(PREVIOUS PRB)
マニュアルには載っていませんが、プログラムの終了はSVC 3によって行われます。リンケージ規約ではGR14が呼び出し元への戻りアドレスを示しますが、JCLのEXEC文のPGMパラメーターで指定され実行されたロードモジュールやLINK/XCTLマクロによって呼び出されたロードモジュールの入口点では、GR14が示す戻りアドレスはOSのコントロールブロックCVT内にあるSVC 3命令をポイントしています。なのでBR 14命令で戻る代わりにSVC 3命令を直接コーディングすることもできます。ただしBASR(BALR)命令で呼び出されたプログラムはSVC 3を使ってはなりません。呼び出し元へ戻らずにプログラムは終了してしまいます。BR14かSVC3かを考えて使い分けるのは現実的ではありませんので、基本通りBR 14でいいのですが、スーパーバイザーを経由して呼び出されたモジュールは戻る際もスーパーバイザーを経由する必要があります。戻るときはマクロではなくBR命令で戻っていますが、実際には分岐先にあるSVC 3命令が実行されてEXIT SVCが呼び出されスーパーバイザーを経由して呼び出し元へ戻っているのです。
サンプルはSVC 3命令でプログラムを終了させるものです。呼び出し元に戻った時のレジスターはSVC 3命令発行時の内容と同じです。プログラムを終了する場合はレジスターを復元しなくてもMVSがおかしくなるようなことはありませんが、LINKやXCTLマクロで呼ばれた場合、レジスターをきちんと復元しないと、戻った後で呼び出し元は正しく動くことができません。