Linuxの空間管理

空間レイアウト

32ビットで表現できる4Gバイトの空間で最初の3Gバイトはプロセス空間に、 あとの高位メモリアドレスの1Gバイトは物理メモリに直接マップされています。 Linuxカーネルは物理メモリの先頭にロードされているため、 仮想空間上は0xC0000000番地に割付いているように見えます。 Linuxカーネル空間は多重仮想化せず、このままの状態で動作します。 0x0〜0xC0000000の間に、動的に生成されるプロセスの空間は、 各プロセス毎に独立した多重仮想として生成されます。 仮想空間上では有効なページであっても, そのページに物理ページが 割り当てられてるとは限りません。ディスク上のファイルのブロックや SWAPを指していたり, 実際にアクセスがあったときに0クリアしたメモリを 割り当てる指定になっていることもあります。
1Gbyte以上の物理メモリを搭載する時は、コンフィギュレーションで この割り付けアドレスを変更することができるようになりました。 物理メモリをマッピングする仮想空間上のアドレスを示すマクロ (__PAGE_OFFSET)と、Linuxカーネルをリンクする時に指定する リンクディレクティブファイルの二箇所に情報が 反映されます。
/usr/src/linux/include/asm-i386/page.h
/usr/src/linux/arch/i386/vmlinux.lds

ページング

Linuxはページング機能を持つCPUの空間管理方式を汎用化し, 様々なCPUへの対応性を高めています。 Intel CPU版Linuxの空間管理も、全てのタスクが同じ4Gbyte空間のセグメントを 共有するよう、セグメント機能は利用せずページング機能のみで実現しています。 Linuxは三段のページテーブルまで対応できる作りになっています。 それぞれ、PGD, PMD, PTE という名前がつけられています。 Intel CPUではページテーブルは二段引きであるため、 物理的なPMDは無く、 仮想的な何もしないPMDを挟み込んでいます。 Intel CPUアーキティクチャでは、PGD,PTEは物理空間上に存在しなければなりません。 Linuxカーネルがこれらを操作する時は、カーネル空間に ストレートマップされた領域を通して行います。

カーネル空間は、単純な物理メモリのストレートマッピングで実現されています。 プロテクションなどの恩恵を受けることができないデメリットがありますが、 逆にどこからでもカーネル内の全てのデータ構造を参照でき、 またカーネルデータを指すポインタを使いまわすことが可能であるという メリットもあります。
v2.4からは2Gbyte以上の物理メモリを利用することができるようになりました。 これらのメモリは物理メモリにストレートマッピングできないため、 必要に応じて動的に仮想空間にマッピングしアクセスさます。 動的に物理メモリを仮想空間へマッピングする領域としては、 カーネル空間の最後の128Mbyteが予約されています。

プロセス空間

プロセスは各々独立した空間を持っていますが、その独立した空間、1つは、 メモリ管理構造体mm_struct、1つによって管理されています。 つまり、1つのプロセスは、1つのメモリ管理構造体mm_structを所有しています。 また、仮想空間管理構造体vm_area_structは1続きの連続した仮想空間を 管理する構造体です。プロセス空間のうち, 有効な空間は非連続な複数領域であり、 それぞれの領域に対して仮想空間管理構造体vm_area_structが割り当てられる。 メモリ管理構造体mm_structのmmapメンバには複数の仮想空間管理構造体 vm_area_structがリンクされることになります。このvm_area_structの総計が このプロセスが利用できる有効な空間です。 メモリ管理mm_struct構造体は,またPGD, PTE 領域を管理しています。 PGDとPTEは, プロセスの仮想空間アドレスを物理メモリアドレスに 変換するテーブルです。実際の変換処理はIntel CPUの場合、CPUの中にあるMMUが 自動的に行います。

仮想空間の生成は, 仮想空間管理構造体vm_area_structのアロケートと, そのvm_area_structに対応するPGD,PTEの初期化を行うことです。 プロセス空間の生成/拡張はdo_mmap関数、do_brk関数で実現しています。

do_mmap(ファイル構造体、アドレス、サイズ、プロテクション...)
{
        固定仮想アドレス指定で無い場合、空いている仮想空間を検索する。
        仮想空間管理構造体vm_area_structを確保する。
        vm_area_structを初期化、仮想空間を予約する。
        if (ファイル指定?) {
                仮想空間管理構造体vm_area_structにファイル構造体を登録
                ファイル構造体のmmapオペレーションを呼び出す。
                     (ext2fsの場合はgeneric_file_mmap関数)
        }
        可能なら隣り合ったvm同士でマージする(merge_segments関数)
}
do_brk(アドレス、サイズ)
{
        仮想空間管理構造体vm_area_structを確保する。
        vm_area_structを初期化、仮想空間を予約する。
        可能なら隣り合ったvm同士でマージする(merge_segments関数)
}
生成されたばかりのプロセス空間は、仮想空間だけは存在しますが、 PTEの中身は全て空であり、物理メモリは割り当てられていません。 生成直後の仮想空間(ページ)にアクセスをすると, まだ実際には物理メモリが 割り当てられていないため、CPU例外が発生します。そのときlinuxでは、 do_no_page関数が呼び出されます。
do_no_page(タスク、仮想空間管理構造体vm_area_struct、アドレス...)
{
        if (ファイルマップされていない領域?) {
                単純物理ページ確保とマップ(do_anonymous_page関数)
                return;
        }
        仮想空間管理構造体vm_area_structのnopageオペレーションを呼び出す。
          (ext2fsの場合は、 filemap_nopage関数)

        pteのデータを生成(mk_pte関数)
        if(書きこみアクセスか) {
                書きこみ可とする(pte_mkwrite関数)
                dirtyのビットも立てておく(pte_mkdirty関数)
        } else if (共有mmapでないが、複数のプロセスで共有している) {
                書きこみ禁止とする(pte_wrprotect関数)
                  (※これに関しては、コピーオンライトの説明を見よ)
        }
        実際のページテーブルに登録(set_pte関数)
}
メモリが不足してくると参照頻度の低いページはtry_to_swap_out関数で スワップデバイスに追い出されます。空きスワップ域を検索し、そこに 物理メモリの内容を書き込み, 物理メモリを解放します。PTEにはスワップ 先を示すインデックスを書き込んでおきます。 まだ一度も書き込みを行っていないページの場合は, スワップデバイスに 追い出さず, 単に物理メモリの解放と PTEのクリアを行います。

一度スワップアウトされたページへのアクセスが発生すると、CPU例外が発生します。 このときlinuxはdo_swap_page関数を呼び出します。 この関数では、例外を発生した空間に対するスワップ上のブロックの内容を 空き物理ページに読み込み、その物理ページをこの空間を管理するpteに登録します。

do_swap_page(タスク、仮想空間管理構造体vm_area_struct, アドレス、....)
{
        swapキャッシュを検索(lookup_swap_cache関数)
        if (swapキャッシュ上にない) {
                swapデバイスからswapキャッシュに読み込む.
                    (swapin_readahead関数、read_swap_cache関数)
        }
        swapデバイス上の領域の参照数を1減らす(swap_free関数)
        if (書き込みアクセスで、かつ共有領域でない) {
                swapキャッシュから削除(delete_from_swap_cache_nolock関数)
                書きこみ可、dirtyビットオン、でページを指すpteを作成する。
                   (pte_mkwrite関数、pte_mkdirty関数、mk_pte関数)
        } else {
                ページを指すpteを作成する(mk_pte関数)
        }
        pteを実際のページテーブルに登録(set_pte関数)
}

v2.4からはフリーページはzoneと呼ばれる複数の領域に分けて 管理されるようになりました。 すなわち、normal zone, HighMem zone, DMA zoneです。v2.2までと異なり、カーネル空間に ストレートマッピングすることのできない領域の物理メモリまで 管理できるようにするために、二つの領域を明確に別のzoneとして 分離管理しています。 カーネル空間にストレートマッピング されていない領域は HighMemと呼ばれ、アクセス前には仮想空間に マップするという処理が必要となります。 伝統的UNIXでは、通常カーネル空間に全ての物理メモリを ストレートマッピングするという手法はとっていないため、 このような区別はありません。 また、更にDMA可能領域も意識し、別のzoneとして管理しなければならないのは、 古いPCのアーキティクチャに縛られているためです。 また、v2.4からは非連続な物理メモリ領域を管理するための汎用的なコードが 追加されました。ただし、Intel+ PC/ATアーキティクチャのマシンでは 物理メモリ領域は1つのみです。

主な操作関数群