01.1 仮想記憶域を獲得する(GETMAINとFREEMAIN)

プログラムで使うデータ領域には、あらかじめその大きさを固定できないものもあります。例えば、データセットを読み込む処理でレコードを読み込む領域の大きさを実際のデータセットのレコード長に合わせるような場合です。考え得る最大の長さでプログラム内に領域を用意するのも1つの方法です。しかし、ほとんどが100~200バイトの範囲で収まるが、たまに30000バイトという大きなものも取り扱うと言ったケースでは、その「たまに」のケースのために無駄に大きい領域を定義しなければなりません。いくら今では仮想記憶はふんだんに使えると言ってもこのようなプログラムは考え物です。
このような場合、プログラムは必要な時に必要な量の仮想記憶域(ストレージ)をダイナミックに獲得して使うことが出来ます。これが、MVSのGETMAINおよびFREEMAINサービスです。GETMAINはストレージを獲得し、FREEMAINはストレージを解放します。返却と言ってもいいでしょう。必要な時に必要な領域をOSから借りて、使い終わったら返すのです。

72バイトのレジスター保管域をプログラムの外に確保する

最も基本的なGETMAINとFREEMAINマクロの例です。Rパラメーターは、16MBラインの下の領域(以下24ビット域と記す)の無条件要求/解放を行います。LVパラメーターで獲得または解放する長さを指定します。FREEMAINマクロではAパラメーターで解放する領域のアドレスも指定します。
無条件要求なので獲得や解放に失敗するとプログラムはABENDします。例えば、指定した領域が大きすぎて獲得できない、獲得されていない領域を解放しようとしたなどです。GETMAINが成功するとGR1に獲得された領域の先頭アドレスが返ります。GR0、GR14~GR15はマクロ内あるいはサービスルーチン側で作業用に使用されるため内容は破壊されます。GR2~13はそのままです。
このGETMAINとFREEMAINも含め、多くのサービスではGR0~1、GR14~15は作業レジスターとして使用されるか、サービスの処理結果(復帰コードや獲得された領域のアドレス等々)を通知する目的で使われます。このことは共通して覚えておくといいでしょう。GR14はその値のまま保持されるものもありますが、それはたまたまだと考えます。今は値がキープされていても、次のバージョンでは使われてしまうこともあり得ます。マニュアルにはその辺りもきちんと明記されていますから必ず確認してください。

このサンプルは、リエントラント(再入可能)構造のプログラムを作るような場合に使われるものです。リエントラントであることが要求されるプログラムでは自分のプログラム内にデータを書き込むことができません。そのため、レジスター保管域も自分のプログラム内には定義できません。サンプルでは簡略化してありますが、実際には入口点ではGR0やGR1にパラメーター・アドレスが入っていたり、復帰時にはGR15にリターン・コードを返す必要があったりします。リンケージ・レジスターであるGR0~1、GR14~15は、マクロを発行されると内容が壊れてしまうため、必要に応じて別レジスターに一旦コピーしておくなどの工夫が必要です。どこにどういう命令を入れればよいかなどはご自身で考えてみて下さい。入口点でのGR13は呼び出し元レジスター保管域を指しますが、これは自分のプログラム内領域ではありませんから、リエントラントかどうかに関係なくそのまま使用できます。呼ばれたプログラムが最初に何をなすべきかがわかっていれば、壊されてしまうレジスターをどこでコピーすればよいかすぐにわかるでしょう。

OS出口ルーチンで使う作業用領域をプログラムの外に確保する

同じ無条件要求ですが、RではなくRUパラメーターを指定しています。RU要求では、LOCパラメーターで獲得したいストレージの位置(16MBラインの上下)を指定できます。SPパラメーターはストレージを割り当てるサブプール番号です(サブプールについてはここに簡単な説明があります「サブプールと記憶保護キー」。SP=230は、PVTの高位アドレスから割り当てられる領域です。一般に使用されるSP=0は、低位アドレスから割り当てられます。SP=230は、OSの出口ルーチンなどでPVTを使う際によく使われるサブプールです。ちなみに128以上のサブプールは、APF許可やスーパーバイザーモードのプログラムでなければ利用できません。

LOCパラメーターを省略すれば、プログラムのアドレッシング・モードに合わせて適切な領域が獲得されます。24ビット・モードなら16MB未満、31ビット・モードなら16MB以上です。31ビット・モードの時、マニュアルには16MBの上下いずれかからとありますが、現実には16MBの上から優先して割り振られます。仮に16MB以上の領域が一杯であれば16MB未満からの割り振りになりますが、現実に16MB以上の領域が一杯になるような状況では別のエラーが先に起こるでしょう。
獲得するストレージ位置をOS任せにしてもいいのですが、APIの仕様でアドレッシング・モードに関係なく16MB未満の領域を要求するものや、両方のアドレッシング・モードを状況によって切り替えるようなプログラムでは、LOCパラメーターでストレージ位置を明示的に指定します。その方がソースやリストを見たときにわかりやすくなります。LOC=(BELOW,ANY)は、仮想記憶上は16MB未満で実記憶は16MBの上下いずれでもよいことを示し、LOC=(ANY,ANY)は仮想記憶も実記憶も16MBの上下いずれでもよいことを示します。一般のプログラムは実記憶をアクセスすることはありませんから、24ビット・モードのプログラムでは仮想記憶は16MB未満でなければなりませんが、実記憶はどこであってもかまいません。なお、z/OSではBELOW/ANYの代わりに現在では24/31と書くことが推奨されています。これは実記憶が64ビットに拡張されていることに関連します。

最初の例で、リエントラント・プログラムのレジスター保管域をプログラム外に獲得する方法を紹介しました。こちらのサンプルでは、もう少し拡張して考えます。プログラムで書き込みを行うのはレジスター保管域だけとは限りません。その他にも、処理に使う様々なデータをメモリーに書き込みます。S/370アーキテクチャーでは1つのベース・レジスターでアドレスできるメモリーの大きさは4KBあります。レジスター保管域はGR13でポイントしますが、GR13をレジスター保管域だけの目的で使ってしまうと4KBアドレスできるのに先頭の72バイトしか使いません。もったいないことです。そこで、先頭の72バイトをレジスター保管域にして続く残りの領域をプログラムの作業用領域とすれば、最大4024バイトまでの領域を同じGR13でアドレスすることができます。GR13はレジスター保管域のアドレスを格納するもので、それ以外の目的に使ってはならないと頑なに信じていませんか?レジスター保管域の後ろにプログラムの作業領域を置いても、GR13が72バイトのレジスター保管域のアドレスを示すことに変りありません。特に、リエントラント・プログラムではレジスター保管域はプログラム外に置くのが鉄則ですから、どうせならその後ろの領域も積極的に活用しましょう。

31ビット領域にバッファ用ストレージを獲得する

RCパラメーターは、条件付き要求を示します。条件付き要求とは、指定した長さのストレージが獲得できない時や指定した領域が解放できない時に異常終了ではなく復帰コードで通知するものです。プログラムは、復帰コードを判定することで要求したストレージが獲得できたか解放できたかがわかります。復帰コードは、GR15で通知されGETMAINやFREEMAINに成功すれば0が返ります。0以外の時は要求が失敗したことを示します。

BNDRYパラメーターは、割り当てる領域の先頭アドレスをどのように境界合わせするかの選択です。DBLWD(8バイト・バウンダリー)とPAGE(4KBバウンダリー)があり、デフォルトはDBLWDです。

こちらのサンプルでは、FREEMAINにはRUを使っています。もちろんRCでもかまいません。ただ、FREEMAINの場合、指定した領域の返却に失敗するのはシステムやJCL(REGIONサイズ)の問題よりは、領域アドレスやサブプール番号を誤って指定していたりするケースがほとんどですから基本的にはプログラムのバグです。RU要求ならば、このような時に必ずABENDしますからそれでバグがあることがわかります。ABENDを嫌ってRC要求にしても、復帰コードを判定していないなどきちんとエラー処理をしないのならば、結果として領域の解放漏れを見逃してしまうことになります。その場合の原因追及の大変さを考えたら、テスト時にバグをあぶり出す方が遥かに効率的です。

サブプールの解放

一般のプログラムではあまり使われませんが、FREEMAINでは獲得した記憶域をサブプール単位で解放する機能もあります。特定のサブプールに属するGETMAIN済みストレージをまとめて解放します。例えば、サブルーチンの処理結果を返す領域をサブルーチン側で用意し、解放はメインルーチン側で行うような場合、アドレスや長さを管理しなくともサブプール単位でまとめて解放することができます。
サブプール単位の解放は、TPモニターなどアプリケーション・プログラムを制御する側にとっては便利な機能です。

STORAGEマクロ

従来からのGETMAINとFREEMAINに代わり、OBTAINマクロを使用することもできます。GETMAINとFREEMAINではSVC命令でAPIが呼び出されますが、OBTAINではPC命令によるクロスメモリー機能が使われます。一般のプログラムではGETMAINとFREEMAINでかまいませんが、SVC命令を発行できないモードで走行中のプログラムやアクセス・レジスターモードで走行中のプログラムではOBTAINマクロを使う必要があります。SVC命令を発行できないモードで走行中のプログラム(SRBルーチンやタイプ1SVCルーチンなど)では、従来はブランチ命令によってOSのGETMAIN/FREEMAINルーチンを直接呼び出す必要がありましたが、ローカルロックの保持などOSのSVCハンドラー並みの環境設定を行う必要があります。STORAGEマクロならコーディングも容易で、どのようなモードのプログラムでも共通に利用できるストレージ・サービスです。

ワンポイント知識

長さ0のGETMAIN

LVパラメーターで要求するストレージの長さを誤って 0 と指定してもエラーになりません。ABENDもしません。もちろん実際に領域が獲得されることもありませんが、GR1には0が返ります。あり得ないエラーではないので、レジスターに長さを入れて要求する場合は注意しましょう。なお、GR1の0をアドレス0番地として書き込みに行けば当然ABENDS0C4です。

GETMAINした領域の初期値は?

GETMAINした領域はどうなっているでしょう?NULLクリアーされているはず?一言で言えば、GETMAINした領域の内容は不定です。NULLクリアーされると考えるのは間違いです。実際には、NULLクリアーされる時とされない時があるが正解です。
NULL値でクリアーされるのは新しいページが切り出された時だけで、既に割り振り済みのページ内から割り当てられる時は何もされません。その領域が以前にGETMAINされていればFREEMAINした時の状態と言っていいでしょう。BNDRY=PAGE指定では必ず新しいページが割り当てられるのでNULLクリアーされます。また、一度GETMAINした領域をFREEMAINした時、そのFREEMAINでページ内のすべてがFREEMAINされる場合はページ自体が返却されるので、また同じ長さなどで同じページが割り当てられれば再度NULLクリアーされます。こういうことがわかった上で「GETMAINした領域はNULLクリアーされている」と言う前提でプログラムを作るならかまいませんが、基本的にはGETMAINした領域はNULLクリアーされていないと考える方が間違いないでしょう。この事はMVSのマニュアルにもはっきりと書かれています。

アドレス空間(リージョン)のフラグメント化

GETMAINでは、必要な長さを割り当てられる空き領域がある割り当て済みページがあるか否かが調べられます。4KBの内、3000バイト使われていれば1096バイト空いています。次のGETMAINで1000バイトなら同じページから割り当てられ、1096バイトを超えるなら新しいページが使われます。2192バイトが指定された時、1096バイトの空きページが2つあっても飛び飛びで割り当てられることはありません。やはり新しいページが使われます。GETMAINされる領域は、必ず連続した領域が割り当てられます。
3000バイトの領域を10回GETMAIN要求すると、30ページが使われます。各々のページは3000バイトの割り振りずみ領域と1096バイトの未使用領域となります。このような状態がフラグメント化です。この場合、未使用領域は1096バイト以下のGETMAIN要求でしか使うことができません。30000バイト1回で要求すれば8ページで済みます。システム系プログラムでは、必要な量を一度に要求してプログラム側で区分けするようなこともよく行われます。