Mads が指摘したように、null ポインターを介してほとんどのアクセスをキャッチするために、Unix ライクなシステムはアドレス 0 のページを「マップされていない」ようにする傾向があります。したがって、アクセスはすぐに CPU 例外、つまりセグメンテーション違反を引き起こします。これは、アプリケーションを不正な状態にするよりもはるかに優れています。ただし、例外ベクトル テーブルは、少なくとも x86 プロセッサでは任意のアドレスに配置できます (そのための特別なレジスタがあり、lidt
オペコードがロードされます)。
開始点アドレスは、メモリの配置方法を説明する一連の規則の一部です。リンカーは、実行可能なバイナリを生成するときに、これらの規則を知っている必要があるため、変更される可能性は低くなります。基本的に、Linux のメモリ レイアウト規則は、90 年代初頭の Linux の最初のバージョンから継承されています。プロセスは、いくつかの領域にアクセスできる必要があります。
- コードは、開始点を含む範囲内にある必要があります。
- スタックが必要です。
- ヒープが必要で、システム コール
brk()
とsbrk()
システム コールで制限が増加します。
mmap()
共有ライブラリのロードなど、システム コール用の余地が必要です。
現在、ヒープは、カーネルが適合すると判断したアドレスでメモリのチャンクを取得malloc()
する呼び出しによってサポートされています。mmap()
しかし、以前の Linux は以前の Unix に似たシステムのようであり、そのヒープは 1 つの途切れのないチャンクに大きな領域を必要とし、アドレスの増加に合わせて大きくなる可能性がありました。したがって、規則が何であれ、コードを詰め込み、下位アドレスに向かってスタックし、特定のポイントの後にアドレス空間のすべてのチャンクをヒープに与える必要がありました。
しかし、通常は非常に小さいスタックもありますが、場合によっては非常に劇的に大きくなる可能性があります。スタックが減少し、スタックがいっぱいになったときに、一部のデータを上書きするのではなく、プロセスが予測どおりにクラッシュすることを本当に望んでいます。そのため、スタックには広い領域が必要で、その領域の下端にはマップされていないページがありました。そして見よ!null ポインターの逆参照をキャッチするために、アドレス 0 にマップされていないページがあります。したがって、最初のページを除いて、スタックがアドレス空間の最初の 128 MB を取得することが定義されました。これは、コードが 0x080xxxxx のようなアドレスで、これらの 128 MB の後に移動する必要があることを意味します。
Michael が指摘するように、128 MB のアドレス空間を「失う」ことは大した問題ではありませんでした。なぜなら、アドレス空間は実際に使用できるものに関して非常に大きかったからです。当時、Linux カーネルは単一プロセスのアドレス空間を 1 GB に制限していましたが、これはハードウェアで許可されている最大 4 GB を超えており、大きな問題とは見なされていませんでした。