レガシーガジェット研究所

気になったことのあれこれ。

Linux Kernel ~ 物理メモリレイアウトとプロセスのアドレス変換 x86編 ~

概要

「詳解Linux Kernel」を参考にVersion 2.6.11のコードリーディングをしていく。CPUのアーキテクチャは書籍に沿ってIntelx86とする。

今回は物理メモリレイアウトとプロセスのアドレス変換について見ていく。

物理メモリレイアウト

物理メモリにはI/Oがマッピングされていたり、BIOSのデータを含んでいる場合があるため、カーネルは使用できる物理メモリマップを作成する必要がある。

カーネルは以下のページフレームを予約済みとしている。(基本的に予約済みのページフレームは動的に割り当てられたりディスクにスワップされることはない)

Linuxカーネル物理アドレスの0x00100000(1MB)以降に置かれる。その理由として以下のようなものがある。

  • ページフレーム0を使用するのBIOSでありPOST(Power-On Self-Test)中に検知したシステムのハードウェア情報を配置している。
  • 物理アドレスの0x000A0000(640KB) ~ 0x000FFFFF(1MB)までは通常BIOSが予約しておりグラフィックカードなどが内部メモリをマッピングしており、これはIBM互換機PCで有名なメモリホールである。

カーネルは起動処理の初期段階でBIOSに物理メモリの大きさを問い合わせ、machine_specific_setup()で物理アドレスマップを作成する。

BIOSが提供する物理アドレスマップの例を以下に示す。

先頭 最後 種類
0x00000000 0x0009FFFF 使用可能
0x000F0000 0x000FFFFF 予約済み
0x00100000 0x07FEFFFF 使用可能
0x07FF0000 0x07FF2FFF ACPIデータ(ハードウェアデバイスの情報を保持ている。POSTでBIOSが書き込んだもの。)
0x07FF3000 0x07FFFFFF ACPI不揮発記憶(ハードウェアデバイスのROMチップをマッピング。)
0xFFFF0000 0xFFFFFFFF 予約済み(ハードウェアによってはBIOSのROMチップをマッピング)

カーネルの物理メモリ配置を示す変数を以下に示す。

変数名 説明
num_physpages 利用できるページフレームの最大番号
totalram_pages 利用できるページフレームの総数

非連続なページフレームにカーネルを読み込んでしまうことを防ぐため最初の0x00000000 ~ 0x000FFFFF(1MB)をスキップし、0x00100000からカーネルを展開する。

プロセスのページテーブル

プロセスのリニアアドレス空間は2つに分けられる。(今回は32bit CPUで考えているため最大で使用できるメモリは4GBとする)

  • 0x00000000 ~ 0xBFFFFFFF(3GB)のリニアアドレスはユーザモード及びカーネルモードの両者共がアドレッシング可能。
  • 0xC0000000 ~ 0xFFFFFFFF(1GB)のリニアアドレスはカーネルモードのみアドレッシング可能。

プロセスがユーザモードで動作する時は0xC0000000未満(3GB = 0x00000000 ~ 0xBFFFFFFF)のリニアアドレスを使用し、カーネルモードで動作する時は0xC0000000以上(1GB = 0xC0000000 ~ 0xFFFFFFFF)のリニアアドレスでカーネルコードを実行する。またカーネルコードはデータを読み書きする際にユーザモードのリニアアドレスを空間にアクセスする。

PAGE_OFFSETマクロの値は以下のように定義されており(0xC0000000)、先ほど説明したようにカーネルが存在している場所を示している。

// include/asm-i386/page.h
#ifdef __ASSEMBLY__
#define __PAGE_OFFSET      (0xC0000000)
#else
#define __PAGE_OFFSET      (0xC0000000UL)
#endif

#define PAGE_OFFSET        ((unsigned long)__PAGE_OFFSET)

ページグローバルディレクトリの前半のエントリ(先頭768個。PAE有効時は先頭3個)は0xC0000000以下のリニアアドレスをマッピングしていてプロセス固有となっている。これに対して残りのエントリは全プロセスで同じ内容でマスターカーネルページグローバルディレクトリのエントリに対応する。

カーネルのページテーブル

カーネルカーネル自身が使用するページテーブルを管理しており、それをマスターカーネルページグローバルディレクトリと呼ぶ。マスターカーネルページグローバルディレクトリの高位部にあるエントリ(末尾から256個。PAE有効時は末尾の1個)は全プロセスのページグローバルディレクトリに対応するエントリになっており、カーネルのマスターカーネルページグローバルディレクトリに反映されることを保証している。

カーネルが使用できる0xc00000000 ~ 0xFFFFFFFFの1GBは全て使用できる訳ではなく、いくつかの種類のメモリマッピングのために128MB残しておく。よってカーネルが使用できるのは残りの896MBとなる。そしてその896MBに物理RAMの先頭から896MB分をマッピングする。

前述の128MBは非連続メモリ割り当てや固定マップ用リニアアドレスに用いられる。固定マップ用リニアアドレスはユーザモード空間でのアドレス変換のようにリニアアドレスが任意の物理アドレスに変換されるのではなく、カーネル空間でのアドレス変換のようにある一定の物理アドレスに変換されるものを指す。ただ固定マップ用リニアアドレスの場合はその物理アドレスを指定できるというところがカーネル空間のアドレス変換とは異なってくる。ポインタの関節参照では固定マップ用リニアアドレスと比べると一回余分なメモリ参照が必要となり、かつポインタ変数の有効性を確認する必要があるが、固定マップ用リニアアドレスでは当該処理が必要ではない。