おい、最近入ってきた若手が「先輩、固定長レコードのブロック化って、結局OS側はどうやってレコードの境界を認識してるんですか? パディングって勝手に入るんですよね?」なんて聞いてきやがった。
ほう、いい着眼点だな。オンラインの画面設計やC#、Javaあたりのモダンな言語ばかり触ってきた連中には、この「ブロック化(Blocking)」と「パディング(Padding)」の概念、特にメインフレーム特有のOS(z/OS)とアクセス方式(BSAM/QSAM)が裏でこっそりやってくれるお節介の仕様は、魔術のように見えるらしい。
だがな、基幹システムのバッチ処理でデータ破損や「S013-18」「OC4」といったお馴染みのナイスなabend(異常終了)に泣かされたくなかったら、この辺りのPL/Iの`ENVIRONMENT`属性の挙動とブロック物理構造の仕組みは、骨の髄まで叩き込んでおかなきゃならん。
今日は、PL/Iにおける固定長(F形式)およびブロック化固定長(FB形式)のパディングの挙動と、OSがレコード境界をどうやって見極めているのかについて、実務の現場目線で徹底的に解説してやる。覚悟して聞きやがれ。
—
1. そもそも「F形式」と「FB形式」の裏側で何が起きているのか
まず基本の「キ」だ。PL/Iでファイルを定義するとき、`ENVIRONMENT`属性に `F` や `FB` を指定するよな。
- F(Fixed-length / 固定長レコード): 1つの論理レコード(プログラムが1回 `READ` や `WRITE` で読み書きする単位)の長さが完全に一定の形式。
- FB(Fixed-length Blocked / ブロック化固定長レコード): その固定長論理レコードをいくつか集めて、1つの物理的なブロック(DASD上のトラックやセクタに書き込む単位)としてまとめた形式。
ここで若手がよく勘違いするのが、「プログラムから書き出すレコードサイズと、ディスクに書き込まれる物理サイズが同じだと思っている」という点だ。
違うんだよ。FB形式の場合、OS(BSAM/QSAM)は複数の論理レコードをガチャンコと連結して1つの「ブロック」にしてから、DASD(直1磁気ディスクなど)へ書き込んでいる。なぜそんなことをするか? ディスクの利用効率(ギャップの削減)とI/O回数の削減のためだ。メインフレームの歴史の重みを感じる最適化機構だな。
OSはどうやってレコードの境界を認識しているのか?
ここで冒頭の疑問、「OSがレコード境界を認識する仕組み」につながる。
可変長(V/VB形式)であれば、レコードの先頭に4バイトのRDW(Record Descriptor Word)やBDW(Block Descriptor Word)という「長さを示すヘッダ情報」が付くから、OSもプログラムも「ここまでが1レコードね」と自力で判断できる。
しかし、固定長(F/FB形式)にはそんな親切な長さのヘッダは一切ついていない。
じゃあ、どうやって境界がわかるのか? 答えは単純明快。
「DCB(Data Control Block)に定義された論理レコード長(LRECL)で、物理ブロックを完全に割り算しているから」だ。
例えば、LRECL(論理レコード長)が 100バイト で、BLKSIZE(ブロックサイズ)が 1000バイト のFBファイルがあったとする。
OSは物理ブロックの先頭から 100バイト ごとに包丁でスパッ、スパッと等間隔に切り分けているわけだ。ヘッダに頼らず、算術的に境界を計算している。だからこそ、LRECLとBLKSIZEの組み合わせをJCL(DD文)やPL/Iの属性で1ミリも違わず一致させる必要があるんだ。ここがズレたら、データが盛大にズレて夜間バッチは即死する。
—
2. パディング(Padding)の正体と発生条件
さて、本題の「パディング」だ。
もし、BLKSIZEが LRECLの整数倍 にならなかったらどうなると思う?
例えば、LRECL = 80バイト なのに、うっかり BLKSIZE = 300バイト なんて指定したとする。
300 ÷ 80 = 3.75……。割り切れないよな。
こんな不整合な定義をすると、コンパイラやOSはどう処理するか。
1. コンパイル時/オープン時のエラー: 基本的にJCLのDCBパラメータやPL/Iのコードで `BLKSIZE` が `LRECL` の倍数になっていないと、ファイルオープン時(`OPEN`文実行時)に `OPEN` エラーやファイル入出力時の異常終了を引き起こすことが多い。
2. 書き込み時のパディング: システムやアクセス方式によっては、ブロックの末尾がLRECLの境界に満たない場合、勝手に余白をゼロ(X’00’)やスペース(X’40’)で埋める「パディング(Padding)」を行うケースがある。しかし、これはコンパイラやOSのバージョン、RECFMの解釈によって挙動が微妙に異なるため、「BLKSIZEは必ず LRECL の整数倍にする」というのがメインフレーム開発における絶対の鉄則なのだ。
—
3. 実践! PL/Iコードによるファイル入出力とENVIRONMENT属性の指定
口ばかりじゃつまらないからな。実際に現場で使える、F/FB形式のファイルを安全に扱うためのPL/Iサンプルコードを見せてやろう。大文字ベースの、我が輩が愛用する美しいフォーマットだ。
1
/ /
/ PROGRAM-ID: BLKTEST1 /
/ DESCRIP : FB形式ファイルの正しい定義と入出力処理のサンプル /
/ /
BLKTEST1: PROC OPTIONS(MAIN);
————————————————————;
- 1. データの構造定義(LRECL = 80バイトの固定長レコード)
————————————————————;
DCL 1 REC_IN,
5 EMP_ID CHAR(5), / 社員ID /
5 EMP_NAME CHAR(30), / 社員名 /
5 FILLER CHAR(45); / 予備領域 (合計80) /
DCL 1 REC_OUT,
5 EMP_ID_O CHAR(5),
5 EMP_NAME_O CHAR(30),
5 STATUS_O CHAR(45); / 処理ステータス /
————————————————————;
- 2. ファイル(DD名)の宣言とENVIRONMENT属性の指定
————————————————————;
/ INPUT_F : ブロック化固定長 (FB), LRECL=80, BLKSIZE=8000 /
Dcl INPUT_F FILE RECORD INPUT
ENVIRONMENT(F(80) BLKSIZE(8000) RECSIZE(80));
/ OUTPUT_F: ブロック化固定長 (FB), LRECL=80, BLKSIZE=8000 /
Dcl OUT_F FILE RECORD OUTPUT
ENVIRONMENT(F(80) BLKSIZE(8000) RECSIZE(80));
DCL EOF_FLG BIT(1) INIT(‘0’B);
————————————————————;
- 3. ONユニットによるファイル終了(ENDFILE)の制御
————————————————————;
ON ENDFILE(INPUT_F) EOF_FLG = ‘1’B;
————————————————————;
- 4. ファイルのオープン
————————————————————;
OPEN FILE(INPUT_F), FILE(OUT_F);
————————————————————;
- 5. メインの読み書きループ
————————————————————;
DO WHILE(^EOF_FLG);
READ FILE(INPUT_F) INTO(REC_IN);
IF EOF_FLG THEN
LEAVE;
/— ビジネスロジック(簡単なデータ加工)—/
EMP_ID_O = EMP_ID;
EMP_NAME_O = EMP_NAME;
STATUS_O = ‘PROCESSED NORMALLY’;
/— 出力ファイルへ書き出し —/
WRITE FILE(OUT_F) FROM(REC_OUT);
END;
————————————————————;
- 6. クローズ処理
————————————————————;
CLOSE FILE(INPUT_F), FILE(OUT_F);
PUT SKIP LIST(‘ BATCH PROGRAM NORMALLY TERMINATED ‘);
END BLKTEST1;
コードのポイント解説
- `ENVIRONMENT(F(80) BLKSIZE(8000) RECSIZE(80))`:
ここが今回のキモだ。PL/Iのコンパイラに対して、「このファイルは論理レコード長80バイト、ブロックサイズ8000バイト(つまり80×100=100レコードが1ブロックに詰まっている)」であることを明示している。
JCL側の `DCB=(RECFM=FB,LRECL=80,BLKSIZE=8000)` と完全に一致させる必要があるぞ。ここが一致していないと、先ほど言ったパディングの不整合やデータ化けの温床になる。
- `ON ENDFILE(…)`:
PL/Iの真骨頂であるONユニット(条件化割り込み処理)。ファイルの読み込みが物理的なブロックの終端、ひいてはファイル全体の終端に達したときの制御フローを美しくカプセル化している。モダン言語の例外処理とは一味違う、メインフレーム特有のスマートさがあるだろ?
—
4. 現場でありがちなトラブルと先輩からのアドバイス
最後に、保守現場で若手がやらかしがちな「パディングとブロック化に関する失敗談」を共有しておこう。
1. JCLとPL/Iソースのブロックサイズ矛盾
JCL側で `BLKSIZE=27920`(現代の3390型ディスクの最適ブロックサイズの一つだな)と指定しているのに、PL/Iの `ENVIRONMENT` 側で古いテスト時代の `BLKSIZE(400)` がハードコーディングされたまま残っていたケース。
結果、オープン時にOSとコンパイル時情報の不一致で弾かれるか、最悪の場合、誤ったブロック境界でメモリを読みに行ってS0C4(ストレージ保護例外)を引き起こす。
対策: 原則として、BLKSIZEはJCL側(あるいはSMS管理による自動割振)に任せ、PL/Iの `ENVIRONMENT` 属性では `F(80)` のようにレコード長のみを指定するか、JCLと完全に一致させること。
2. COPY書式(構造体)のLRECLズレ
コピーブック(`%INCLUDE`で取り込むレコードレイアウト)を修正して、全体のバイト数が 80バイト から 84バイト に増えたのに、`ENVIRONMENT` や JCL の `LRECL` を 80 のまま放置したケース。
後ろの4バイトが切り捨てられるか、次のレコードの先頭4バイトを食い込んで読み込むため、次レコード以降のデータがすべてゴミになる(シリアルシフトエラー)。これが一番恐ろしい。データが壊れているのに、abendせずしれっと正常終了しやがるからな。
—
まとめ
どうだ? 識別子の命名規則なんて些細なルールよりも、こうした「OSとPL/Iコンパイラが裏側でどうメモリとディスクをハンドリングしているか」という物理層の挙動を理解しているかどうかが、一人前のメインフレームエンジニアと、ただコードをコピペしているだけの素人を分ける境界線だ。
固定長レコードのパディングやブロック化の仕組みを正しく恐れ、JCLとPL/Iソースコードの整合性を完璧に保つこと。これが、今日もどこかで動き続ける日本の基幹システムを支える我々のプライドなのだ。
さて、理論はここまでだ。手を動かして、テストJCLを組んで検証してみろ。何かトラブったら、また私のところに相談に来るといい。
