ページフォルト
表1 ページフォルトの発生要因とその動作
ページフォルト 発生要因 | 結果 |
---|---|
不正な仮想アドレスへのアクセス | ユーザモードでページフォルトが発生していた場合は、該当スレッドを終了させる。カーネルモードでページフォルトが発生していた場合はシステム停止。 |
物理ページがまだ割り当てられていない仮想アドレス空間へのアクセス | 物理ページを割り当てて、PTEを設定する。 |
プロテクション不正 | ユーザモードでページフォルトが発生していた場合は、該当スレッドを終了させる。カーネルモードでページフォルトが発生していた場合はシステム停止。 |
プロテクション不正(COWのためにReadOnlyになっているページへの書込み) | COW(Copy On Write)処理を行なう。ページをコピーして書込み可能にする。 |
Pageoutされているページへのアクセス | Pageinを行う。 |
[ページフォルトのハンドラ]
vm_fault()
例外ハンドラルーチンkernel_trap(),user_trap()から呼ばれる。
メモリの割り当て時にもページフォルトをシミュレートして物理ページを割り当てるために呼ばれる。
ページフォルトのハンドラは前半の高速処理部(whileループ内)と後半の通常処理にわかれている。高速処理部では、mapとvm_objectのロックを取りっぱなしで処理を行う。ロックを取ったままでは処理を行えないもの(Pager処理など(*1))を行う場合は高速処理部を抜けて(break)、通常処理部で処理を行う。
通常処理部ではmapのロックは取得しない。vm_objectのロックは持つが、Pagerへのアクセス時にはロックを解放している。ロックの代わりにpageをbusy状態にして他のスレッドとの競合を回避している。
(*1) Pagerはブロックする場合があるので、ロックを持ったままPager処理を呼んでしまうとデッドロックする場合がある?
ページフォルトのハンドラは前半の高速処理部(whileループ内)と後半の通常処理にわかれている。高速処理部では、mapとvm_objectのロックを取りっぱなしで処理を行う。ロックを取ったままでは処理を行えないもの(Pager処理など(*1))を行う場合は高速処理部を抜けて(break)、通常処理部で処理を行う。
通常処理部ではmapのロックは取得しない。vm_objectのロックは持つが、Pagerへのアクセス時にはロックを解放している。ロックの代わりにpageをbusy状態にして他のスレッドとの競合を回避している。
(*1) Pagerはブロックする場合があるので、ロックを持ったままPager処理を呼んでしまうとデッドロックする場合がある?
vm_fault()の概要
vm_map_lookup_locked()
仮想アドレスに対応するvm_objectを取得する。
不正なアドレスへのアクセスでvm_objectがない場合はKERN_INVALID_ADDRESS。
プロテクション不正の場合はKERN_PROTECTION_FAILURE。
/*
* ページフォルト処理の高速処理
* 処理できない場合は諦めて(以下のwhileをbreak)
* 通常のフォルト処理を行なう。
*/
while (TRUE) { <-- shadowオブジェクトをたどる為にループさせている
m = vm_page_lookup(cur_object, cur_offset);
ページを取得
if (m != VM_PAGE_NULL) {
if (m->busy) {
既にPagein中などなんらかの処理中なので
スレッドをブロック
goto done;
}
:
if ((fault_type & VM_PROT_WRITE) == 0) {
/* ページへのリードかページ上のコードを
実行しようとしてフォルトした */
/* kmem_alloc()でメモリを確保した場合は、
物理ページのマップをここで作成する。
(fault_type:VM_PROT_NONEでページフォルトをシミュレートするため)
*/
FastPmapEnter:
PMAP_ENTER()でPTE設定
PAGE_WAKEUP_DONE(m) ページ待ちのスレッドがいたらWakeup
return KERN_SUCCESS;
}
/* ページへの書込みでフォルトが発生した場合 */
Copy On Writeの処理
vm_page_grab()で新しいページを取得し
vm_page_copy()でページをコピーする。
goto FastPmapEnter; pmap設定へ
} else {
/* ページがない場合 */
if (cur_object->pager_created) {
/* Pagerがある場合はあきらめる。
* PageOutされていた場合など。
*/
break;
}
/*
* kmem_alloc_pageable()で仮想アドレス空間のみ割り当てて、
* 物理ページが存在しない場合はなどは、ここでページが確保&
* 割り当てられる。
*/
if (cur_object->shadow == VM_OBJECT_NULL) {
ページの割り当て処理
vm_page_alloc() ページを取得してvm_objectにつなげる
if (!map->no_zero_fill) {
ページを0で初期化
}
goto FastPmapEnter;
}
/* shadowオブジェクトがある */
shadowをたどる
continue;
}
}
kr = vm_fault_page() 仮想アドレスに対応した物理ページを取得する
PageInの処理もこのなかで行われる。
PMAP_ENTER()
vm_page_lookup(object, offset)
指定objectのoffsetに該当するページ(vm_page_t)を返す。
vm_map_lookup_locked()指定仮想アドレスに対応するVM Objectを取得する。不正なアドレスへのアクセスでvm_map_entryが存在しない場合は
KERN_INVALID_ADDRESS を返す。
vm_map_entryがあった場合でもProtectionが不正であった場合(Writeしようとしたのに、該当RegionがWrite不可である等)は KERN_PROTECTION_FAILURE を返す。
また、該当Regionのneeds_copyがTRUEであった場合、ここでShadow Objectを作る。
vm_map_entryがあった場合でもProtectionが不正であった場合(Writeしようとしたのに、該当RegionがWrite不可である等)は KERN_PROTECTION_FAILURE を返す。
また、該当Regionのneeds_copyがTRUEであった場合、ここでShadow Objectを作る。
COW (Copy On Write)
fork()などでアドレス空間を複製した場合に仮想アドレス空間は複製するが、物理ページはコピーせずに共有する。代わりに、物理ページを
ReadOnlyにしてどちらかのスレッドがそのページに書込みを行なった際にページフォルトが発生するようにしておく。実際にページを共有している何れ
かのスレッドがページに書込みを行ないページフォルトが発生した時点で物理ページがコピーされて書込み可にされる。このように、実際にページに書込む段階
になって初めてページの複製することをCopy On Writeと呼ぶ。
このように物理ページの複製を遅延させることで、fork()後の大量のページコピー処理の発生を抑えることができる。また、Readしか行なわないページはそもそもページのコピーが発生しなくなる。
このように物理ページの複製を遅延させることで、fork()後の大量のページコピー処理の発生を抑えることができる。また、Readしか行なわないページはそもそもページのコピーが発生しなくなる。
[関連ページ]
例外処理