11.SYSINとSYSOUTのI/O

CPU命令だけを使っていても、あまり面白くないし実用的ではありません。「S370アセンブラー講座」の最後に「アセンブラーではI/Oをどうやって処理するのだろう?」と言うことに触れて一連の解説を終えます。誰かに呼ばれるだけのサブルーチンやOSの出口ルーチン等でなければ、プログラムにI/Oはつきものです。

プログラムの基本的なINPUTとOUTPUTは、上記のようにJCL定義されます。ここでは実際にこのように定義されたDDステートメントに基づく入出力先に対するI/Oのやり方を説明します。基本的なI/Oはアセンブラーでも大して難しくありません。I/Oを行う部分のコードに関してはCOBOLと大差ないのです。

QSAM(待機順アクセス法)

QSAMは、順次データセットをアクセスするためのMVSのAPIです。正確にはDFSMS/DFPと言うデータ管理コンポーネントが提供するAPIです。CPUには命令としてのI/O命令(例えばSSCH:Start Sub CHannel)もあります。しかし、このハードウェアのI/O命令を使って一般のプログラムが入出力を行うことはありません。たとえ命令解説書を読んで理解したとしても、決してやってはいけません。MVSではI/Oは全てOS自身が一元管理します。I/Oに関するOSの機能はいくつかの階層に分かれていますが、一般のプログラムはDFP(データ管理コンポーネント)に対してQSAM等のAPIを使ってデータセット等へのI/Oを要求します。
アセンブラー・プログラムからOS等のAPIを呼び出すために使われるのがマクロ命令です。マクロはアセンブラーの機能で、いくつかの命令をまとめたもので定型的な処理などを行うために使われます。マクロを使わずに直接CPU命令でDFPのAPIルーチンを呼び出すこともできますが一般的ではありません。最初は(ほとんどのケースで最後まで)マクロ命令を使って基本的なアクセスの仕方を覚えましょう。

QSAMではデータセットをレコード単位にアクセスできます。上記のJCLでは、SYSINとSYSOUTをDDステートメントで定義していますがデータセットそのものを定義してもかまいません。QSAMを使ってアクセスする限り、DDステートメントに定義された実際のデバイスに関してはプログラムで意識する必要がありません。

DCBマクロ(Data Control Block)

データセット(SYSIN、SYSOUTも含めここではデータセットと言います)をアクセスするために必ず必要になるのがDCBです。DCBはプログラムがDFPに示すパラメーターでもあります。

DCBはデータなのでプログラム内でもデータ領域に置きます。最低限必要なのはDSORGとMACRFです。その他のパラメーターは任意ですが、DD名が固定であればDDNAMEをここで指定します。DDNAMEは省略できますがOPENするまでにはプログラムで直接セットしなければなりません。QSAMを理解し覚えるまではあまり応用編にとらわれず、基本的な使い方を通して仕組みを理解する方がいいでしょう。

まずは入力となるSYSINのDCBです。ラベル名は任意です。ここではDD名をラベルとしています。DDNAMEパラメーターでDD名を指定します。DSORGパラメーターにはPSと指定します。DSN=dsname(member)で区分データセットのメンバーをアクセスする場合でもPSです。MACRFはGMもしくはGLです。GMは移動モードで読み込んだレコードのデータをプログラム内の指定された領域にコピーします。始めはGMがわかりやすいでしょう。GLは位置付けモードを示すもので、読み込んだレコード内容をコピーするのではなく、レコード領域の先頭アドレスをGR1に返します。プログラムは、QSAM APIのバッファ内にあるレコード領域に直接アクセスします。GLの利点は、プログラム内にレコードの読み込み領域が不要になることです。READ OnlyのデータならGLが便利です。なお、先頭のGはGetの意味でこのデータセットから読み込むことを示します。
EODADパラメーターはINPUTアクセスでは必要なパラメーターです。最後のレコードを読んだ後、さらに次を読もうとすると、ここで指定したアドレスに飛んできます。そこでEOD(End Of Dataset)状態になったことがわかります。EODADを忘れると、最終レコードの次を読もうとした際にABENDさせられます。
RECFMとLRECLは任意です。JCLにもDCBパラメーターがありますが、そちらと同じものです。プログラムで指定するかJCLで指定するかです。ユーティリティーのような汎用的なプログラムでは、これらのパラメーターは指定しないのが普通です。それはどのようなRECFMやLRECLのデータセットでも受け入れるためです。しかし、アプリケーションであれば固定長レコードしか扱わない、レコード長は80でなければならないなど、プログラム側の都合を優先する場合は指定した方がいいでしょう。固定長レコード前提なのに可変長レコードのデータセットが指定されたり、レコード長80しか処理出来ないのに100バイトのレコード長のデータセットを指定されたりすれば、OPENした時にABENDします。そうしておかないと属性が合わないデータセットを指定してもOPENが通ってしまうので、プログラム内での処理がおかしくなったり、プログラムやデータ領域を破壊してしまったりします。なお、BLKSIZEは基本的に指定すべきではありません。レコード単位のアクセスですからブロック長を固定するメリットはありません。

次に出力であるSYSPRINTのDCBです。MACRFでPMを指定します。PはPutで出力、Mは移動モードのMです。位置付けモードも使えますが、最初はわかりにくいので出力では移動モードで覚えた方がいいです。PMはGMの逆で、プログラム内に用意したレコード領域の内容をそのままデータセットにレコードとして書き出します。RECFMとLRECLは出力時にはプログラム側で指定するのが一般的です。データセット側の属性にプログラムが合わせることもありますが一般的ではありません。特に、実行結果の要約等の印刷リストの場合は、プログラム側で横幅の桁数などを決めるのが普通です。ブロックサイズは指定しなければMVSが最適なサイズを設定します。RECFM=FBAは、ANSI印刷制御文字付きレコードを示し、出力レコードの先頭にプリンターの制御文字を付けるものです。改ページや改行などがあります。

OPENマクロ

OPENマクロでデータセットをオープンします。アクセスに先立ってあらかじめオープンしておかなければなりません。

第1パラメーターでDCBアドレス、第2パラメーターでモードを指定します。入力ならINPUT、出力ならOUTPUTです。
OPENでは復帰コード(成功すればRC=0)がGR15に返りますが、実際には対応するDDステートメントの定義忘れでRC=8が返るぐらいで、それ以外のエラーでは特別なエラーリカバリー処理を組み込まない限り復帰コードでは返されずプログラムはABENDさせられます。

CLOSEマクロ

OPENマクロでオープンしたデータセットをクローズします。データセットのアクセスが終了したら、あるいはプログラム終了時にはCLOSEマクロを使ってデータセットをクローズする必要があります。万一クローズし忘れたり途中でABENDなどしても、MVSはプログラム終了時に代行してクローズ処理を行います。とは言え、どうせOSがやってくれるからとあえてCLOSEを発行しないプログラムを作るのはよくありません。取ったら返す、起動したら止める、上げたら降ろす等、後始末は必ず行います。アセンブラーで作るシステム系プログラムの場合、リソースの返却漏れ、解放忘れは致命的な障害を引き起こす原因になります。やさしいプログラムや勉強で作るプログラムのうちからきちんと後始末を付ける癖をつけましょう。後始末処理は、クリーンアップ(Clean Up)処理とも呼ばれます。

第1パラメーターでDCBアドレスを指定します。第2パラメーターもありますが通常は省略します。複数のDCBを指定する場合、カンマ記号のみ指定して DCB1,,DCB2,,DCB3,,…のように記述します。
CLOSEでも復帰コード(成功すればRC=0)がGR15に返りますが、実際には対応する正しくないDCBの指定や未オープンのデータセットのCLOSEでRC=4が返ります。CLOSE処理に伴うI/O動作でのエラーなどは特別なエラーリカバリー処理を組み込まない限り復帰コードでは返されずプログラムはABENDさせられます。
なお、QSAMでレコードを書き込んだ場合、QSAM側のバッファに残っているレコードデータがあればCLOSE処理の中でデータセットに書き込まれます。

GETマクロ

データセットからレコードを読み込みます。GMモードの時は指定した領域にレコード長の長さで読み込まれます。GLモードの時は読み込んだレコードの先頭アドレスがGR1で通知されます。EOD状態の時は、DCBのEODADパラメーターで指定したアドレスへ制御が渡ります。可変長レコードの場合、レコードの先頭にはRDWが付加されていますので、それを見て実際のレコード長を求めます。固定長および不定長レコードではDCB内のDCBLRECLフィールドに読んだレコードの長さが格納されます。固定長であればプログラム側でもレコード長がわかっていますから普通のプログラムであればDCBをいちいち参照する必要はありません。DCB内のフィールドの参照については機会を改めて解説します。

第1パラメーターでDCBアドレス、第2パラメーターでレコードを読み込む領域のアドレスを指定します。GLモードの場合はDCBアドレスだけを指定します。
GR13は72バイトのレジスター退避領域をポイントしていなければなりません。GETマクロは分岐命令で呼び出されるサブルーチンAPIです。QSAMルーチン側でレジスターの退避が行われます。GETマクロから戻ってきた時、GR2からGR13はマクロ発行前と同じです。復帰コードはありません。

EODADルーチンに入ってきた時、GR2からGR13はGETマクロ発行前と同じです。GR14はGETマクロの次の命令を指しています。2番目のサンプルでは、EODADルーチンでGR1のレコードアドレスを0クリアーしてGETマクロの次の命令に飛んでいます。こうすることでメインの処理でEODを判定できます。GMモードの時は、別の領域にEODフラグなどを用意してそれをON/OFFしてもいいでしょう。入力データセットを読み終えたら処理終了となるようなプログラムなら、1番目のサンプルのように終了処理そのものをEODADルーチンにしてもいいでしょう。

PUTマクロ

データセットにレコードを書き込みます。PMモードの時は指定した領域にあるデータがレコード長の分だけ書き込まれます。可変長レコードの場合、レコードの先頭にはRDWを付加しなければなりません。

第1パラメーターでDCBアドレス、第2パラメーターで書き込むレコードが格納された領域のアドレスを指定します。
GR13は72バイトのレジスター退避領域をポイントしていなければなりません。PUTマクロから戻ってきた時、GR2からGR13はマクロ発行前と同じです。復帰コードはありません。
RECFM=FBAまたはVBAの時、レコードの先頭にはANSI制御文字(内容はサンプル参照)を置きます。特別な制御をせずに行単位にそのまま印刷する場合は、レコードの先頭に空白文字を置けばいいでしょう。VBAの場合、RDWに続いてANSI制御文字を置きます。ANSI制御文字は、JES2ライターから印刷する場合に使われます。VTAMやTCP/IP(TN3270)などで接続されたプリンターに出力する場合、ANSI制御文字がプリンター自身の制御文字に変換されるかどうかは使っているソフトウェアの仕様によります。

以上簡単ですが、QSAMによるデータセットのアクセス方法を紹介しました。アセンブラー・プログラミングを覚える過程で、データセットのI/Oのために最初に使うのがQSAMです。わかりやすく簡単なインターフェースなので、QSAMは手軽に利用できますが、実際には奥深いアクセス法でもあります。QSAMはアクセス法の中でも高いI/Oパフォーマンスを持ちます。後ろへ戻ったり、レコードをスキップして読んだりなどの細かな制御はできませんが、順次にアクセスすれば良いのなら初心者でもそれなりの高速で動くI/Oプログラムが作れます。中級レベルのスキルのプログラマーが作ったBSAMやEXCP(ブロック単位アクセス)のプログラムよりも初心者が作ったQSAMのプログラム(レコード単位アクセス)の方が恐らく性能が高いでしょう。今日では、DASDやテープ装置のユーティリティー・プログラムでない限り、プログラマーのレベルに関係無くVSAMやデータベースではない一般の順次データセットのアクセスに使われるのはQSAM一択と言っていいでしょう。

ざっとまとめるとこんな感じのサンプルになります。処理したいデータをSYSINから読んで、実行結果をSYSPRINT(SYSOUT)に出すものです。COBOLと比べても、OPENはOPEN、CLOSEはCLOSE、READがGET、WRITEがPUTとソースを書く手間は高級言語とほとんど変わりません。I/Oそのもののコードを書くこと自体はそれほどむずかしくないでしょう。
ここで解説されていないパラメーターなど、QSAMに関しての詳細はマニュアルを参照して下さい。「DFSMSデータ・セットの使用法」や「DFSMS Macro Instructions for Data Sets」が参考になります。QSAMマクロ命令のマニュアルは英文しかありませんが、IBM Documentationのサイトでは機械翻訳されるので日本語でも参照できます。