based on: NetBSD: pmap.9,v 1.24 2003/05/10 21:13:27 thorpej Exp 名称 pmap - 仮想メモリシステムのマシンに依存する部分 詳細 pmap モジュールは NetBSD の仮想メモリシステムである uvm(9) の マシン依存部である。pmap モジュールの目的は、物理アドレスマップの 管理やシステムに搭載されているメモリ管理ハードウェアのプログラミング、 そして仮想メモリシステムの正しい動作を保証するために必要なすべての キャッシュ操作である。 仮想メモリマッピングの無効化(たとえば TLB の無効化やマルチプロセッサの ための TLB シュートダウン操作)が高コストなハードウェアアーキテクチャに 対処するために、pmap モジュールはマッピングの無効化や保護操作を、 それらが本当に必要になるような時まで遅延させることが許されている。 そのような動作を遅延させることが許されている関数は、pmap_enter()、 pmap_remove()、pmap_protect()、pmap_kenter_pa()、そして pmap_kremove() である。これらの関数を呼び出す側は、マッピングを正しい状態にする必要が 生じた時に、pmap_update() 関数を用いて pmap モジュールへと通知しなければ ならない。pmap モジュールへは、各プロセッサが与えられた物理マッピングを 使用しているという情報が提供されているため、仮想対物理マッピングの 同期を取ることに伴う高コストを削減することができるような、 いかなる最適化をも利用することができる。 ヘッダファイルとデータ構造体 マシン依存部は ヘッダファイルを提供しなければならない。 このファイルは次のような pmap 構造体の定義を含む: struct pmap { /* pmap の実装により定義される中身 */ }; typedef struct pmap *pmap_t; このヘッダファイルは、pmap の実装が利用するその他のデータ構造体も 定義することができる。 注意すべきは、pmap のインターフェース関数に対するすべてのプロトタイプは ヘッダファイルで定義されていることである。 この動作は ``PMAP_EXCLUDE_DECLS'' という C プリプロセッサマクロで 抑止できる。これはたとえば、一つの pmap モジュールでいくつかの 異なる MMU タイプを取り扱うために、pmap API 呼び出しに対して 間接化を行う層を追加する目的で利用できる。もし、``PMAP_EXCLUDE_DECLS'' マクロが定義されているのならば、 は以下のような 関数プロトタイプのブロックを提供しなければならない: #ifdef _KERNEL /* ユーザの名前空間には晒さない */ __BEGIN_DECLS /* C++ で安全にする */ /* プロトタイプをここに置く */ __END_DECLS #endif /* _KERNEL */ ヘッダファイルは pmap 統計(後述)を追跡するための 構造体を定義している。この構造体は次のように定義されている: struct pmap_statistics { long resident_count; /* マップされているページの数 */ long wired_count; /* 束縛されているページの数 */ }; 束縛された(wired)マッピング pmap モジュールは、それが管理している物理マッピングのおのおのに 含まれているすべての情報が冗長であるという前提のもとに成り立っている。 これは、必要があれば pmap モジュールは物理マッピングの保持する情報を 「忘れて」よいということである --- ページフォールトが起こることにより、 uvm(9)によってそれが再構築可能であるということだ。この原則に 対する一つの例外があり、それは「束縛された(wired)」マッピングと 呼ばれるもので、忘れることが許されない。束縛されたマッピングは、 そのマッピングを再構築するための高位の情報が存在しないような場合か、 あるいはページフォールトを受け入れることができないような、いわゆる クリティカルセクションによって利用されるマッピングへと使われる。 マッピングが束縛されているかどうかということについての情報は、 マッピングが確立される時点で pmap モジュールへと提供される。 訳注:ページフォールトと、TLB ミスによるフォールトを 混同してはならない。ページフォールトは、一次記憶上にデータがなく、 それをディスクなどから呼び込まねばならないようなケースであるのに対し、 TLB ミスによるフォールトは、一次記憶上にデータはあるものの、 それにアクセスするための TLB エントリが失われているだけの状態で、 これは、明示的または暗示的な操作で(とはいえ、明示的な TLB ミスが 起こるアーキテクチャではやはり明示的な操作が必要であろうが) ページテーブルから TLB エントリを復元すればよいだけである。 「束縛された」マッピングは前者を抑制するものであって、 もちろん後者を抑制するものではない(し、それはしばしば現実的ではない)。 変更/参照情報 pmap モジュールは、仮想メモリシステムによって管理されているページが、 参照ないし変更されているか否かということを追跡する必要がある。 この情報は、そのページが pagedaemon によってスキャンされているときに、 そのページで何が起こっているかを決定する目的で uvm(9) によって使われる。 多くの CPU では、変更/参照情報の追跡のためのハードウェアサポートを 提供している。しかし、多くの CPU、特に最近の RISC CPU はそうではない。 変更/参照情報の追跡のためのハードウェアサポートが欠如した CPU 上では、 pmap モジュールがこれをソフトウェアでエミュレートしなければならない。 これを行うためにはいくつかの戦略があり、最適な戦略は CPU に依存する。 「参照されている」という属性は、そのページが「アクティブ」かどうかを 決定するためにページデーモンによって用いられる。アクティブなページは、 ページ置き換えアルゴリズムにおいて再利用されるための候補ではない。 精密な参照情報は正確な操作を行うために必ずしも必要ではない --- もしも、あるページに対する参照情報の提供が適切に行えないような場合には、 pmap 実装は常に、「参照されている」という属性が FALSE であると考える べきである。 「変更されている」という属性は、そのページを掃除する必要があるかどうか (つまり、バッキングストア --- スワップ領域や普通のファイルなど --- へと 書き込む必要があるかどうか)を決定するためにページデーモンによって 用いられる。精密な変更情報は、(訳注:参照情報とは異なり) 仮想メモリシステムの正しい操作を行うために必ず提供されている必要がある。 注意すべきは、変更/参照情報は仮想メモリシステムによって管理されている (つまり、そのページのために vm_page 構造体が用意されている)ページのみを 対象として追跡される。それに加えて、このようなページの 「管理されている(managed)」マッピングのみが変更/参照の追跡情報 を保持している。 pmap_enter() 関数によって配置されたマッピングが 「管理されている」マッピングである。また、pmap_kenter_pa() 関数を 使うことにより、あるページの「管理されていない(unmanaged)」マッピングを 作ることが可能である。「管理されていない」マッピングの利用は、 割り込みコンテキストで実行されることのあるコード (たとえばカーネルメモリアロケータ)のためや、あるいは仮想メモリ システムによって管理されていない物理アドレス(訳注:メモリマップド I/O 空間など)のためのマッピングを配置するために制限されるべきである。 「管理されていない」マッピングは、カーネルの仮想空間にのみ配置できる。 pmap の実装がデータ構造体の操作やロックを保持しているときに 割り込みをブロックする必要がないようにするために、 pmap_kenter_pa() ないし pmap_kremove() 関数を呼び出す側に対して この制約が設けられている。 もう一つ注意すべきことは、変更/参照情報はページごとを基底として 追跡されなければならない --- これらはマッピングの属性ではなく、 各ページの属性であるということである。ゆえに、 たとえ与えられたページに対するすべてのマッピングが外されたあとでも、 そのページに対する変更/参照情報は保持されていなければならない。 変更/参照情報が消去されても良い唯一の時は、仮想メモリシステムが 明示的に pmap_clear_modify() ならびに pmap_clear_reference() 関数を 呼び出した時のみである。また、変更ないしは参照状態を消去した後に、 これらの関数は、再びそのページの変更ないしは参照を検出する必要が あるようにすべての内部状態を変更しなければならない。 (NetBSD 1.6 までは、変更/参照状態を消去する前にUVM (あるいはそれ以前の Mach VM)が常に pmap_page_protect() を呼んでいたため、これを避けることが できたが、もはやそうしないよう UVM が変更されたため、いまではすべての pmap 実装がこれを取り扱わねばならない) 統計 pmap は、「駐在している(resident)」ページの数と「束縛されている」 ページの数に関する統計をつけることが要求される。 「駐在している」ページとは、それに対するマッピングが存在するページの ことである。この統計は、あるプロセスの駐在サイズの計算と、 資源の制限を強制するために使われる。(仮想メモリシステムによって 管理されているか否かにかかわらず)物理マップに張りつけられたページのみが 駐在している数としてカウントされる。 「束縛された」ページとは、それに対する束縛されたマッピングが存在する ページのことである。この統計は資源の制限を強制するために利用される。 pmap 実装は pmap 統計の追跡においてあらかじめ pmap_statistics 構造体を pmap 構造体に配置しておき、そしてマッピングが確立されたり 変更されたり外されたりしたときにそのカウントを調整するという形での pmap_statistics 構造体の利用が(必須ではないものの)推奨されることを 注記しておく。これは、統計が問い合わせられた時に、潜在的に高コスト たりうるデータ構造体の巡回が行われることを避けるためである。 必須関数 このセクションでは、pmap モジュールが仮想メモリシステムへと 提供しなければならない関数群について述べる。 void pmap_init(void) この関数は pmap モジュールを初期化する。これは、 pmap モジュールが物理マップを管理するために必要な すべてのデータ構造体を初期化するために、uvm_init() によって呼び出される。 pmap_t pmap_kernel(void) カーネル仮想アドレス空間をマップしている pmap 構造体への ポインタを返す。 この関数を C のプリプロセッサマクロとして提供してもよい ことを注記しておく。 void pmap_virtual_space(vaddr_t *vstartp, vaddr_t *vendp) pmap_virtual_space()関数は、初期のカーネル仮想アドレスの 始点と終点を決定するために呼び出される。これらの値は、 カーネルの仮想メモリマップを作るために利用される。 この関数は、uvm(9)によって管理されるであろう カーネルの仮想アドレスの先頭を *vstartp に、 同様に末尾を *vendp に、それぞれセットしなければならない。 もし pmap_growkernel() 機能が pmap 実装によって利用されるの ならば、*vendp は実装によって許される最大のカーネル仮想 アドレス値がセットされなければならない。もし、 pmap_growkernel() が使われないのであれば、カーネル 仮想アドレス空間をマップするために現在割り当てられている 資源によってマップ可能なカーネル仮想アドレスの最大値が *vendp へとセットされなければならない。 pmap_t pmap_create(void) 物理マップを作成し、これを呼び出し元へと返す。 新しいマップの参照カウントは 1 である。 void pmap_destroy(pmap_t pmap) 指定された物理マップの参照カウントを減らす。 もしリファレンスカウントが 0 まで落ちたら、 その物理マップに関連するすべての資源を解放し、 そしてその物理マップを破壊する。0 へと落ちた場合、 そのマップには一つもマッピングが存在しないであろう。 pmap 実装はそのことをアサートしてよい。 void pmap_reference(pmap_t pmap) 指定された物理マップの参照カウントを増やす。 long pmap_resident_count(pmap_t pmap) pmap に対応する「駐在ページ」の統計を問い合わせる。 この関数を C プリプロセッサとして提供してよいことを注記する。 long pmap_wired_count(pmap_t pmap) pmap に対応する「束縛ページ」の統計を問い合わせる。 この関数を C プリプロセッサとして提供してよいことを注記する。 int pmap_enter(pmap_t pmap, vaddr_t va, paddr_t pa, vm_prot_t prot, int flags) 物理マップ pmap の中に、物理アドレス pa のためのマッピングを、 仮想アドレス va の位置に prot の各ビットで指定された 保護属性で作成する。prot の各ビットは次のとおり: VM_PROT_READ マッピングは読み込み可であるべし VM_PROT_WRITE マッピングは書き込み可であるべし VM_PROT_EXECUTE マップされるページはプロセッサに よって実行されるであろう命令を 含んでいる flags 引数は、そのマッピングが作られる原因となるアクセスの 型を示す保護ビット(prot 引数で使われるのと同様のビット群)を 含む。この情報は、ソフトウェアで変更/参照情報を追跡する プラットフォーム上で冗長なフォールトの発生を極力さけるよう、 そのページがマップされるためにあらかじめ変更/参照情報の 初期値として使うことができる。それ以外にも以下のような 情報が flags によって提供される: PMAP_WIRED 作成されるマッピングは束縛された マッピングである PMAP_CANFAIL pmap_enter() の呼び出しが 失敗することを許す。このフラグが セットされていないときに、 おそらくリソースの不足などによって pmap_enter() の呼び出しでマッピングの 作成ができない場合には、pmap モジュールはパニックしなければ ならない(訳注:失敗状態で呼び出し元に 戻ることは許されない)。 flags 引数で与えられたアクセスタイプは、決して prot によって指定されている保護の範囲を超えることは ないだろう。pmap 実装はこのことをアサートしてもよい。 注意すべきは、PMAP_WIRED フラグがセットされているのならば、 変更/参照情報の追跡にハードウェアのサポートを提供していない システムにおいては、そのページに対する変更/参照情報は flags で提供されているアクセスタイプが初期値でなければ ならない。これは、フォールトが受け入れられない場所である クリティカルセクションにシステムがある間、変更/参照 情報の追跡の目的で発生するフォールトを防ぐためである。 pmap_enter() はしばしば、すでにマッピングが 存在するような仮想アドレスに対するマッピングを 配置するよう呼び出されることがあることを注意しておく。 このような状況では、たとえ新しいマッピングを配置する前に 以前のマッピングを無効化する必要のある動作であっても、 実装はそれを採用しなければならない。 また、pmap_enter() はしばしば、以前から存在するマッピングの 保護属性や「束縛」の属性を変更するために呼び出されることも 注意する。 pmap_enter() 関数は成功時に 0 を返し、そうでなければ 失敗のしかたを示すエラーコードを返す。 void pmap_remove(pmap_t pmap, vaddr_t sva, vaddr_t eva) sva と eva で指定された仮想アドレスの範囲に対応する マッピングを物理マップから取り外す。 void pmap_remove_all(pmap_t pmap) この関数は pmap 実装に対して、これ以上のあらゆるエントリが 配置されるよりも前に、pmap 中のすべてのエントリが 取り外されることになるであろうというヒントとなる。 この関数の呼び出しの後、すべてのマッピングを取り外すような pmap_remove() の呼び出しがあり、そしてそれに引き続いて pmap_destroy() もしくは pmap_update() の呼び出しがあるだろう。 この過程の間では、 上記以外の pmap インターフェースのうち、 pmap が引数に含まれるものは呼ばれることがないであろう。 一方、明示的な pmap 引数を持たないインターフェースをもつが、 その pmap への暗示的なアクセスが必要になるかもしれない (pmap_page_protect() のように)関数に関しては、 この過程の間に呼び出されることが許されている。 pmap 実装は、pmap_remove_all() が呼び出されたら直ちに その pmap のすべてのマッピングを取り外すのも、 あるいは、続いて呼ばれる pmap_remove() が取り外しを 最適化するための知識として利用するのも(あるいは単純に この関数の呼び出しを無視するのも)自由である。 void pmap_protect(pmap_t pmap, vaddr_t sva, vaddr_t eva, vm_prot_t prot) 物理マップ中で sva と eva で指定された仮想アドレスの範囲に あるマッピングの保護属性を設定する。 void pmap_unwire(pmap_t pmap, vaddr_t va) 仮想アドレス va に対応するマッピングの「束縛」属性を 解除する。 boolean_t pmap_extract(pmap_t pmap, vaddr_t va, paddr_t *pap) この関数は、指定された物理マップから、マッピングを抽出する。 これは二つの目的を提供する --- 指定された仮想アドレスに マッピングが存在するかどうかを決定することと、 その仮想アドレスにマップされている物理アドレスを 決定することである。 pmap_extract() 関数は、va に対応するマッピングが 存在しなければ FALSE を返す。そうでない場合、 これは TRUE を返し、pap が NULL でない場合には、 va にマップされている物理アドレスを *pap へと置く。 void pmap_kenter_pa(vaddr_t va, paddr_t pa, vm_prot_t prot) カーネル物理マップ中に、物理アドレス pa のための 「管理されていない」マッピングを、仮想アドレス va の位置に prot で指定された保護属性で配置する。このタイプの マッピングは常に「束縛」されており、また(pmap_page_protect() のような)ページの保護属性を変更するルーチンの影響を受けない。 このようなマッピングは、ページについての変更/参照情報の集合に 含まれることもない。マシンに依存しないコードから pmap_kenter_pa() によって配置されたマッピングは実行権限を 持っていてはならず、これは、実行権限を追跡するために 必要なデータ構造体が pmap_kenter_pa() では利用できないから である。マシンに依存しないコードは、すでに有効なマッピングが 存在しているような仮想アドレスへ pmap_kenter_pa() によって あるマッピングを配置することができない。pmap_kenter_pa() によって生成されたマッピングは、唯一 pmap_kremove() を 呼び出すことによって取り外すことができる。 pmap_kenter_pa() は、割り込みコンテキストでの利用が 安全に行えなければならないことを注記する。 splvm() は、pmap_kenter_pa() が呼び出される可能性のある 割り込みをブロックする(訳注: splvm() よりも高い割り込み レベルのコンテキストからは pmap_kenter_pa() を 呼び出してはならない)。 void pmap_kremove(vaddr_t va, vsize_t size) カーネル物理マップ中の、仮想アドレス va から始まる size バイトの部分にあるすべてのマッピングを取り外す。 取り外されるすべてのマッピングは pmap_kenter_pa() によって 生成された「管理されていない」マッピングでなければならない。 実装はこれをアサートできる。 void pmap_copy(pmap_t dst_map, pmap_t src_map, vaddr_t dst_addr, vsize_t len, vaddr_t src_addr) この関数は、src_map 中の src_addr から始まる len バイトの マッピングを、dst_map の dst_addr から始まるところへと コピーする。 この関数は pmap 実装により提供される必要があるものの、 それは実際に何かを行わなければならないということではない。 pmap_copy() は単なる勧告である(fork(2) の途中で、 子プロセスのアドレス空間を事前フォールトするために使われる)。 void pmap_collect(pmap_t pmap) この関数は、プロセスのアドレス空間をマップするために 使われている資源を pmap モジュールが開放できるよう、 プロセスがスワップアウトする直前に呼び出される。 実装は、たとえばページテーブルをシステムへと返却し 開放する目的で、物理マッピングを取り外すという選択を することが可能である。しかしながら、pmap_collect() が 呼び出されても、束縛されたマッピングは取り外されては ならないことを注意しておく。 注意すべきは、pmap 実装によってこの関数が提供されることは 必須のことではあるものの、実際に何かをすることが必須である というわけではない、ということである。pmap_collect() は 単なる勧告である。しかしながら、pmap_collect() が pmap 実装によって完全に実装されていることが推奨される。 void pmap_update(pmap_t pmap) この関数は pmap モジュールに対し、指定された pmap に 対応するすべての物理マッピングが、この関数呼び出し時点から 正しい状態に置かれていなければならないことを通知する。 これは、(TLB の無効化や、アドレス空間識別子の更新といった) 遅延されていたすべての仮想-物理マッピングの更新状態が、 この関数呼び出しによって実際に反映されなければ ならないということである。この関数は、仮想メモリシステムの 操作が正しく行われることを保証するために、pmap_enter() や pmap_remove()、pmap_protect()、pmap_kenter_pa()、そして pmap_kremove() の呼び出しの後で使われなければならない。 もしも、pmap 実装が仮想-物理マッピングの遅延更新を 行わない場合には、pmap_update() は何もしない。 この場合、 で C プリプロセッサマクロを 使うことにより(訳注: pmap_update() という空のマクロを 定義することにより)、この関数の呼び出しを無くすことができる。 void pmap_activate(struct proc *p) プロセス p によって使われる物理マップを活性化する。 これは、プロセスに対するメモリコンテクストが 変更されたときに仮想メモリシステムによって呼ばれたり、 あるいは、しばしばプロセスのページテーブルベースに 基づいてメモリ管理ハードウェアをプログラムするために マシンに依存する部分のコンテキストスイッチコードから 利用されたりすることがある。pmap_activate() は、 p がいつもカレントプロセスの時に呼ばれるとは 限らないことに注意せよ。pmap_activate() はこのような 場合でも扱えなければならない。 (訳注: 現在は struct proc ではなく struct lwp である) void pmap_deactivate(struct proc *p) プロセス p によって使われている物理マップの活性化を解除する。 これは通常 pmap_activate とともに利用される。 pmap_deactivate() は、p がいつもカレントプロセスの時に 呼ばれるとは限らない。 (訳注: 現在は struct proc ではなく struct lwp である) void pmap_zero_page(paddr_t pa) 物理アドレス pa から始まる大きさ PAGE_SIZE の範囲を ゼロで埋める。pmap 実装は、たとえその過程でその ページをカーネルがアクセス可能なページへマップして、 それからページをゼロで埋める必要があったとしても、 それを行わなければならない。この関数のパフォーマンスが ページフォールトのパフォーマンスに直接影響するため、 実装が最適化されたゼロ埋めアルゴリズムを用いることが 提唱される。実装は、この領域が PAGE_SIZE でアラインメント されており、その長さがちょうど PAGE_SIZE に等しいことを 仮定してよい。 そのプラットフォームのキャッシュの設定も、pmap_zero_page() の実装において考慮されるべきであることを注意する。 たとえば、物理アドレス指向のキャッシュをもつシステムでは、 ゼロ埋めが普通オンデマンドで行われるため、ページのゼロ埋め によって生じるキャッシュの負荷が浪費されないだろう。 しかし、仮想アドレス指向のキャッシュを持つシステムでは、 そのページがゼロ埋めで用いられるのと異なる仮想アドレスへと マップされているであろうという理由により、ページのゼロ埋めに よってキャッシュの負荷が浪費されるだろう。仮想アドレス指向の キャッシュの場合にはまた、キャッシュのエイリアス問題を 避けるよう注意するべきである。 void pmap_copy_page(paddr_t src, paddr_dst) 物理アドレス src から始まり PAGE_SIZE のサイズの領域を、 物理アドレス dst から始まり同じサイズの領域へとコピーする。 pmap 実装は、たとえその過程でコピー元およびコピー先の ページをカーネルがアクセス可能なページへマップして、 それからコピーを行う必要があったとしても、それを行わなければ ならない。この関数のパフォーマンスがページフォールトの パフォーマンスに直接影響するため、実装が最適化されたゼロ埋め アルゴリズムを用いることが提唱される。実装は、それぞれの領域が PAGE_SIZE でアラインメントされており、その長さがちょうど PAGE_SIZE に等しいことを仮定してよい。 pmap_zero_page() のキャッシュについての考察は pmap_copy_page() にもあてはまる。 void pmap_page_protect(struct vm_page *pg, vm_prot_t prot) ページ pg のすべてのマッピングに対する保護権限を prot へと下げる。この関数は仮想メモリシステムによって、 コピーオンライトの実装や(→prot の VM_PROT_READ をセットして 呼び出す)、ページを消去する時にすべてのマッピングを 無効化する(→prot の全ビットをセットせずに呼び出す)ために 利用される。アクセス権限をこの関数の呼び出しの結果として 与えることは決してできない。 boolean_t pmap_clear_modify(struct vm_page *pg) ページ pg の「変更」属性を消去する。 pmap_clear_modify() 関数は、消去する前にそのページの 「変更」属性がセットされていたかどうかを示す TRUE か FALSE を返す。 この関数は C プリプロセッサマクロとして提供されても 良いことを注記する。 boolean_t pmap_clear_referece(struct vm_page *pg) ページ pg の「参照」属性を消去する。 pmap_clear_modify() 関数は、消去する前にそのページの 「参照」属性がセットされていたかどうかを示す TRUE か FALSE を返す。 この関数は C プリプロセッサマクロとして提供されても 良いことを注記する。 boolean_t pmap_is_modify(struct vm_page *pg) ページ pg の「変更」属性がセットされているかどうかを検査する。 この関数は C プリプロセッサマクロとして提供されても 良いことを注記する。 boolean_t pmap_is_reference(struct vm_page *pg) ページ pg の「参照」属性がセットされているかどうかを検査する。 この関数は C プリプロセッサマクロとして提供されても 良いことを注記する。 paddr_t pmap_phys_address(int cookie) デバイスの mmap() 関数によって返されたクッキーを 物理アドレスへと変換する。この関数は、プラットフォームの paddr_t 型によって直接アドレス可能なサイズよりも大きな 物理アドレス空間を持つシステムに便宜を図るために提供される。 この関数の存在は非常にうさんくさく、NetBSD の将来のリリース における pmap API からは削除されると予想される。 この関数は C プリプロセッサマクロとして提供されても 良いことを注記する。 オプション関数 このセクションでは、pmap API のオプション関数を記述する。 vaddr_t pmap_steal_memory(vsize_t size, vaddr_t *vstartp, vaddr_t *vendp) この関数はブートストラップメモリアロケータであり、 uvm(9) 自身の範囲内で利用されるブートストラップアロケータの かわりに提供することができる。これはたとえば、ダイレクト マップなメモリセグメントを提供するシステムなどで 特に便利である。この関数は、vm_physmem[] 配列として uvm(9) へと提供されている、管理された(されるべき)メモリの プールからページを借用(steal)することによって働く。 これらのページはそれから、マシンに依存した方法で、 マップされるかあるいはそれ以外の方法でカーネルから アクセス可能にされる。そのメモリは pmap_steal_memory() に よってゼロ埋めされなければならない。pmap_steal_memory() によって割り当てられたメモリは決して開放されず、 また、pmap_steal_memory() によって作成されたマッピングは 決して「忘れられて」はならないことに注意せよ。 pmap_steal_memory() は、スタートアップ初期の汎用の メモリ割り当てルーチンとして使われるべきではないことを 注記する。uvm_pageboot_alloc() ルーチンやそのサポート ルーチンによってのみ使われることが意図されている。 もしも、仮想メモリシステムが初期化される前にメモリを 割り当てる必要があるのならば、uvm_pageboot_alloc() を 利用せよ。さらに詳しくは uvm(9) を参照のこと。 pmap_steal_memory() 関数は、割り当てられたメモリの カーネルがアクセス可能なアドレスを返す。割り当てのできる メモリがない場合や、割り当てられたメモリをマップすることが できない場合には、この関数はパニックしなければならない。 pmap_steal_memory() 関数が pmap_virtual_space() 呼び出しに よって uvm(9) へと提供した範囲のアドレス空間を利用する場合、 pmap_steal_memory() 関数は戻る時に *vstartp と *vendp を 調整しなければならない。 pmap_steal_memory() 関数は、 において ``PMAP_STEAL_MEMORY'' C プリプロセッサマクロを定義する ことによって有効になる。 vaddr_t pmap_growkernel(vaddr_t maxkvaddr) カーネル仮想アドレス空間の管理は、カーネル仮想アドレスを マップするために利用される資源の待機をいつでも安全に 行うことができるとは限らないという事実によって込み入った ものとなる。しかし、完全なカーネル仮想アドレス空間を マップするために必要なすべての資源を事前に割り当てることが 常に好ましいとも限らない。 pmap_growkernel() インターフェースは、この問題を緩和するために デザインされている。仮想メモリのスタートアップコードは、 マッピング資源の初期セット(たとえばページテーブル)を 割り当てたり、これらの初期資源を利用してマップすることのできる カーネル仮想アドレス空間の大きさを知らせる内部変数を設定する という選択が可能である(訳注:最初は必要最低限の空間空間資源 だけを割り当てておくという実装が可能)。そして、 仮想メモリシステムが最初の制限値を超えたところに何かを マップしたくなった時には、そのマッピングを作成するために 利用される資源を事前割り当てするために pmap_growkernel() を 呼び出す。ひとたび追加のカーネル仮想アドレスマッピング資源が 割り当てられたら、それらは開放されるべきではないことを 注記しておく --- それらはおそらくまた必要になるだろう。 pmap_growkernel() は、利用可能なリソースによってマップ可能な 新しいカーネル仮想アドレスの最大値を返す。もし新しいリソースを 割り当てることができない場合には、pmap_growkernel() は パニックしなければならない。 pmap_growkernel() 関数は、 において ``PMAP_GROWKERNEL'' C プリプロセッサマクロを定義する ことによって有効になる。 void pmap_fork(pmap_t src_map, pmap_t dst_map) いくつかの pmap 実装は、仮想アドレス空間に直接関連しない その他の情報を追跡する必要があることがある。 たとえば i386 への移植版では、プロセスのローカル デスクリプタテーブルは pmap へと結びつけられている (これは、アプリケーションが、そのプロセスの仮想メモリ 状態へと論理的に結びつけられているものと仮定して 直接ローカルデスクリプタテーブルを操作するという事実による)。 pmap_fork() 関数は、vmspace が分裂(fork)されるときに、 src_map から dst_map へと情報を関連づける方法として 提供される。pmap_fork() は uvmspace_fork() から呼ばれる。 pmap_fork() 関数は、 において ``PMAP_FORK'' C プリプロセッサマクロを定義する ことによって有効になる。 vaddr_t PMAP_MAP_POOLPAGE(paddr_t pa) この関数は、pool(9) メモリプールマネージャにより 利用される。pool は、裏打ちとなるひとかたまりのページを 一度に割り当てる。この関数は、pool(9) アロケータによって 利用されるページをマップするために、ダイレクトマップな メモリセグメントのようなハードウェア機能を利用する手段として 提供される。これは、たとえば TLB の操作を減らすといった 効果により、良いパフォーマンスへと導く結果となる。 PMAP_MAP_POOLPAGE() は、マップされたページのカーネルが アクセス可能なアドレスを返す。常に成功しなければならない。 PMAP_MAP_POOLPAGE() 関数は、 において それ自身を C プリプロセッサマクロとして定義することによって 有効となる。PMAP_MAP_POOLPAGE() が定義されている場合には、 PMAP_UNMAP_POOLPAGE() も同様に定義されていなければならない。 以下は PMAP_MAP_POOLPAGE() の定義の仕方の例である: #define PMAP_MAP_POOLPAGE(pa) MIPS_PHYS_TO_KSEG0((pa)) これはページの物理アドレスを取り、そのページの MIPS プロセッサにおける KSEG0 領域のアドレスを返す。 訳注: MIPS プロセッサは、プロセッサの設計によって 仮想アドレスの各領域の使われ方が固定されている。 上で述べられている KSEG0 仮想アドレス領域 (MIPS32 では 0x80000000-0x9FFFFFFF)に対してアクセスすると、 MMU を介さずに直接そのアドレス値をアドレスデコーダに対して 生成する(ただしキャッシュされうる)。MIPS プロセッサは TLB が直接プログラマに対してむき出しとなっており、したがって TLB ミス時などの処理をすべて OS 自身が行う必要がある。 また、TLB のエントリ数はそれほど多くはなく、世代が遡ると 少なくなる傾向がある。したがって、このように MMU を バイパスする機能が有用と考えられる。 paddr_t PMAP_UNMAP_POOLPAGE(vaddr_t va) この関数は、PMAP_MAP_POOLPAGE() の逆を行う。 PMAP_UNMAP_POOLPAGE() は、引数として与えられた、 カーネルがアクセス可能なアドレスに対応する、 そのページの物理アドレスを返す。 PMAP_UNMAP_POOLPAGE() 関数は、 において それ自身を C プリプロセッサマクロとして定義することによって 有効となる。PMAP_UNMAP_POOLPAGE() が定義されている場合には、 PMAP_MAP_POOLPAGE() も同様に定義されていなければならない。 以下は PMAP_UNMAP_POOLPAGE() の定義の仕方の例である: #define PMAP_UNMAP_POOLPAGE(va) MIPS_KSEG0_TO_PHYS((va)) これはページの MIPS プロセッサにおける KSEG0 領域のアドレスを 取り、そのページの物理アドレスを返す。 参照 uvm(9) 歴史 pmap モジュールはもともと、Mach オペレーティングシステムの 仮想メモリシステムのデザインの一部であった。その目標は、 仮想メモリシステムのマシンに依存する部分と依存しない部分との 綺麗な分離を提供することであり、これは VAX へと依存していた オリジナルの 3BSD 仮想メモリシステムとは全く対象的なものである。 4.3BSD と 4.4BSD の間に、pmap API を含む Mach 仮想メモリシステムが BSD へと移植され、そして 4.4BSD リリースに含まれるに至った。 NetBSD は Mach 仮想メモリシステムの BSD 版を継承していた。 NetBSD 1.4 が、新しい uvm(9) 仮想メモリシステムを搭載した 最初の NetBSD リリースであり、pmap API への若干の変更を含んでいた。 uvm(9) の導入からも、pmap API は大いに進歩しつづけている。 著者 Mach のもともとの VAX pmap モジュールは Avadis Tevanian, Jr. と Michael Wayne Young によって書かれた。 Mike Hibler は Mach 仮想メモリシステムの 4.4BSD への統合を 行い、そしてモトローラ 68020+68851/68030/68040 のための pmap モジュールを実装した。 NetBSD に存在するものとしての pmap API は 4.4BSD から派生されており、 以下の人々によって変更されてきた: Chuck Cranor, Charles M. Hannum, Chuck Silvers, Wolfgang Solfrank, Bill Sommerfeld, and Jason R. Thorpe. このドキュメントの著者は Jason R. Thorpe である。 バグ pmap_activate() と pmap_deactivate() の利用およびその定義については 再検討する必要がある。 pmap_copy() の利用は再検討する必要がある。pmap_copy() が実際に その定義された機能を行うようなときにはシステムのパフォーマンスが 影響を受けるということを経験的な根拠が示唆している。 これは主に、プロセスが fork(2) の後に execve(2) を呼び出すような ケースでは、仮想-物理マッピングのコピーが徒労として浪費されてしまうと いう事実のためである。このような理由により、いまのところ pmap 実装は、 pmap_copy() 関数の本体を空のままにしておくことが推奨されているのである。 訳注: pmap_copy() の目的は、fork() した子プロセスでも 元のプロセス空間が継承されるようなケース(たとえば fork() による 並行サーバの実装)を最適化するものであり、これは execve() を最初から 前提としているような(その他の多くの)ケースとは相容れない。後者のような 目的には vfork()+execve() を使うことが推奨されるものの (c.f.「なぜ伝統的な vfork() を実装したか」)、移植性を考えると 必ずしもそれは簡単であるとは限らないため、要はどちらにバランスを取るか、 ということになるだろう。