05.リンケージ規約(サブルーチンを作る)
リンケージ規約(Linkage convention)とはプログラム間の呼び出しに関する取り決めです。規模の大きいプログラムはいくつかのモジュールに分割して、異なる人によって作成されたりします。各人が好き勝手に作っては上手く動きません。そこでプログラム間のインタフェースに共通のルールを作ります。
MVSでもプログラム間のインタフェースに関して標準的な約束事を取り決めています。MVSで動作するプログラムはこの約束事に従って作ることが重要ですし、知っていなければ(守らなければ)正しく動くプログラムを作ることができません。タイトルには(サブルーチンを作る)とありますが、メインルーチンを作る場合でも同じ事です。アプリケーションとしてはメインルーチンであっても、MVSからは一種のサブルーチンとして扱われるのです。なおリンケージ規約はアーキテクチャーで定められたものではなく、使用するオペレーティング・システムによって取り決められます。MVSやVSE(MSP,VOS3,XSPも)などIBMメインフレーム系アーキテクチャーで動くOSは基本的に同じリンケージ規約が使われています。アプリケーションのみならず言語処理プログラムやユーティリティなどもリンケージ規約に従って作られています。OSや多くのミドルウェアなどもユーザープログラムに制御を渡す(呼び出す)際はリンケージ規約を守ります。
リンケージ規約が取り決めているのは以下のプログラム間インタフェースです。
- 呼出し先プログラムの入口点アドレスの提示
- 呼出し元プログラムへの復帰アドレスの提示
- 呼出し元レジスターの保証
- パラメーターの通知
- 復帰コードの通知
MVSのリンケージ規約
- プログラムを呼び出す側が用意する (CALLする方が用意する)
- 呼び出されたプログラム側が保管する(サブルーチン側で使う(保管する))
入口点アドレス(GR15)
他のプログラムを呼び出す際、呼び出し元プログラムは、呼び出すプログラムの入口点アドレスをレジスター15に格納して呼び出さなければなりません。
呼び出されたプログラムはレジスター15に自分の入口点アドレスが格納されているものとして処理を行うことができます。(ペースアドレスの設定)
入口点アドレスの先頭ビットは呼び出し先プログラムのアドレスモード(AMODE)を示します。正しいリンケージ手順であれば、呼び出された時点でPSWのアドレスモードは呼び出されるプログラムのAMODEに切り替わっています。
復帰アドレス(GR14)
他のプログラムを呼び出す際、呼び出し元プログラムは、呼び出すプログラムが自分に戻ってくるための復帰アドレスをレジスター14に格納して呼び出さなければなりません。
呼び出されたプログラムは、処理終了後に呼び出し元へ戻る際、呼ばれた時点のレジスター14に格納されているアドレスを使って呼び出し元へ帰ることができます。
復帰アドレスの先頭ビットは呼び出し元プログラムのアドレスモード(AMODE)を示します。正しいリンケージ手順であれば、呼び出された時点でレジスター14の先頭ビットには、呼び出し元のアドレスモードがセットされています。
学習として24ビットモードのプログラムを作る場合、とりあえずアドレスモードについては意識しなくてもかまいません。まずはGR15に入口点、GR14に復帰先のアドレスがセットされるのだ、と覚えて下さい。
呼び出し元レジスター保管域(GR13)
レジスターの保管はリンケージ規約の中でも重要です。呼び出されたプログラムは汎用レジスターを自分の処理のために自由に使ってかまいませんが、呼び出し元に戻る際には一部を除き復元しなければなりません。このためにリンケージ規約では「レジスター保管域」(またはレジスター退避領域)と呼ばれる18ワード(72バイト)の領域を使って使用するレジスターを保管することになっています。
慣れないと少しわかりにくいのですが、レジスター保管域は、
となります。(親が用意した領域を子供が使う)
レジスター保管域は呼び出し元で用意し、その先頭アドレスをレジスター13に設定しておきます。呼び出されたプログラムは呼び出された時点のレジスター13が示すアドレスへ、自分のプログラムで使用するレジスターを保管しておき、戻る際にそこから復元しなければなりません。使うレジスター/使わないレジスターをいちいち選んで保管・復元するのは面倒なので、よほどの理由がない限りレジスター13以外のすべての汎用レジスターを保管します。そしてレジスター15以外のすべてのレジスターを復元して呼び出し元へ戻ります。
レジスター保管域は複数のプログラムで共用することはできません。呼び出されたプログラムがさらに別のプログラムを呼び出すならば、別のレジスター保管域を用意してレジスター13にて指し示す必要があります。通常は別のプログラム(サブルーチン)を呼ぶ/呼ばないに関わらず、新たなレジスター保管域を用意します。新たに用意した保管域の2番目のワードに、呼び出し元の保管域アドレスをセーブします。これは呼び出し元レジスター13の保管となります。そして新たな保管域のアドレスを呼び出し元保管域の3番目のワードにセーブします。
このように呼び出し元と呼び出し先のレジスター保管域はチェインされます。プログラムに問題が発生した時、このチェインをたどることによって、どのモジュールがどういうレジスター内容で呼び出されたかを追跡することができるようになっています。
+-------------------------------------------+ +00 | ユーザー領域(PL/Iコンパイラー等で使用) I +-------------------------------------------+ +04 | 呼び出し元レジスター保管域アドレス(*1) I +-------------------------------------------+ +08 | 呼び出し先レジスター保管域アドレス I +-------------------------------------------+ +12 | レジスター14 保管域 | +-------------------------------------------+ +16 | レジスター15 保管域 | +-------------------------------------------+ +20 | レジスター0 保管域 | +-------------------------------------------+ +24 | レジスター1 保管域 | +-------------------------------------------+ +28 | レジスター2 保管域 | +-------------------------------------------+ +32 | レジスター3 保管域 | +-------------------------------------------+ +36 | レジスター4 保管域 | +-------------------------------------------+ +40 | レジスター5 保管域 | +-------------------------------------------+ +44 | レジスター6 保管域 | +-------------------------------------------+ +48 | レジスター7 保管域 | +-------------------------------------------+ +52 | レジスター8 保管域 | +-------------------------------------------+ +56 | レジスター9 保管域 | +-------------------------------------------+ +60 | レジスター10 保管域 | +-------------------------------------------+ +64 | レジスター11 保管域 | +-------------------------------------------+ +68 | レジスター12 保管域 | +-------------------------------------------+
パラメーターの通知(GR1およびGR0)
パラメーターは個々のプログラムの処理内容によって大きさ、個数、内容がバラバラなので、規約として取り決めることはできません。しかしパラメーターの通知については汎用的な方法が標準化されています。
GR1 +------------------------------+ | アドレスパラメーターリスト ↑| +------------------------------+ |↑記号は「○○○のアドレス」を示す。 | | ↓ アドレスパラメーターリスト(フルワード境界に揃える) +----------------------+ +00 | パラメーター1 ↑ | ――――→ パラメーター1 F'67123' +----------------------+ +04 | パラメーター2 ↑ | ――――→ パラメーター2 CL10'ABCDEFGHIJ' +----------------------+ +08 | パラメーター3 ↑ | ――――→ パラメーター3 H'123' +----------------------+ : : : +-+--------------------+ +nn |1| パラメーターn ↑ | ――――→ パラメーター4 X'0A' +-+--------------------+ 最終パラメーター エントリーの先頭ビットを 1にする。そうすることで 可変長のパラメーターリスト を持つことができる。 COBOLっぽく書くと、 CALL "module名" USING パラメーター1 パラメーター2 パラメーター3... として呼ばれたプログラムは上記のフォーマットでパラメーターが渡ってくる。
パラメーターは1つであれ、複数であれ、その値が格納された領域のアドレスを、渡す順番にフルワードの領域に並べて格納します。パラメーターのアドレスが格納された領域を「アドレスパラメーターリスト」と呼びます。C言語におけるポインターです。
ポインターと聞いてもくじけないで下さい。S/370アセンブラーでのアドレスの使い方は、C言語のポインター演算や定義よりはるかにシンプルかつ単純です。C言語のポインターに挫折した方はS/370アセンブラーでのアドレスの使い方を覚えた後、再度チャレンジして見て下さい。次は理解できると思います。
アドレスパラメーターリストは可変長のフルワード領域で、パラメーターの個数*4バイトの長さを持ちます。最後(1つであれば最初の)のパラメーター領域のアドレスを示すワードの先頭ビットを1にすることで、呼び出されたプログラムは何個のパラメーターが指定されたかを知ることが出来ます。呼び出し元プログラムはアドレスパラメーターリスト自身のアドレスをレジスター1に入れて、サブルーチンを呼び出します。
またアセンブラープログラム同士であれば、レジスター0に追加のパラメーターを入れて通知することもあります。MVSやDFPのAPIサービスではこのようなパラメーター通知の方法を採るものもあります。
リンケージ規約の中でもこれまでに出てきたGR13からGR15で指定する、入口点アドレスと復帰アドレスの通知、レジスター保管域の使用、に比べるとアドレスパラメーターリストによるパラメーターの通知方法は絶対に守らなければならないものではありません。アプリケーションなら独自のパラメーター通知方法を使ってもかまいませんし、4バイト以下のパラメーターなら、アドレスではなく値そのものを、アドレスパラメーターリストの中に入れてしまうことも行われます。しかし他言語から呼び出されるアセンブラールーチンや、汎用的なAPIルーチンやユーティリティのようなプログラムを作るなら、できるだけこのフォーマットによるパラメーター通知方法を設計した方がいいでしょう。COBOLなどの言語環境プログラム、OSの各種ユーティリティなどもこのフォーマットに沿ってパラメーターを処理しています。
GR1 +------------------------------+ | アドレスパラメーターリスト ↑| +------------------------------+ | ↓ アドレスパラメーターリスト(語境界) +-------------------------+ +00 |1| EXECパラメーター ↑ | +-+-----------------------+ | ↓ +00(半語境界)+02 +--------------+--------------------//--------------+ +00 | 文字列の長さ | パラメーター文字列(0?100バイト)| +--------------+--------------------//--------------+ 2バイト 可変長
JCLのEXEC文のPARMパラメーターで指定したパラメーター文字列はこの図のように渡されます。PARMパラメーターが省略されても同じです。その場合、文字列の長さが0で通知されます。これは必ず必要になりますから是非覚えてください。
復帰コードの通知(GR15)
プログラム処理の結果の1つである復帰コードはレジスター15に入れて呼び出し元に通知します。レジスター15は入口点アドレスで使われますが、呼び出し元へ帰る際は不要です。よってMVSではGR15を復帰コードの通知レジスターとして使用します。COBOLなどのコンパイラー言語から呼び出された場合でもレジスター15へ復帰コードを設定します。復帰コードはコンパイラーの実行時ライブラリによって、然るべき変数(RETURN-CODEなど)に設定されます。プログラムがEXEC文のPGMパラメーターで指定されて実行されたジョブステップタスクのメインプログラムであれば、レジスター15に設定された値は完了コードとなります。
リンケージ規約で使用するGR0,1およびGR13,14,15の各レジスターはリンケージレジスターとも呼ばれます。これらのレジスターはプログラム間リンケージで重要な役割を果たすため、壊してはならないと後生大事に抱えているビギナー・プログラマーをたまに見かけますが、プログラムの冒頭で保管域にセーブしているのですから自由に使ってかまいません。それにMVSやDFPのサービスを呼び出せばどうせ壊れます。レジスター13以外の0,1,14,15番レジスターは作業用のワーキング・レジスターです。積極的に使いましょう。
レジスター | 用途 |
---|---|
GR0 | 追加のパラメーター等 |
GR0 | アドレスパラメーターリスト等 |
GR13 | レジスター保管域のアドレス |
GR14 | 呼び出し元への復帰アドレス |
GR15 | 入口点アドレス、復帰コード |
プログラムの入口点処理と呼び出し元への復帰処理のサンプル
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- MYPROG CSECT , DEFINE CODE SECTION USING *,12 DEFINE BASE REGISTER STM R14,R12,12(R13) ① SAVE CALLER REGISTERS LR R12,R15 ② GR12 -> OUR BASE ADDRESS LR R15,R13 ③ SAVE CALLER SAVEAREA LA R13,SAVEAREA ④ LOAD OUR SAVEAREA ADDRESS ST R15,4(,R13) ⑤ SAVE CALLER SAVEAREA POINTER ST R13,8(,R15) ⑥ SET BACK CHAIN FOR LINK TRACE L R1,0(,R1) ⑦ LOAD EXEC PARM FIELD ADDRESS LH R15,0(,R1) GR15 -> PARM STRING LENGTH LA R14,2(,R1) GR14 -> BEGIN OF PARM STRING BAS 10,SHOWPARM SHOW EXEC PARM UPON CONSOLE LA R15,0 ⑧ SET RETURN CODE = 0 L R13,4(,R13) ⑨ RESTORE CALLER SAVEAREA L R14,12(,R13) ⑩ LOAD RETURN ADDRESS TO CALLER LM R0,R12,20(R13) ⑩ RESTORE CALLER REGISTERS BR R14 ⑪ RETURN TO CALLER : SAVEAREA DC 18F'-1' ⑫ OUR REGISTER SAVEAREA : * *----------------------------------* * * CONSOLE DISPLAY SUB-ROUTINE. * * * GR14 --> TEXT ADDRESS * * * GR15 --> TEXT LENGTH * * *----------------------------------* SHOWPARM DS 0H LTR 1,15 VALID LENGTH BZR 10 NO, LA 1,4(,1) CORRECT WTO PARM LENGTH STH 1,MSGTLNG SET IT BCTR 15,0 DECREMENT TEXT LENGTH FOR EX. EX 15,*+4+4 MOVE PARM TEXT IN OUR AREA B *+4+6 (AROUND MOVE INSTRUCTION) MVC MSGTEXT(0),0(14) (MVC MODEL INSTRUCTION) WTO MF=(E,WTOPARM) SHOW MESSAGE UPON CONSOLE BR 10 RETURN TO MAINLINE WTOPARM DS 0F MVS WTO SVC PARAMETER LIST MSGTLNG DC Y(0) MSG TEXT LENGTH(INC CNTLS) DC AL2(0) MCS FLAGS MSGTEXT DC CL100' ' EXEC PARM TEXT(COPY) : : :
- ①呼び出し元レジスター14,15,0から12を保管。
- ②入口点アドレスを自分のベースレジスターにセット。
- ③呼び出し元の保管域アドレスを退避。(この時点でGR15は不要となっている)
- ④レジスター13に自分のレジスター保管域のアドレスをセット。
- ⑤呼び出し元保管域のアドレスを、自分のレジスター保管域の第2ワードに退避。(呼び出し元レジスター13の保管)
- ⑥自分の保管域アドレスを呼び出し元保管域の第3ワードにセット。
- ⑦JCLのEXEC PARMパラメーター領域のアドレスをロード、GR14と15にパラメーター文字列のアドレスと長さをセット。
- ⑧復帰コード(完了コード)をレジスター15にセット。
- ⑨呼び出し元レジスター保管域のアドレスをロード。(レジスター13の復元)
- ⑩レジスター15を除く呼び出し元レジスターを保管域から復元。(レジスター14,0?12)
- ⑪呼び出し元プログラムへ戻る。
- ⑫自分のレジスター保管域の定義。例では初期値-1としているが0でもかまわない。値は何でもよい。
このプログラムはEXEC文のPARMパラメーターで指定されたパラメーター文字列を、コンソールに表示する簡単なプログラムです。
必要な初期設定を行った後、BAS 10,SHOWPARM命令でSHOWPARMサブルーチンを呼び出してパラメーターをコンソールに表示し、復帰コード0を設定して呼び出し元(MVSのジョブ管理ルーチン)に戻ります。
CSECTアセンブラー命令以下、⑥までの処理がリンケージ規約に基づく最初のセットアップ処理です。ハウスキーピング処理とも呼ばれます。何種類かの方法がありますが、リンケージ規約に合っていれば、使う命令や順序などに決まりはありません。⑧以降は呼び出し元への復帰処理ですが、これも重要なのは、レジスターを呼び出された時の状態に復元する、と言うことです。レジスターを元に戻さずに復帰してしまうと、呼び出し元は正しく処理を継続できません。
リンケージ規約によるハウスキーピングの処理には複雑な命令は必要ありません。「マシン命令入門」で紹介した基本となる何種類かの命令でロジックが作れます。プログラムの規模や内容に関係なく、とにかく絶対必要な処理なのでしっかりと覚えて下さい。
ここまでくれば、実際にMVSで動かすプログラムを書き始めることができます。まだ使ってない命令やOSのサービスAPIなどたくさんありますが、残りは必要に応じて調べて行けばいいのです。次回からは計算や文字処理、分岐とループなどプログラミングっぽいテーマで解説します。