こんにちは!メインフレームの世界へようこそ。
JavaやCOBOLといったモダン、あるいはビジネス標準の言語をバリバリ書いてきた方にとって、IBMメインフレームの「PL/I(ピーエルアイ)」という名前を聞くだけで、なんだか古めかしい黒い画面と、難解なエラーメッセージのコンボが頭に浮かんで身構えてしまうかもしれませんよね。
でも、安心してください。怖がる必要は全くありません。
今回は、PL/Iのデータ制御において避けて通れない、しかし初学者が最もハマりやすい「ENVIRONMENT属性のF/FB形式におけるパディングとブロック化の挙動」について、お馴染みの他言語の概念と比較しながら、優しく紐解いていきたいと思います。
レガシー独特のルールに見えても、本質を掴めば「なるほど、そういうことね!」と腑に落ちるはずです。さあ、一緒に見ていきましょう!
—
そもそも「ブロック化」ってなに?(JavaやCOBOLとの違い)
Javaでファイルを読み書きするとき、私たちは `BufferedReader` や `FileOutputStream` を使って、1行ずつ、あるいはバイト単位でストリームを扱いますよね。COBOLでも、`FD` 記述項で `BLOCK CONTAINS` なんて書いた覚えがあるかもしれません。
メインフレーム(z/OS)の世界では、データを磁気ディスクやテープなどのストレージに保存する際、ハードウェアの効率を最大化するために、「レコード」をいくつも集めて「ブロック」という単位にまとめて書き込みます。
- 論理レコード (Logical Record): アプリケーションが1回処理するデータの単位(例:社員データ1人分)。
- 物理ブロック (Physical Block): ストレージに実際に読み書きされるデータの単位(複数の論理レコードがギュッと詰まっている)。
この「いくつまとめるか(ブロック化因子)」を制御するのが、PL/Iの `ENVIRONMENT` 属性における F (Fixed: 固定長) や FB (Fixed Blocked: 固定長ブロック化) という指定なんです。
—
F形式とFB形式、そして「パディング」の正体
では、今回の本丸である パディング(余白埋め) のお話をしましょう。
例えば、あなたが「1レコードの長さが 80バイト」のデータを扱うプログラムを書いたとします。これを「3レコードずつブロック化(FB形式)」してストレージに書き出したいとします。
このとき、1ブロックのサイズは `80バイト × 3 = 240バイト` になりますよね。綺麗に割り切れるので問題ありません。
「じゃあ、もしレコードの総数が中途半端だったらどうなるの?」
これが今回の最大のポイントです。
例えば、ファイル全体のレコード数が「7件」だったとしましょう。
3件ずつブロック化していくと…
- 1ブロック目:3件(240バイト)
- 2ブロック目:3件(240バイト)
- 3ブロック目:残り「1件」しかない!
ブロックのサイズは常に一定(この場合は240バイト)であるべきなのに、最後の3ブロック目は80バイト分しかデータがありません。さあ、残りの160バイト分はどうなると思いますか?
ここで登場するのが パディング(Padding) です。
OSやコンパイラは、最後のブロックの足りない空間に「ゴミデータ(あるいはヌル文字や特定のパターンのパディングバイト)」を勝手に埋め込んで、強制的に規定のブロックサイズ(240バイト)に仕立て上げます。これがブロック末尾のパディング処理です。
—
OSはどうやって「どこまでが本当のデータか」を知るのか?
ここで、JavaやCOBOL経験者なら誰もがこう思うはずです。
「おいおい、そんな風に余白を埋められたら、ファイルの読み込み時に『最後のゴミデータ』まで有効なレコードとして読んじゃうんじゃないの?」と。
鋭いですね!その通り、もしアプリケーションが何も考えずにブロック単位でガバッとデータを読んだら、パディングされたゴミデータまで社員データとして処理してしまい、大惨事になります。
ですが、安心してください。F/FB形式の場合、OS(z/OSのアクセス法であるQSAM)が賢く管理してくれています。
- F形式 (固定長): レコード長が完全に固定なので、JCL(Job Control Language)の `DCB=(RECFM=F,LRECL=80)` で定義されたサイズごとに、OSが機械的にスパッ、スパッとレコードを切り分けます。
- FB形式 (固定長ブロック化): `DCB=(RECFM=FB,LRECL=80,BLKSIZE=240)` のように定義されます。OSは、ブロックの先頭から `LRECL`(80バイト)ごとに区切ってアプリケーションに渡しますが、ファイル全体の論理的なレコード数(あるいはEOF)をちゃんと把握しているため、パディングされた無効な領域に達する前に「ハイ、ここまで!」と読み込みを終了させてくれます。
つまり、アプリケーション側(PL/Iのコード)は、パディングの存在をそこまで過度に恐れなくても、OSとコンパイラが裏側でいい感じに帳尻を合わせてくれるのです。
—
実践!PL/Iでのファイル定義コード例
百聞は一見にしかず。実際にPL/IでこのF/FB形式のファイルをどのように定義して読み書きするのか、サンプルコードを見てみましょう。実務のマイグレーション調査でもよく見かける典型的な書き方です。
1
/ ======================================================== /
/ ENVIRONMENT属性(FB形式)を用いたファイル入出力のサンプル /
/ ======================================================== /
SAMPLE_JOB: PROC OPTIONS(MAIN);
/ 1. レコードの構造体定義 (LRECL = 80バイトを想定) /
DCL 1 EMP_RECORD,
5 EMP_ID CHAR(5), / 社員ID /
5 EMP_NAME CHAR(25), / 社員名 /
5 FILLER CHAR(50); / 予備領域(合計80バイト) /
/ 2. ファイルの宣言とENVIRONMENT属性の付与 /
/ ENVIRONMENT(FB LRECL(80) BLKSIZE(240)) を指定し、 /
/ 3件ごとのブロック化と固定長ブロックであることを明示する /
Dcl IN_FILE FILE RECORD INPUT
ENVIRONMENT(FB LRECL(80) BLKSIZE(240));
DCL EOF_FLAG BIT(1) INIT(‘0’B);
/ ファイルのオープン /
OPEN FILE(IN_FILE);
/ ファイル終了(ENDFILE)時の条件ハンドラ /
ON ENDFILE(IN_FILE) EOF_FLAG = ‘1’B;
/ メインの読み込みループ /
DO WHILE(^EOF_FLAG);
/ 1レコードずつ読み込む /
/ ※OSとランタイムがブロック展開とパディングスキップを自動処理 /
READ FILE(IN_FILE) INTO(EMP_RECORD);
IF ^EOF_FLAG THEN DO;
/ 読み込んだデータの処理(コンソールに出力する例) /
PUT SKIP EDIT (‘ID: ‘, EMP_ID, ‘ NAME: ‘, EMP_NAME)
(A, A, A, A);
END;
END;
/ ファイルのクローズ /
CLOSE FILE(IN_FILE);
END SAMPLE_JOB;
コードのポイント解説
- `ENVIRONMENT(FB LRECL(80) BLKSIZE(240))`:
これが今回の主役です。「このファイルは固定長ブロック(FB)であり、1レコードは80バイト、1ブロックは240バイト(3レコード分)ですよ」とPL/Iコンパイラに伝えています。この宣言があるおかげで、コンパイラとOSの連携モジュールが適切な入出力制御ブロック(DCB)を構築してくれます。
- `READ FILE(IN_FILE) INTO(EMP_RECORD);`:
プログラマは、ブロック化の複雑な計算やパディングの存在を意識することなく、あたかも「1レコードずつ綺麗にファイルが並んでいるかのように」シンプルにループを回すことができます。
—
まとめ
いかがでしたでしょうか?
「ENVIRONMENT属性のF/FB形式におけるパディングとブロック化の挙動」、こうして紐解いてみると、決して得体の知れない怖いものではなく、「限られたメインフレームのストレージ容量を効率よく使うために、OSとランタイムが裏側で頑張ってくれている工夫の跡」であることが分かっていただけたかと思います。
- F/FB形式のブロック末尾には、割り切れない分のパディング(余白)が入り得る。
- でも、LRECLやBLKSIZE、そしてOSの機能によって、アプリケーションがゴミデータを読み込んじゃう心配はない。
- PL/Iでは `ENVIRONMENT` 属性でそのルールを正しくコンパイラに教えてあげればOK!
レガシーシステムの移行や保守において、こうしたストレージの物理的な仕組みを知っていると、JCLのパラメータ変更やデータ移行のトラブルシューティングで迷うことがグッと減ります。
「なんだ、PL/Iも意外と怖くないじゃん!」と思っていただけたら幸いです。
それでは、快適なメインフレーム・ライフを!
