プログラムを論理的な単位(セグメント)に分けてメモリ管理する仮想記憶の方式。
セグメンテーション方式とは、プログラムを論理的に意味のある単位(セグメント)に分けて管理する仮想記憶の方式です。コード部(命令の並び)・データ部(変数などのデータ)・スタック部(関数呼び出しの一時領域)といった役割ごとに区切ります。各セグメントは可変長です。
身近な例で考えると、本を章ごとに分けることに似ています。第1章・第2章…と内容のまとまりで区切り、章の長さはそれぞれ違います。機械的に「10ページずつ」と切るのではなく、意味のある区切りで分けるのがポイントです。
上のツールで▶ボタンを押すと、プログラムを論理単位のセグメントに分け、可変長のままセグメントテーブルで物理位置を管理し、アドレスを変換する流れを確認できます。
ページングとセグメンテーションは、どちらも分割して管理しますが、分け方の考え方が正反対です。
| 項目 | ページング | セグメンテーション |
|---|---|---|
| 分割の単位 | 固定サイズ(機械的) | 論理的な意味(可変長) |
| 論理的な意味 | なし | あり(コード/データ等) |
| 外部断片化 | 起きない | 起こりうる(可変長のため) |
ページングは固定サイズで機械的に分割するため論理的な意味はなく、外部断片化が起きません。セグメンテーションは論理的な意味の単位で可変長に分割するため、長さがまちまちで、割り当てと解放を繰り返すと外部断片化が起こりうる点が違いです。上のツールのSTEP3で、可変長ゆえに大きさがバラバラな様子を確認できます。
セグメンテーションには次のような利点があります。
・保護がしやすい:論理単位ごとに「読み取り専用」「実行のみ」といった権限を設定でき、誤った書き換えを防げます。
・共有がしやすい:共有ライブラリ(=複数のプログラムで使う共通部品)のように、同じセグメントを複数のプログラムで使い回せます。
・プログラム構造に自然に対応:もともとプログラムが持つコード・データ・スタックといった構造に沿って分けられます。
一方で可変長ゆえに外部断片化が起こりうる弱点もあります。そこで、セグメンテーションとページングを組み合わせるセグメントページング方式もあります。論理的な意味で大きくセグメントに分け、そのセグメントを内部では固定サイズのページで管理することで、両者の長所を活かす考え方です。上のツールの最終ステップで、論理単位での管理が物理アドレスに変換される様子を確認できます。
なぜページングだけでは不十分なのか。ページング(=メモリを固定サイズのかたまりで管理する方式)は断片化を防ぐ優れた仕組みですが、プログラムの内容(コード・データ・スタックなど)を無視して機械的に切ってしまうという弱点があります。
たとえばコード部(命令の並び)とデータ部(変数の値)が同じページに混在してしまうと、「コード部は読み取り専用にしたい」「データ部だけ共有したい」という管理がページ単位では難しくなります。そこでセグメンテーションの出番です。
・コード部(命令の並び):プログラムが動く手順が書かれた部分。変更されると困るので読み取り専用が理想
・データ部(変数などの値):実行中に読み書きするデータ。サイズがプログラムごとに異なる
・スタック部(一時領域):関数を呼び出すたびに積まれ、終わると解放される一時的な記憶
役割が違う部分を論理的に分ければ、それぞれ異なるルールで管理できるのがセグメンテーションの本質です。身近な例で言えば、本を「第1章・第2章」と章ごとに分けることで、「第1章だけを他の人に貸す」「第3章だけ書き換える」という細かい操作ができるのと同じです。
セグメンテーションでのアドレス変換(=仮想的な番地を実際の番地に換算すること)は、「どのセグメント番号か」と「そのセグメントの先頭から何番目か(オフセット)」の2つで行います。
手順は3ステップです。
・① セグメント番号でテーブルを引く ― セグメントテーブル(=各セグメントが物理メモリのどこにあるかを記録した一覧表)から、該当セグメントの先頭アドレスと長さを取り出す
・② 範囲チェック ― オフセットがセグメントの長さ内に収まっているか確認。超えていれば「領域外アクセス」として止める(他のプログラムの領域に踏み込まない保護)
・③ 物理アドレス = 先頭アドレス + オフセット ― これが実際のメモリ番地
身近な例で考えると、「第2章(セグメント番号)の50ページ目(オフセット)を読みたい」とき、まず目次(セグメントテーブル)で「第2章は全体の何ページから始まるか」を調べ、そこに50を足した実際のページを開く、というイメージです。上のツールのSTEP5以降でこの変換を1ステップずつ確認できます。