06.1 プログラムのローディングと実行(LOAD,LINKとXCTL)

プログラムのローディングと実行は、一般のプログラムでも身近な機能です。規模の大きなプログラムでは、複数のモジュールに分割して開発、管理及び保守が行われます。この記事では、複数のモジュールで構成されるプログラムの処理に関するMVSの機能を解説します。

MVSにおけるプログラムの構造

単純構造プログラム

プログラムのCSECTが1つであっても複数であっても、1つのロード・モジュール内で全てのCSECTがバインダー(リンケージ・エディター)で結合されているものです。
ソースモジュールが分割され個々にコンパイル(アセンブル)されたとしても、バインダーが1つにまとめ上げます。サブルーチンの呼び出しなど、外部アドレスの参照はバインダーが解決します。単純構造プログラムでは、ロード・モジュール内の各々のモジュール(サブルーチン)は、BASRやBASSMなどCPUの分岐命令を使い直接呼び出すことができます。そのため、モジュール間のリンケージ動作は最も効率が高くなります。

動的構造プログラム

複数の単純構造プログラムの組み合わせによって構成されるものです。1つ1つのモジュールは、それぞれが独立したロード・モジュールです。もちろん、複数CSECTで構成されるロード・モジュールが含まれていてもかまいません。動的構造プログラムでは、サブルーチンの呼び出し等に関する外部参照は解決されていません。外部参照を解決するには、MVSのAPIを使ってプログラム自ら行うことになります。
大規模なプログラムでは、モジュールが占める記憶域量を減らせたり(必要なモジュールだけが読み込まれる)、モジュール修正が行われル場合も修正を施すモジュールだけの変更で済むためモジュールの保守性を高めることができます。

再入可能(リエントラント)プログラム

単純構造や動的構造に関係なくプログラム・デザインに関するもので、複数のタスクで同時に実行できるプログラムのことです。プログラムが処理に使うデータの内、更新を伴うものをプログラム内部に置かない設計になっているものです。端的な例がレジスター保管域です。レジスター保管域をプログラム内に置いたプログラムが同時に複数のタスクで実行されると、タスク1がそのプログラムでサブルーチンAを呼び出し、そのサブルーチンが実行中に、タスク2でも同じプログラムが実行されサブルーチンAが呼び出されると、最初に呼び出されたタスク1のレジスター保管域は、後から呼び出されたタスク2のサブルーチンAによって上書きされてしまいます。そうなるとタスク1で最初に呼び出されたサブルーチンAは、元の呼び出し元に正しく戻れなくなってしまいます。
これは、同じ領域にローディングされているプログラムを複数のタスクで共有使用する場合の問題です。同じプログラムのコピーをタスク毎に使えばこのような問題は解決しますが、メモリーの利用効率やパフォーマンスの観点で劣ってしまいます。特に、あらゆるプログラムから頻繁に利用されるシステムの共通機能ルーチンのようなものでは顕著です。そこで、このような問題を解決するために、レジスター保管域など書き込みされるデータ領域をプログラムの外に置き、タスク毎にGETMAINして使用すればデータ領域は他のタスクによって壊されることがなくなります。プログラムの命令コード部分と書き込みされるデータ領域を分けた設計になっているプログラム・モジュールを再入可能プログラム、再入可能モジュールと呼びます。

なお、再入可能プログラムのように複数のタスクで同時に使用することはできないものの、データ領域をプログラムの先頭で初期化するような処理を持ち、順番に1タスクずつなら同じ領域のプログラムを実行することができるものは逐次再使用可能(reusable)プログラムと呼びます。

非再入可能プログラム

同じ領域GPRSAVEへの書き込みを複数のタスクで行うため、競合のような同時書き込みでなくてもTASK1で保管→復元してからTASK2で保管→復元のシーケンスが崩れ、TASK1で保管→TASK2で保管→TASK1で復元→TASK2で復元となったり、TASK1で保管→TASK2で保管→TASK2で復元→TASK1で復元となってしまうと、TASK1はTASK2のレジスター内容で復元されてしまいロジックに矛盾が生じます。

再入可能プログラム

領域GPRSAVEは、呼び出しの都度プログラムの外に確保されるため複数のタスクで同時に呼び出されてもレジスター保管域は競合しません。TASK-1とTASK-2がどのような順序でプログラムを使用しても互いのデータ領域が侵されることはありません。

逐次再使用可能プログラム

プログラム内に書き込みデータ領域を持っていても、あるタスクがそのプログラムを実行中は、他のタスクでは呼び出すことができません。APIを使用してスーパーバイザー経由で呼び出せば、他のタスクでプログラムを使用中の時、先に呼び出した側が呼び出し元へ戻るまでスーパーバイザー内で待ち状態になります。

再入可能プログラムにはRENT、逐次再使用可能プログラムにはREUSのオプションをロード・モジュール作成時のバインダー(リンケージエディター)のパラメーターに指定します。このオプションでロード・モジュールの属性を明示的に示さない限り、MVSはモジュールをRENTとREUSのどちらにもしません。なお、バインダーもOSも本当にモジュールがRENTあるいはREUSの動作に見合うロジックで書かれているかのチェックまではしません(できません)

単純構造プログラムにおけるサブルーチンの呼び出し

単純構造プログラムでは、サブルーチン(他のモジュール)をCPU命令で直接呼び出すことができます。リンケージ規約に則れば、GR15に呼び出すモジュールの入口点アドレス、GR14に戻りアドレスを格納して入口点アドレスへ分岐します。BASR(BALR)命令を、サンプルのようにコーディングすればいいのです。サンプルでは、GR1にパラメーター・アドレスをセットしています。パラメーターはアドレス・パラメーター・リスト形式で渡していますが、呼び出す側と呼び出される側で合意すればどんな形式で渡してもかまいません。アドレス・パラメーター・リスト方式は、パラメーター数が少ないときは面倒ですが、汎用的な方法なので他の言語で作成されたプログラムとリンケージを取るような場合にも適しています。
サンプル中程は、同じことをCPU命令ではなくCALLマクロ命令を使って行うものです。呼び出すモジュールの入口点アドレス・フィールドやパラメーター・リストなどは、CALLマクロの中で自動生成されます。CALLマクロは、スーパーバイザーのサービスを呼び出すAPIではなく、機能に対応するCPU命令列をプログラマーに代わり生成するものです。COBOL言語を主に使うプログラマーには比較的理解しやすい方法です。最初に呼び出すプログラムの入口点名(CSECT名)の名前または入口点アドレスを格納したレジスター番号を指定し、続いてプログラムへ渡すパラメーターが格納されている領域の名前を括弧でくくって指定します。VLは、パラメーター・リスト上の最後のパラメーターを示すエントリーの先頭ビットを1にするオプションです。最終パラメーターのエントリーの先頭ビットを、1にする必要があるかどうかはプログラムの呼び出しインターフェースによって決まります。
呼び出されるプログラムで使用しているSAVEマクロは、プログラムの先頭で呼び出し元のレジスターを保管する命令を展開します。ただし、自プログラムのレジスター保管域の定義や呼び出し元プログラムのレジスター保管域とのチェインを行う命令は生成されません。サンプルでは省略していますが、必要な処理は自ら行わねばなりません。パラメーターとして「,,*」を追加指定すると、CSECT名をプログラム中に埋め込みます。これはプログラム・ヘッダーあるいはeye catcherとも呼ばれ、複数のCSECTで構成されるプログラムのダンプを見る際に、どのモジュールかを判別しやすくするために行われるものです。規模の大きなプログラムや商用プログラムでは、様々な情報を持つヘッダーが作られることも多く、どのような情報でヘッダーを構成するかはプログラム・デザインの1つでもあります。
RETURNマクロは、SAVEマクロの逆で呼び出し元レジスターを保管域から復元して呼び出し元へ復帰する命令を展開します。CALL、SAVEおよびRETURNの各マクロは、サブルーチンを呼び出したりプログラムのハウス・キーピング処理を行うためのアシスト・マクロですが、必ずしも使わなければならないものではありません。特に、SAVEとRETURNマクロは自分で直接命令を書いてもさほど違いはありません。ただ、一般的にはよく利用されていますから、使い方を知っていれば他の人が書いたプログラムを追う場合などで役に立ちます。

動的構造プログラムにおけるモジュールのローディング

動的構造のプログラムでは、呼び出すモジュールは各々が独立したメンバーになっているためCPU命令だけでは呼び出すことができません。呼び出す前に、LOADマクロを使って予めリージョン内にローディングしておきます。ローディングされたモジュールの入口点アドレスはGR0に返されます。モジュールがローディングされた後は、単純構造のプログラム同様にBASR命令やCALLマクロで呼び出すことができます。使用し終わったローディング済みモジュールは、DELETEマクロでリージョン内から消去します。サンプルでは、呼び出すモジュールへ渡すパラメーター・リストをCALLマクロのリスト形式を使って定義しています。DC A(name)命令の代わりに利用しています。
2番目のLOADマクロは、動的リンクの例です。動的リンクでは、LOADマクロを出す前にサブルーチンの入口点アドレスをテストします。0でなければ既にローディング済みなのでLOADは迂回します。0ならば初回なのでLOADマクロを発行してローディングします。モジュール名は、名前をマクロに直接指定するのではなくEPLOCパラメーターで名前を格納した領域アドレスを指定する方法を使っています。ERRETパラメーターは、ローディングに失敗した場合に実行されるルーチンのアドレスです。失敗するとここで指定したラベル箇所(アドレス)に飛び込んできます。ERRETを指定しないと、ローディングに失敗した場合プログラムはAEBNDします。ERRETルーチンに飛び込んできた時のGR1にはABENDコードが入っています。

動的構造プログラムにおけるサブルーチンの実行

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はスーパーバイザー内で待たされます。APIを使わずに直接CPU命令で呼び出すと排他制御されないため、REUS属性のモジュールをLINKマクロとBASR等による直接CALLを混在して呼び出すことは危険です。

REUSモジュールでなければ、LOADとCALLの組み合わせがパフォーマンスの面でも優れています。実際には、本当の意味での逐次再使用可能モジュールをプログラムとして作ることは稀です。バッチ処理のようにシングル・タスクのプログラムならREUSもRENTも必要ありませんし。マルチ・タスクならば再入可能プログラムが一般的です。わざわざ同時に並行しての実行を目的にタスクを分けているのに、排他制御されてしまうREUSモジュールを作ることは設計に矛盾が起きてしまいます。REUSモジュールは、どちらかと言えばプログラムそのものよりは、プログラムで使用するテーブル・データなどを作るために使われる方が多いでしょう。名前がわかっていればLOADマクロによって入口点アドレスが求められます。REUSならば複数のタスクでLOADされても、空間内での初回のLOAD時のみディスクからモジュールがロードされ、2回目以降は同じアドレスが通知されます。なお、1つのロード・モジュールをDELETEせずにLOADマクロを出せる上限回数は32767です。これは知っておくべき事です。

動的構造プログラムにおける他プログラムへの実行切り替え

XCTLマクロは、LINKと異なり呼び出したプログラムが終了しても自分に戻ってきません。呼び出したプログラムが終了した後は、自分ではなく自分を呼び出した親モジュールに直接戻ります。XCTLは、サブルーチンの処理が長く複数のモジュールに分割する場合や機能に応じて処理モジュールを分割する場合などに利用できます。XCTLを発行すると、EP/EPLOCで指定したモジュールに制御が渡った時点で、自分自身のモジュールは他のタスクで使用中でなければ消去されます。(r1,r2)パラメーターで自分が呼び出されたときのレジスターを復元することができます。r1は2以上、r2は12以下で連続した範囲のレジスターが、XCTLマクロ発行時点のGR13が示すレジスター保管域から復元されます。GR1は復元できないので、XCTLマクロ発行前に必要な値をセットします。GR0は引き継ぐことができません。GR13は、自分を呼び出した親モジュール(呼び出し元)の保管域をポイントしておきます。

プログラムの終了(SVC 3:EXIT SVC)

サンプルは、SVC 3命令でプログラムを終了させるものです。呼び出し元に戻った時のレジスターは、SVC 3命令発行時の内容と同じです。プログラムを終了する場合、レジスターを復元しなくてもMVSがおかしくなるようなことはありませんが、LINKやXCTLマクロで呼ばれた場合はレジスターをきちんと復元しないと戻った後で呼び出し元は正しく動くことができません。

マニュアルには載っていませんが、プログラムの終了は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命令で復帰します。スーパーバイザーを経由して呼び出されたモジュールは戻る際もスーパーバイザーを経由する必要がありますが、MVSではスーパーバイザー経由呼び出しでは、GR14が示す分岐先(戻りアドレス)にスーパーバイザー経由で戻るための命令であるSVC 3が書き込まれています。SVC 3:EXIT SVCが呼び出され、スーパーバイザーを経由して呼び出し元へ戻ります。