Linuxカーネルのメモリ管理
Linuxカーネルは、モノリシックカーネルとして設計されており、カーネルの主要なサービスやドライバは同じアドレス空間で実行される。このアドレス空間は仮想的であり、カーネルスペースとして知られる。
1. 仮想メモリの導入
モダンなオペレーティングシステムのほとんどは、物理的なメモリとは独立した仮想アドレス空間を使用して動作する。Linuxカーネルも例外ではなく、仮想アドレスを通じて物理メモリにアクセスする。
2. メモリマッピング
物理アドレスと仮想アドレスは、ページテーブルというデータ構造を用いて関連付けられる。この関連付けを効率的に行うためのハードウェアコンポーネントとしてMMU(Memory Management Unit)が活用される。
3. カーネルスペースとユーザースペース
Linuxでは、メモリアドレス空間は大きく2つに分かれる: カーネルスペースとユーザースペース。カーネルスペースは、OSのコア機能を実行するための専用領域。ユーザースペースは、ユーザープロセスがそれぞれ独立して使用する領域。
4. メモリ管理の利点
仮想的なアドレス管理によるオーバーヘッドは存在するが、以下のような利点が得られる:
- メモリの効率的な利用
- プロセス間の隔離と保護
- 物理メモリの断片化の軽減
5. カーネルのアドレス空間とプロセスのアドレス空間の確保・動作の流れ
1. カーネルのアドレス空間の確保と動作の流れ
-
起動時のマッピング: システムが起動するとき、ブートローダーはカーネルをメモリにロードし、初期ページテーブルをセットアップします。これにより、カーネルが使用するアドレス空間が最初に確保されます。
-
Direct Mapping: Linuxカーネルは物理メモリのすべてを仮想アドレス空間の特定の範囲に直接マッピングします。これにより、カーネルは物理アドレスに直接アクセスできるようになります。
-
静的な配置: カーネルのコード、データセグメント、初期のスタックなどは、起動時に静的に配置されます。これらのセグメントはカーネルのライフサイクル全体で変わることはありません。
-
動的メモリの確保: カーネルは動的にメモリを確保する必要がある場面で、例えばページキャッシュやslabアロケータなどのメカニズムを使用してメモリを確保します。
2. プロセスのアドレス空間の確保と動作の流れ
-
プロセスの生成: 新しいプロセスは
fork()
やexec()
などのシステムコールによって生成されます。fork()
は親プロセスのアドレス空間をコピーし、exec()
は新しいアドレス空間をセットアップして新しいプログラムをロードします。 -
ページテーブルの作成: 新しいプロセスが生成される際に、新しいページテーブルがセットアップされます。このページテーブルは、プロセスの仮想アドレスを物理アドレスにマッピングします。
-
セグメントの配置: プロセスのアドレス空間は、テキスト(コード)、データ、ヒープ、スタックのようないくつかのセグメントに分かれています。これらはプロセスが実行される際に配置されます。
-
動的メモリの確保: プロセスは、
malloc()
やnew
などの関数を使用してヒープ上に動的にメモリを確保します。また、関数の呼び出しに伴うスタックの成長も動的に行われます。
主な違い
-
起動と配置: カーネルはシステムの起動時にメモリにロードされ、静的な配置が行われます。一方、プロセスは動的に生成され、アドレス空間が実行時にセットアップされます。
-
アクセス範囲: カーネルは物理メモリ全体にアクセスする能力がありますが、プロセスはそのアドレス空間内の仮想アドレスにのみアクセスできます。
-
メモリ管理の手法: カーネルは特定のメモリ管理のメカニズム(例: slabアロケータ)を使用してメモリを確保しますが、プロセスはヒープやスタックを使用してメモリを確保・解放します。
6. ユーザースペース(プロセス)のアドレス空間レイアウト
Linuxや他の多くのオペレーティングシステムにおけるアドレス空間は、プロセスがメモリにアクセスするための仮想的な領域を指します。この仮想的なアドレス空間は、物理的なRAM上の実際の位置とは独立しています。以下に、ユーザースペースにおけるアドレス空間の主要な特徴を説明します。
1. 仮想メモリ:
- 各プロセスは、それ自体が専用のメモリアドレス空間を持っているという観点から実行されます。これにより、一つのプロセスが他のプロセスのメモリ領域に干渉することなく、安全に動作できます。
2. アドレス空間のレイアウト:
プロセスのアドレス空間は、以下の主要なセクションに分けられます。
- テキストセグメント:この領域にはプログラムの実行可能コードが配置されます。
- データセグメント:静的変数やグローバル変数が配置される領域。
- ヒープ:動的に確保されるメモリ領域。例えば、C言語のmalloc()やC++のnewによって確保されるメモリはここに配置されます。
- スタック:関数の呼び出しやローカル変数のためのメモリ領域。関数が呼び出されるたびに、その情報がスタックにプッシュされ、関数が終了するとポップされます。
- メモリマップトセグメント:ファイルをメモリにマッピングするときに使用される領域。
3. メモリ保護:
ページテーブルとCPUの機能を組み合わせることで、各プロセスのアドレス空間が他のプロセスやカーネル空間から隔離されていることを保証します。
4. アドレス空間のスイッチ:
OSがコンテキストスイッチを行う際、ページテーブルもスイッチされ、新しいプロセスのアドレス空間がアクティブになります。
わかりにくいので具体例
Aliceは、彼女のLinuxベースのコンピュータで複数のアプリケーションを同時に実行しています。彼女はWebブラウザを開いて情報を検索しながら、テキストエディタでレポートを書いています。彼女のOSはマルチタスクをサポートしているので、これらのアプリケーションは同時に動作しているように見えます。
この例では、Webブラウザのプロセスと、テキストエディタのプロセスが、高速に切り替わって(switchして)いる。