link もっと前
   2008年 5月 2日 -
      2008年 5月 2日  
link もっと後

link 未来から過去へ表示(*)
link 過去から未来へ表示

日々

link permalink

ページテーブルはどこに行った

Linux のページディレクトリを見ていると 0xc000 0000 以降(※)はページディレクトリエントリがあるのに、その先のページテーブルがありません。なぜこれで動くんでしょう?

調べてみると、なるほど、トリックがありました。

Linux は CR4 の PSE ビット(Page Size Extension、ビット 4)を 1 にして、4MB/4KB どちらのサイズでも使えるようにしています。

確かめ方は、右 Alt + ScrLock を押してレジスタダンプすると簡単でしょうか?以下のようなメッセージが出るはずです。Pentium Pro から導入されているはずですので、未対応というのはそうそうないかと…。

レジスタダンプの例
Pid: 0, comm:              swapper
EIP: 0060:[<c01019bd>] CPU: 0
EIP is at default_idle+0x31/0x59
 EFLAGS: 00010246    Not tainted  (2.6.18-6-486 #1)
EAX: 00000000 EBX: 00000800 ECX: c1101040 EDX: c030e000
ESI: 00099100 EDI: c0302800 EBP: 003a7007 DS: 007b ES: 007b
CR0: 8005003b CR2: c02bd7f4 CR3: 07175000 CR4: 00000690
 [<c0101a1c>] cpu_idle+0x37/0x4c
 [<c03105fa>] start_kernel+0x270/0x272

肝心なのは CR4 レジスタの値です。CR4 レジスタが 0x0000 0690、2進数だと 0110 1001 0000 ですから、ビット 4(ビット 0 から数えるので、右から 5番目)の PSE ビットが 1 になっていることが分かります。

(※)カーネルが使うリニアアドレス用のマッピングで、物理メモリ先頭 896MB 分を仮想アドレス 0xc000 0000 へマップしています。
物理: 0x0000 0000 .... 0x37ff ffff
リニア: 0xc000 0000 .... 0xf7ff ffff
というマッピングです。

二種類のページ

じゃあ 4MB と 4KB の区別はどこでやってるのさ?というと、ページディレクトリテーブルの要素である、ページディレクトリエントリでやっています。インテルのマニュアルによれば、ページディレクトリエントリの構成は以下のようになっています。


32ビット x86 のページディレクトリエントリ

細かい説明は後述しますが、PS ビット(Page Size、ビット 7)が 1 になっていれば 4MB のページを指し、0 ならば 4KB のページを使うことを意味します。

128MB の実メモリを割り当てた仮想マシン上で Linux を起動して、適当なプロセスのページテーブルをダンプしてみると、768 要素目から以下のようなページディレクトリエントリが見受けられます。

128MB しか実メモリがないので、768〜799 番目(4MB x 32ページ)までしか値が埋まりません。余った 800 番目以降は別の用途に使われます。

カーネルのページディレクトリテーブルの一部
  768: 0x000001e3, 0x004001e3, 0x008001e3, 0x00c001e3, 
  772: 0x010001e3, 0x014001e3, 0x018001e3, 0x01c001e3, 
  776: 0x020001e3, 0x024001e3, 0x028001e3, 0x02c001e3, 
  780: 0x030001e3, 0x034001e3, 0x038001e3, 0x03c001e3, 
  784: 0x040001e3, 0x044001e3, 0x048001e3, 0x04c001e3, 
  788: 0x050001e3, 0x054001e3, 0x058001e3, 0x05c001e3, 
  792: 0x060001e3, 0x064001e3, 0x068001e3, 0x06c001e3, 
  796: 0x070001e3, 0x074001e3, 0x078001e3, 0x07c001e3, 
  800: 0x00000000, 0x00000000, 0x07baf067, 0x00000000, 

772 番目を例に取ると、0xc100 0000〜0xc13f ffff の 4MB 分のアドレスをマップしています。0x010001e3 は 2進数だと 0001 0000 0000 0000 0001 1110 0011 ですので、先ほどの図に当てはめてみると、

ビット 0: P(存在): 1
4KB ページの時はページテーブルがロードされていることを表します。4MB ページの場合はページが存在していることを表します。
ビット 1: R/W(読み取り/書き込み): 1
0 は読み取り専用、1 なら書き込み可能であることを表します。
CR0 の WP ビット(書き込み保護、ビット 16)を 1 にすると、スーパバイザからユーザの書き込み禁止ページに書き込んだときに、ページフォルト例外が発生するようになります。コピーオンライト機能の実現に役立ちます。
  • ページテーブルエントリ EA が指すページ A をコピーせよという命令に対し、実際はコピーせず、エントリ EA を読み取り専用にし、さらに同じページを指すエントリ EB を読み取り専用で作ります。
  • 読み取り時
    • エントリ EA の指すページ A への読み取りは今まで通りです。
    • エントリ EB は書き込みがない限りページ A と同じ内容なので、読み取りだけならばエントリの指すページを変更する必要はありません。
  • 書き込み時
    • エントリ EA の指すページ A に書き込もうとすると、ページフォルト例外を発生させてページをコピーし、ページ A' を作ります(書き込み時のコピー、コピーオンライト、COW とも)。
      以降はエントリ EA は書き込み可能、指す先はページ A' です(EA -> A', EB -> A)。
      そうしないとエントリ EB が指すページ A を使っている人が混乱します。エントリ EB を使っている人から見ると、何もしていないのに(エントリ EA の指すページ A への書き込み発生時に)勝手にページの内容が変わったように見えます。
    • 同様にエントリ EB の指すページ A に書き込もうとしても、ページフォルト例外が発生します。そのとき初めてページ A の複製、ページ B を作ります(コピーオンライト)。
      以降、エントリ EB は書き込み可能、指す先はページ B に変更されます。(EA -> A, EB -> B)
      ただし既にエントリ EA がページ A を指していないことが分かっているなら、コピー作業は無駄なのでしません。エントリ EB はページ A を指したままで、書き込み可能に変更されるだけです(EA -> A', EB -> A)。
ビット 2: U/S(ユーザ/スーパバイザ): 0
0 はスーパバイザ用の領域を意味し、1 はユーザ/スーパバイザ用の領域を意味します。
ビット 3: PWT(ページライトスルー): 0
0 ならページへの書き込み時に、可能ならキャッシュのみを更新(ライトバック方式)します。1 にするとキャッシュとメモリを同時に更新(ライトスルー方式)します。PCD が 1 のときは、そもそもキャッシュを使わないので無意味です。
  • CR0 の NW ビット(非ライトバック、ビット 29)が 1 だと、ライトスルー方式が採用されます。こちらが優先です。
  • CR3 の PWT ビット(ページライトスルー、ビット 3)が 1 だと、ライトスルー方式が採用されます。これはページディレクトリエントリだけのキャッシュ方式に適用されます。
しかし MTRR の設定とも関係します。ライトバックとライトスルーの設定がかち合ったときは、ライトスルーが優先されます。
ビット 4: PCD(ページキャッシュ無効): 0
0 にするとページをキャッシュします。1 にするとページをキャッシュしなくなります。I/O メモリなど、キャッシュしても意味がない領域などに使うそうです。
  • CR0 の CD ビット(キャッシュ無効、ビット 30)が 1 だと、メモリに対するキャッシュ全てが無効になります。こちらが優先です。
  • CR3 の PCD ビット(ページキャッシュ無効、ビット 4)が 1 だと、ページ単位のキャッシュは無効になります。これはページディレクトリエントリだけのキャッシュ方式に適用されます。
キャッシュの設定は MTRR の設定とも関係します。ライトバックとライトスルーがかち合ったときは、ライトスルーが優先されます。
ビット 5: A(アクセス): 1
ページ番号が指しているページに読み込み、書き込みがあると 1 にセットされます。
ビット 6: D(ダーティ): 1
ページ番号が指しているページに書き込みがあると 1 にセットされます。
ビット 7: PS(ページサイズ): 1
0 なら 4KB ページ、1 なら 4MB ページを表します。ページディレクトリエントリ(一段目のテーブル)だけで有効です。
Linux ではページテーブル(二段目のテーブル)でアクセス権のないページを表すために、P ビットと合わせて使っています(当然 CPU は無視します)。P:PS = 0:0 なら存在しないページ、P:PS = 0:1 ならアクセス権のないページを表します。
(4KB ページ、ページテーブルエントリ(二段目のテーブル)のみ)Pentium Pro 以降であれば、このビットは PAT ビットを表します。このビットが 1 であれば PCD および PWT の 2ビットで 4 つのメモリ属性(ライトバック、ライトスルー、MTRR 優先のキャッシュ無効、MTRR 上書きのキャッシュ無効)を表します。このビットが 0 であれば、従来と同じです。
ビット 8: G(グローバル): 1
0 なら TLB フラッシュの時に消されます、1 なら消されずにずっと残ります。
CR4 の PGE ビット(グローバルページ有効、ビット 7)を 1 にしないと意味がありません。
ビット 12: PAT(ページ属性テーブル): 0
(4MB ページのみ)0 なら PAT 機能を無効にして、1 なら PAT 機能を有効にします。
ビット 31-22: ページ番号: 4(0x4)
先頭から 16MB(4MB x 4個目)の位置にある 4MB 分の領域(0x0100 0000〜0x013f ffff)をマップすることを表します。

なぜ使い分けるのか

4MB のページングを使っているのはカーネル用のリニアアドレス(0xc000 0000〜)だけのようです。他のページディレクトリエントリには PS ビットが設定されていません。ページ番号の指す先には、もう一段ページテーブルがあり、その先にやっと 4KB のページがあります。

理由ですが、インテルのマニュアルによると 4MB と 4KB のページは別の TLB に置かれるので、カーネルのように頻繁に使うコードやデータは 4MB ページに置いておくと、フラッシュされなくて良いよ、ってなことが書いてあります。また、ページサイズが大きければ TLB ミスも少ないよ、ってな解説もあります。恐らくそんな理由です。


あるプロセスのページディレクトリテーブル

絵にしてみるとこんな感じでしょうか。間違ってるかも…。

サイズはアドレスの範囲を意味するのではなくてテーブル自体の大きさのことです。ページディレクトリテーブル(一段目のテーブル)も、ページテーブル(二段目のテーブル)も 4バイト x 1024 要素なので同じ 4KB ですよ、って言いたかっただけです。

[編集者: すずき]
[更新: 2008年 5月 3日 19:20]
link 編集する

コメント一覧

  • hdk 
    あれ? PAT は? (・∀・)

    A ビットは書き込みがあっても 1 になりますね。A, D ビットは実際は TLB とも深く関わってます。R/W と CR0 の WR フラグも関係があります。 
    (2008年05月03日 14:11:03)
  • すずき 
    PAT に MTRR ですか…正直、勘弁してくださいという領域ですorz
    良くわからんくせに、書き足しました。内容には全く自信ないです。 
    (2008年05月03日 16:23:33)
  • hdk 
    MTRR ってわけわからんですよねー。
    たしか CR3 のキャッシュの設定って CPU がページディレクトリエントリを読み書きする時にキャッシュの設定をどうするかじゃなかったですか?  
    (2008年05月03日 18:07:45)
  • すずき 
    >MTRR
    マニュアル読んだけど理解させる気ないね。難しすぎ。
    >CR3
    うお、早速間違ってた。直します。 
    (2008年05月03日 19:19:30)
  • すずき 
    それにしても読みづらい日記だな…。
    hdk氏には感謝感謝。 
    (2008年05月03日 19:22:14)
  • すずき 
    かなりいまさらになりますが…日記でやってるページテーブルダンプのやり方(bash, zsh 専用)
    # i=0x6e94000; dd if=/dev/mem skip=$((i / 4096)) bs=4096 count=1 | hexdump -v -e '" " 4/4 "0x%08x, " "\n"' | nl -v 0 -i 4 -s :
    です。

    i に代入する値は、
    ・適当にレジスタダンプ
    ・dmesg で結果見る
    ・CR3 レジスタ(= ページディレクトリエントリの値です)
    を確認してそれを 16進でそのまま入れてください。 
    (2010年05月07日 10:30:51)
  • すずき 
    用語がふにゃふにゃしててすみません。訂正します。
    ページ「テーブル」のダンプじゃなくて、ページ「ディレクトリテーブル」のダンプです。
    日記に書いたとおり 4MB ページの場合は、ページテーブルはありません。 
    (2010年05月07日 10:35:35)
open/close この記事にコメントする



link もっと前
   2008年 5月 2日 -
      2008年 5月 2日  
link もっと後

管理用メニュー

link 記事を新規作成

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDF ファイル RSS 1.0
QR コード QR コード

最終更新: 9/17 20:03

カレンダー

<2008>
<<<05>>>
----123
45678910
11121314151617
18192021222324
25262728293031

最近のコメント 5件

  • link 18年09月07日
    すずき 「ありがとう!\nこちらこそ、楽しみにして...」
    (更新:09/11 19:30)
  • link 18年09月07日
    よしだあ 「おつかれさまでした!\nまた仕事できるの...」
    (更新:09/11 19:17)
  • link 18年08月15日
    すずき 「うーん、なんか暴走したり、動かなかったり...」
    (更新:08/15 10:52)
  • link 18年08月15日
    すずき 「実行できた。あと実行ファイルパスについて...」
    (更新:08/15 10:42)
  • link 18年08月15日
    すずき 「さすがに x86_64 と arm のク...」
    (更新:08/15 10:35)

最近の記事 3件

link もっとみる
  • link 18年09月13日
    すずき 「[府民から都民へ] 家が決まりました。今月末から東京都民です。さよ...」
    (更新:09/17 20:03)
  • link 18年09月11日
    すずき 「[エアコン浄化] 今年の 7月に(2018年 7月 17日の日記参...」
    (更新:09/17 19:38)
  • link 18年09月10日
    すずき 「[引っ越し準備] 引っ越し用の新品の段ボールが 50箱以上届き、家...」
    (更新:09/17 19:32)

こんてんつ

open/close wiki
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報