はじめに
Linuxエンジニアとして10年以上やってきた人が、メインフレーム案件に入って最初に詰まる場所がある。コードの書き方でも、コマンドの違いでもない。メモリの話をしたときだ。
「このプログラム、なぜ24bitモードで動いているんですか」と聞くと、「昔からそうなっているので」という答えが返ってくる。
「ストレージ保護キーって何ですか」と聞くと、「MMUとは別の話です」と言われる。
「データスペースって共有メモリですか」と聞くと、「似ていますが違います」と返ってくる。
Linuxのメモリモデルを知っていれば知っているほど、z/OSのメモリは「似ているようで根本的に違う」と感じる。この記事では、その「違い」の正体を一つずつ解体する。
1. Linuxのメモリモデルをおさらいする
プロセスごとの仮想アドレス空間
Linuxではプロセスごとに独立した仮想アドレス空間が与えられる。64bitシステムなら理論上128TBのアドレス空間をプロセスは持つ。
Linuxプロセスの仮想アドレス空間(64bit)
0xFFFFFFFFFFFFFFFF ┬─────────────────────
│ カーネル空間
0xFFFF800000000000 ├─────────────────────
│ スタック(下方向に伸びる)
│ ↓
│ (未使用)
│ ↑
│ ヒープ(上方向に伸びる)
│ テキスト・データセグメント
0x0000000000000000 ┴─────────────────────
物理メモリへのマッピングはMMU(Memory Management Unit)とページテーブルが管理する。プロセスAのアドレス0x1000とプロセスBのアドレス0x1000は、別々の物理メモリを指す。
メモリ保護はMMUが担う
Linuxのメモリ保護はMMUが行う。ページテーブルに読み取り専用・実行不可などのフラグが設定され、違反するとSIGSEGV(セグメンテーション違反)が発生する。プロセスは他プロセスのメモリに原則アクセスできない。
// Linuxでのメモリ確保(ユーザー空間)
void *ptr = malloc(1024); // ヒープから確保
void *mmap_ptr = mmap(...); // ファイルやデバイスのマッピング
この「プロセス空間・MMU・仮想アドレス」という3点セットが、Linuxエンジニアにとってのメモリの常識だ。z/OSはこの常識が通用しない。
2. z/OSのアドレス空間は「レイヤー」が多い
アドレス空間(Address Space)という単位
z/OSでもプロセスに相当する単位はあるが、正確には**アドレス空間(Address Space)**と呼ぶ。1つのアドレス空間は最大で16EBのアドレス空間を持てる(64bitモード時)。
しかしz/OSのアドレス空間は、Linuxのようにフラットではない。以下の4つのリージョンに分かれている。
z/OSアドレス空間の構造(64bitモード)
0xFFFFFFFFFFFFFFFF ┬─────────────────────
│ 拡張プライベートエリア
│ (64bitで使える広大な空間)
0x0000000080000000 ├───────────────────── ← 2GB境界
│ 共通サービスエリア(CSA)
│ システム共有データ
│ LSQA(ローカルシステムキュー)
0x0000000001000000 ├───────────────────── ← 16MB境界(24bit上限)
│ プライベートエリア(31bit空間)
│ SUBPOOL管理領域
0x0000000000000000 ┴─────────────────────
最大の特徴が、16MB境界(24bit)と2GB境界(31bit)という2つの境界線が今も現役で存在することだ。
24bit/31bit/64bitの3モード共存という異常事態
z/OSでは、1つのシステム上に3つのアドレッシングモードが混在している。
| モード | アドレス空間 | 上限 | 登場時期 |
|---|---|---|---|
| 24bitモード(AMODE 24) | 2^24 | 16MB | 1964年〜(System/360) |
| 31bitモード(AMODE 31) | 2^31 | 2GB | 1983年〜(System/370-XA) |
| 64bitモード(AMODE 64) | 2^64 | 16EB | 2000年〜(z/Architecture) |
HLASMやCOBOLの古いプログラムは今も24bitモードや31bitモードで動いていることがある。OSが64bitだからといって、その上で動くアプリケーションが全部64bitモードを使っているわけではない。
Linuxで言えば「64bitカーネルの上に、16bitアプリと32bitアプリと64bitアプリが混在して動いている」ような状況だ。
なぜ3つのモードが今も混在しているのか
歴史的な後方互換性の結果だ。
1964年のSystem/360は24bitアドレスを採用した。当時の設計者が「16MBもあれば十分だ」と判断したのは時代の産物だ。
1970〜80年代に16MBの壁が問題になると、IBMは1983年のSystem/370-XAで31bitモード(2GB上限)を導入した。しかし24bitで書かれたプログラムの資産は膨大だった。後方互換のために24bitモードは残された。
2000年代に64bitが登場したときも同じことが繰り返された。31bitの資産を守るために31bitモードが残った。
この「捨てない」という設計方針が、今日の3モード共存を生んでいる。移行プロジェクトでは、プログラムがどのモードで動いているかを確認することが、設計の第一歩になる。
Linuxエンジニア驚愕のポイント:1つのプログラム内でビットモードが切り替わる
ここでLinuxエンジニアが最も驚く事実を伝えておく。
Linuxではプロセスは64bitなら64bitで固定だ。しかしz/OSでは、1つのプログラムの実行中にAMODEが動的に切り替わることがある。
* メイン処理は31bitモードで動いている
* ここで古い24bit専用のサブルーチンを呼び出す
BASSM 14,15 * R15のアドレスに飛びつつ、PSWのAMODEビットを
* 呼び先のAMODEに「自動切り替え」する命令
* 戻り先(R14)には現在のAMODEも保存される
* BASSM から戻ってきた時点で31bitモードに自動復帰
BASSM(Branch And Save and Set Mode)命令は、分岐しながらCPUのアドレッシングモードをハードウェアレベルで切り替える。「この関数を呼ぶ瞬間だけ24bitモードに降りて、戻ってきたら31bitに戻る」というアクロバティックな制御が、日常的に行われている。
地層を縦に移動しながら動くのがz/OSのプログラムだ。
3. 24bitモードの呪い──16MB境界問題
今も現役の24bit制約
古いCOBOLやアセンブラプログラムの中には、**AMODE 24(24bitアドレッシングモード)**で書かれたものがある。このプログラムは、16MB以下のメモリしか参照できない。
MYPROG CSECT
MYPROG AMODE 24 * 24bitモードで動作すると宣言
MYPROG RMODE 24 * 24bitのメモリに常駐すると宣言
問題は、このプログラムが「16MB以下にデータが存在すること」を前提として作られている点だ。現代のz/OSで大量のデータを16MB以下に詰め込もうとすると、容量不足になる。
GETMAINとFREEMAIN──mallocではない
z/OSでのメモリ確保はLinuxのmallocではなく、GETMAIN(またはその上位版STORAGE OBTAIN)マクロで行う。
* 24bitエリア(Below the line)から1024バイト確保
GETMAIN R,LV=1024
* 31bitエリア(Above the line)から1024バイト確保
GETMAIN RU,LV=1024,LOC=ANY
LOC=ANYが「どこでもいい(31bitエリアも可)」を意味し、LOC=BELOWが「16MB以下(24bitエリア)に限定」を意味する。
Linuxのmallocはこのような指定ができない。「メモリのどの物理的な位置に確保するか」を意識する必要がないのは、Linuxが物理位置を完全に抽象化しているからだ。z/OSでは抽象化されていない。
「Below the line」と「Above the line」
z/OSのエンジニアは「Below the line」「Above the line」という表現を使う。
Above the line(16MB境界より上)
├─ 31bitプログラムが使えるエリア
└─ 64bitプログラムが使えるエリア
─────────── 16MB境界(The Line)───────────
Below the line(16MB境界より下)
└─ 24bitプログラムが「しか」使えないエリア
(容量が限られており、奪い合いになる)
古い24bitプログラムと新しい31bitプログラムが同じシステムで動くとき、16MB以下のメモリは限られたリソースとして取り合いになる。これが「16MB境界問題」として今も設計上の制約として残っている。
4. ストレージ保護キーという概念がない世界から来ると詰まる
空間で分けるLinux vs メモリにバッジを貼るz/OS
Linuxのメモリ保護の本質は**「アドレス空間をプロセスごとに壁で分断する」**ことだ。プロセスAからプロセスBのメモリは原則として見えない。壁があるから安全が保たれる。
z/OSの設計は根本から異なる。z/OSには**CSA(共通サービスエリア)**のように、すべてのアドレス空間から「同じアドレス」で参照できる共有領域が存在する。壁がない。全員が同じ地平に居座っている。
その代わりz/OSが使う保護の仕組みが**ストレージ保護キー(Storage Protection Key)**だ。
【Linuxの保護モデル】
プロセスA プロセスB プロセスC
[空間A] |壁| [空間B] |壁| [空間C]
→ 壁でアドレス空間を分断して保護
【z/OSの保護モデル】
┌──────────────────────────────────────┐
│ 全アドレス空間から見える共通領域 │
│ ┌──────────┐┌──────────┐┌──────────┐│
│ │ページ Key:0││ページ Key:8││ページ Key:9││
│ └──────────┘└──────────┘└──────────┘│
└──────────────────────────────────────┘
→ メモリのページ1枚1枚に「鍵(キー)」を貼って保護
ストレージキーの仕組み
z/OSでは、メモリの4KBページごとに**4bitのストレージキー(0〜15)**が付いている。
物理メモリの構造(z/OS)
┌──────────────────┬──────┐
│ 4KBページ │ Key:0 │ ← OSカーネル領域(キー0が最強)
├──────────────────┼──────┤
│ 4KBページ │ Key:0 │
├──────────────────┼──────┤
│ 4KBページ │ Key:8 │ ← 一般アプリケーション領域
├──────────────────┼──────┤
│ 4KBページ │ Key:8 │
├──────────────────┼──────┤
│ 4KBページ │ Key:9 │ ← 別アプリケーション領域
└──────────────────┴──────┘
プログラムが実行されるとき、PSW(Program Status Word)にも現在の**実行キー(0〜15)**が設定される。CPUはアクセス時に「実行キー」と「ストレージキー」を照合する。
アクセス制御の原則:
実行キー == ストレージキー → アクセス許可
実行キー == 0 → すべてにアクセス可(特権)
それ以外 → S0C4(保護例外)で即死
LinuxのMMUとの対比
| Linux(MMU) | z/OS(ストレージキー) | |
|---|---|---|
| 保護の単位 | ページ(通常4KB) | ページ(4KB) |
| 保護の哲学 | 空間を壁で分断する | メモリにキーを貼り付けてCPUが照合 |
| 保護の種類 | 読み/書き/実行の可否 | アクセス許可キーの一致 |
| 特権レベル | カーネル/ユーザー空間 | キー0(OS)〜キー15(アプリ) |
| 違反時の挙動 | SIGSEGV | S0C4(プログラム例外アベンド) |
| 共有領域の扱い | mmap・shmで明示的にマップ | CSAは全空間から同一アドレスで見える |
なぜ移行設計でここが問題になるのか
COBOLやHLASMで書かれたプログラムは、ストレージキーを前提とした権限設計になっていることがある。「このデータ領域はキー8のプログラムしか書き込めない」という制御が、コードに埋め込まれている。
これをJavaやKotlinに移行するとき、同等のアクセス制御をどう実装するかは自明ではない。JVMにはストレージキーに対応する概念がなく、OSレベルのAPIや独自の権限管理層を設計しなければならない。
5. データスペースとハイパースペース──Linuxにない概念
アドレス空間の外に出る
ここまで説明したアドレス空間は、あくまで「1つのプログラム(アドレス空間)の内側」の話だ。z/OSにはこれとは別に、アドレス空間の「外側」にデータを置く仕組みがある。
データスペース(Data Space)とARモード
データスペースは、コードを持たないアドレス空間だ。データだけを格納する専用の空間を、メインのアドレス空間とは別に最大2GBまで確保できる。
通常のアドレス空間(コード+データ)
├─ プログラムコード
├─ スタック
└─ ヒープ
データスペース(データのみ)
└─ 大量のデータ(最大2GB)
↑
ARモード + ALETでアクセス
Linuxの共有メモリ(shmget/mmap)と似ているが、根本的な違いがある。Linuxの共有メモリは同じアドレス空間にマップされる。データスペースは別のアドレス空間として完全に独立している。
アクセスには**ARモード(Access Register Mode)**という、低レイヤ好きが大喜びするハードウェア機構を使う。
* データスペースの確保
DSPSERV CREATE,BLOCKS=1000,STOKEN=MYTOKEN
* アクセスリストへの登録(ALETの取得)
ALESERV ADD,STOKEN=MYTOKEN,ALET=MYALET
* ARモードでのアクセス
LAM 4,4,MYALET * アクセスレジスタ4にALETをロード
* (汎用レジスタ4と対になるアクセスレジスタ)
* CPUをARモードに切り替えると...
* L 3,0(4) という命令が
* 「AR4が示すデータスペースの、R4が示すアドレス」
* からデータを取得する動作に変わる
通常モードではL 3,0(4)は「R4が指す仮想アドレスからロード」だ。しかしCPUをARモードに切り替えると、汎用レジスタ4と対になるアクセスレジスタ4(AR4)のALETが自動参照され、「AR4が指す別アドレス空間のR4番地」からデータを引っ張ってくる。
2本のレジスタをガッチャンコして空間を跨いだアドレス指定を行う──この仕組みはOSのシステムコールを一切介さず、CPUのハードウェアとして実装されている。
ハイパースペース(Hiperspace)
ハイパースペースはデータスペースのさらに外側にある概念だ。最大2GBのデータを格納できるが、直接アクセスはできない。データをページ単位でメインの空間に「持ってくる」操作が必要だ。
ハイパースペース(直接アクセス不可)
└─ 最大2GBのデータ
↑↓ HSPSERV マクロでページ単位に移動
メインアドレス空間
└─ ページ単位でデータを入出力
「なぜこんな面倒なものが存在するのか」という疑問には、バッチ処理の歴史が答えてくれる。
ディスク(DASD)に書くと遅い。かといって通常のアドレス空間(31bitエリア)に載せるには2GBの壁がある。そこで登場したのが「中央記憶(リアルストレージ)や拡張記憶(高速フラッシュ相当)を、超高速な一時バッファとしてアプリに開放する」というアイデアだ。
Linuxで言えばtmpfsに近いが、ファイルシステムのAPIを一切介さず、ページを直接やり取りする点が異なる。DFSORTのような大量データのソート処理で、数GBの中間データをディスクに落とさずにメモリ上で高速処理するためのギミックとして設計された。
Linuxのプロセス間通信との比較
| 概念 | Linuxの対応概念 | 本質的な違い |
|---|---|---|
| データスペース | 共有メモリ(shm) | ARモード+ALETによるハードウェアアクセス、空間として独立 |
| ハイパースペース | tmpfs / 大容量スワップ | ページ単位の明示的な入出力、直接参照不可、バッチ特化 |
| CSA(共用サービスエリア) | カーネル共有メモリ | 全アドレス空間から同一アドレスで見える、ストレージキーで保護 |
移行設計でここを「Linuxの共有メモリと同じ」と誤解すると、データアクセスのアーキテクチャが根本から崩れる。ARモードとALETを前提とした処理フローは、Java/Kotlinの設計に置き換えるとき専用の抽象化層が必要になる。
まとめ:違いの本質はどこにあるか
5つの話を通じて見えてくる本質は、**「z/OSのメモリ設計は60年分の後方互換性の上に成り立っている」**という点だ。
Linuxは後方互換より「正しい設計」を優先して進化してきた。z/OSは「動き続けること」を最優先に、古い設計を捨てずに上に積み重ねてきた。
| 設計思想 | Linux | z/OS |
|---|---|---|
| 後方互換 | バージョン間で破壊的変更あり | 60年以上の後方互換を維持 |
| アドレッシング | 64bitに統一 | 24bit/31bit/64bitが共存、実行中に動的切り替え |
| メモリ保護 | MMUで空間を分断 | ストレージキーでページにバッジを貼る |
| プロセス間共有 | 共有メモリ・パイプ | データスペース(ARモード)・CSA・ALET |
| メモリ確保 | malloc/mmap | GETMAIN/STORAGE OBTAIN(LOC指定あり) |
| 高速一時領域 | tmpfs / スワップ | ハイパースペース(ページ直接転送) |
これを「古い」と表現するのは正確ではない。60年間、止まらず動き続けるために最適化されてきた設計だ。移行プロジェクトでこの違いを理解せずにLinuxの感覚で設計すると、アーキテクチャレベルで詰まる。
おわりに
z/OSのメモリモデルを理解すると、「なぜメインフレームのプログラムはこういう書き方をしているのか」が見えてくる。
24bitモードの制約、BASSMによるモード動的切り替え、ストレージキーによるキー照合、ARモードで空間を跨ぐデータアクセス、バッチ特化のハイパースペース──これらはすべて「その時代の要件に対する、その時代のベストアンサー」として存在している。