x86_64アーキテクチャ

メモリアクセス(続き)

ページング(=paging)

メモリ領域を分割する方法には、セグメントの他にページングがある。セグメントが、意味のある区切りでメモリを分割したのに対し、ページングは、メモリを決まった大きさに分割する。ページングはセグメントと対をなすものというわけではなく、この二つを組み合わせて使うことができる。ただし、セグメントはOSにつきCSおよびDSをそれぞれ一つは持たなければならないのに対し、ページングの使用はオプションである。ページングを有効にするためには、CR0(コントロールレジスタ0番)のPGフラグをセット(1にする)する必要がある。

ページを用いる時、メモリーはページサイズで区切られた棚と思い、ページの内容がその棚にしまう本だと思えば良い。あるページが必要になれば、すでにメモリにある別のページを抜き取ってハードディスク等に移し(swap out)、必要なページを空いたメモリに入れる(swap in)ということをする。

種類

ページングには以下の3種類の方法がサポートされている。ページング設定用の種々のレジスタを設定することで、どの方法を使うかを選択する。

  • 32-Bit paging
  • PAE paging
  • 4-level paging
動作モード 32-Bit PAE 4-level
32-Bit o o x
64-Bit x x o

どのページング方式も、結局はリニアアドレスに対応するページを見つけることが最終的な目標だ。それぞれの方式で、どのようにページを探しているのかを示す図を以下に載せる。図の詳しい意味は、以降の説明を読めば(少なくとも4-levelページングに関しては)ある程度わかるはずだ。 f:id:babyron64:20171222224007p:plain この図は僕が書いたのだが、恥ずかしながら訂正がある。時間ができたら、図を直すつもり。

  1. 右端のアドレスはEffective addressでなく、Physical addressだ。
  2. Page-directory-pointer tableから、1-GByte pageへの矢印の付け根に、[38 : 30]の記載が必要だ。

以降は、64-Bitモードで動作する4-level pagingについて主に解説する。

アドレス変換

プロセッサが扱うアドレスには3種類あることは先に述べた。そして、そのうちの論理アドレスからリニアアドレスにどう変換されるのかも述べた。ここでは、リニアアドレスから物理アドレスにどう変換されるのかを説明する。

ページングが無効になっているときは、リニアアドレスがそのまま物理アドレスである。一方、ページングが有効であるときは、ページング機構(=paging structures)を使って、リニアアドレスを物理アドレスに変換する。64-Bitモードにおいて、リニアアドレスは64bitに拡張されているものの、ページングで使えるリニアアドレスは48bitに制限されている1。また、物理アドレスは52bitである。 f:id:babyron64:20171222235054j:plain f:id:babyron64:20171222234208j:plain ページング機構エントリの例として、4-levelページングのPDEを示した(PDE: page-directory entry)。4-levelページングでページサイズが4KByteの場合、リニアアドレスの上位9bitずつ、計3回の参照を経てページ本体の情報が保持されているエントリにたどり着く(ここでいう参照は、各ページング機構のインデックス)。具体的には、

  1. PML4 table
  2. Page-directory-pointer table
  3. Page directory
  4. Page table

という順で参照を辿っていく。ページの参照に使用されなかった、リニアアドレスの下位ビット(4KByteページの時は、下位12bit)は、ページ内でのオフセットである。つまり、リニアアドレスは4KByteページの時、

[インデックス(9bit)][インデックス(9bit)][インデックス(9bit)][オフセット(12bit)]

という計48bitの構成であり、このリニアアドレスが指す物理アドレスは、

物理アドレス=ページのベースアドレス+オフセット

で計算される。

ページサイズにはその他にも、1GByteおよび2MByteがある。ページング機構エントリのPSフラグがセットされているとき、そのエントリはページ本体の情報を持っている(ie. 参照ではない)。そのようなエントリがあれば、そこで参照を辿るのをやめ、使わなかったリニアアドレスの下位ビットをオフセットとして使う。この仕組みを用いて、1GByteや2MByteページ用の大きなオフセット用領域をリニアアドレス内に確保している。物理アドレスの計算方法は、先ほどの場合と同じである。

ページ機構のエントリポイント、すなわち、4-levelページングの場合でいう、PML4 tableのアドレスは、CR3レジスタに入っている(キャッシュの項にCR3レジスタのビット割り当て例がある)。そして、このことは、CR3レジスタの値を書き換えることで、ページ機構をまるまる別のものに入れ替える事ができることを意味する。実際にOSは、プロセス毎に仮想アドレス空間(ie. リニアアドレス)を用意するためにこの仕組みを用いている。プロセス毎にページ機構を用意して、プロセスの切り替えのたびにCR3レジスタを書き換えているのだ。こうすることで、アプリケーションから見れば(ie. 論理アドレスでは)同一のアドレスのメモリを、アプリケーション毎に別のページで保持する事が可能になる。

スワッピング(=swaping)

コンピュータが搭載している主メモリには限りがあるため、全てのアプリケーションのメモリ領域を主メモリに展開しておくことはできない。そのため、OSはデータをページ単位で管理し、主メモリ上にないページが必要になったが主メモリが空いていない場合、主メモリ上にあるがさしあたり不必要なページと「差し替える」。ページを差し替える時は、まず、メモリ上のページをハードディスク等に移し、ハードディスク上のページを主メモリの開いた部分に移す。メモリ上のページをハードディスク等に移すことをスワップアウト、ディスク上のページをメモリ上に移すことをスワップインという。そして、このようにスワップイン・スワップアウトを用いて、有限な主メモリ領域を有効活用する手法をスワッピングという。以下の説明では、ページングが有効になっているものとする。

アプリケーションがデータを要求すると、そのデータが格納されているページの方法を保持しているページング機構が割り出されることは前述した。そのページング機構は、対応するページがメモリ上にあるかどうかということをPフラグで示している。Pフラグがセットされている時、そのページはメモリ上にある。一方、Pフラグがセットされていない時はページはメモリ上にない。Pフラグがセットされていないページング機構にアクセスしようとすると、page fault例外が送出される。このとき、現在参照しているページング機構はCR2レジスタに保存される。OSはこの例外をハンドル(=handle)し、CR2レジスタを参照してスワッピングを行う。

キャッシュ(=cache)

リニアアドレスの変換に、いちいちページング機構を辿るのは非効率であるので、一度辿ったルートはTLBにキャッシュされる(TLB: translation lookaside buffer)。具体的には、リニアアドレスの参照に用いた上位ビット(=page number)と、対応する物理アドレスの上位ビット(=page frame)の対応表が保存される。物理アドレスの上位ビットだけキャッシュすれば良い理由は、ページがページサイズに従って境界決め(=align)されているため、リニアアドレスのオフセットに、物理アドレスの上位ビットを連結させると、それが物理アドレスになるからだ。また、そのページのアクセス権もキャッシュされるのだが、経由したページ機構のエントリのうちで、最もアクセス権限が厳しかったものがキャッシュされる。

ページ機構が別のものに置き換わる(CR3レジスタが変更される)と、キャッシュは削除される。同じリニアアドレスに、違う物理アドレスがマップされていることが起こり得るからだ。

f:id:babyron64:20171222232000j:plain CR3レジスタの変更のたびにキャッシュが削除されるのは非効率だ。そこで、4-levelページングについてはPCIDという仕組みが用意されている(PCID: process-context identifiers)。PCIDを有効にするためには、CR4レジスタの、PCIDEフラグをセットする。PCIDEフラグがセットされると、CR3レジスタの[11:0]がPCIDとして使用される。TLBキャッシュは、PCIDと紐づけられて保存され、CR3が変更されると、対応するPCIDと紐づけられているTLBキャッシュが使われる。PCIDEがセットされていないときは、PCID=0が使われる。

ページ機構のGフラグがセットされている場合は、キャッシュがglobal TLBという特別な場所に作られ、CR3の変更後も変わらず保持される。また、Gフラグがセットされているページ機構を参照した場合は、普通のTLB(ie. 現在のPCIDに紐づいているTLB)ではなく、global TLBからキャッシュが取得される。この仕組みは、複数のページ機構で同じ物理アドレスを参照する場合に有効だ。

とりあえずここで止めておこう。ページについては、プロテクションについての記事で詳しく掘り下げるつもりだ。僕が思うに、プロテクションこそが一番面白い。さぁ、つぎはそのプロテクションだ。

参考

どのようなページ機構が使えるのかは、プロセッサや、その動作モードによって様々である。そのため、Linuxでは様々なページ機構に対応できるように、ページ機構を抽象化して使っている。詳しくは、『Linuxカーネル詳細』を参照。

参考文献

  • Intel developer's manual vol-3 (2016)

    記事中のFigureおよびTableは、このマニュアルからとった。また、記事の内容も、このマニュアルの解釈および要約を多く含む。

  • 詳細Linuxカーネル 第3版

    スワッピングの手順について参考にした。

補足

1: これは、実装による。だが、64-Bitにおけるリニアアドレスは普通48bit。差分の上位16bitは、全て0にするか、すべて1にしなければならない。これを満たしたリニアアドレスをcanonicalなリニアアドレスという。CPUID.800000008H:EAX[15:8]を参照すれば、現在の実装におけるリニアアドレスの制限がわかる。