このページを編集する際は、編集に関する方針に従ってください。

概要

  • mm/memory.cにて定義
  • コピーオンライトを実現する関数
    • ただし、該当するページを参照しているプロセスが1つだけである場合は、コピーオンライト処理を行わず、そのプロセスにページの書き込み権利を与え、将来の書き込みアクセス時にページフォルト例外が発生しないようにする
    • この関数はページが存在し、書き込みアクセス時、ページが書き込み禁止であるときに呼ばれる

引数

  • mm--ページフォルト例外を起こしたプロセスのメモリディスクリプタ
  • vma--メモリリージョン
  • address--例外を発生した仮想アドレス
  • page_table--アドレスaddressをマッピングしているページテーブルエントリのアドレス
  • pmd--ページミドルディレクトリ
  • pte--アドレスaddressをマッピングしているページテーブルエントリの内容

実装

/*

* This routine handles present pages, when users try to write
* to a shared page. It is done by copying the page to a new address
* and decrementing the shared-page counter for the old page.
*
* Goto-purists beware: the only reason for goto's here is that it results
* in better assembly code.. The "default" path will see no jumps at all.
*
* Note that this routine assumes that the protection checks have been
* done by the caller (the low-level page fault routine in most cases).
* Thus we can safely just mark it writable once we've done any necessary
* COW.
*
* We also mark the page dirty at this point even though the page will
* change only once the write actually happens. This avoids a few races,
* and potentially makes it more efficient.
*
* We hold the mm semaphore and the page_table_lock on entry and exit
* with the page_table_lock released.
*/

static int do_wp_page(struct mm_struct *mm, struct vm_area_struct * vma,

	unsigned long address, pte_t *page_table, pmd_t *pmd, pte_t pte)

{

	struct page *old_page, *new_page;
  • old_page--ページフォルト例外を発生した書き込み不可であるページ
  • new_page--old_pageのかわりにページを確保し、old_pageの内容をコピーするページ(つまりコピーオンライトにより確保される新たなページ)
	unsigned long pfn = pte_pfn(pte);
  • ページフォルト例外を発生したアドレスを含むページテーブルエントリpteに対応するページフレーム番号をpfnへ設定する
	pte_t entry;
	if (unlikely(!pfn_valid(pfn))) {
  • メモリが足りない場合:
    • VM_FAULT_OOMを返して終了
    • VM_FAULT_OOMはメモリ不足であったことを意味する
  • ページフレーム番号pfnが適切な値であるかチェックする
		/*
		 * This should really halt the system so it can be debugged or
		 * at least the kernel stops what it's doing before it corrupts
		 * data, but for the moment just pretend this is OOM.
		 */
		pte_unmap(page_table);
  • CONFIG_HIGHPTEが無効である場合、処理を行わない
		printk(KERN_ERR "do_wp_page: bogus page at address %08lx\n",
				address);
		spin_unlock(&mm->page_table_lock);
  • スピンロックmm->page_table_lockの開放を行う。プリエンプション機能を有効にし、可能であれば自ら積極的にプリエンプション(実行権の移譲を行うこと)する
		return VM_FAULT_OOM;
  • VM_FAULT_OOMはメモリ不足であったことを意味する
	}
  • 以下、メモリが足りている場合:
	old_page = pfn_to_page(pfn); 
  • ページフォルト例外を発生した書き込み不可であるページをold_pageへ設定する
  • ページフレーム番号pfnからページ(page構造体)を見つけold_pageへ設定する
	if (!TestSetPageLocked(old_page)) {
  • old_pageがロックされていない場合:
  • old_pageのPG_lockedフラグを1に設定して、もともとの値を返す
    • PG_lockedフラグがセットされているページは、ロックされていることを意味する
    • 詳細はTestSetPageLocked()/linux2.6を参照
		int reuse = can_share_swap_page(old_page);
  • スワップキャッシュのページold_pageが共有されているか調べる
		unlock_page(old_page);
  • old_pageのロックを解除し、このページが属するzoneのページロック解除待ちキューのエントリを起床を試みる
		if (reuse) {
  • old_pageが共有されていない場合:
    • cow(コピーオンライト)する必要はなく、該当するページを書き込み可能にする
			flush_cache_page(vma, address);
			entry = maybe_mkwrite(pte_mkyoung(pte_mkdirty(pte)),
					      vma);
  • cow(コピーオンライト)する必要はなく、該当するページを書き込み可能にする
  • ページフォルト例外を発生したアドレスを含むページテーブルエントリpteの各種フラグ(ダーティフラグ、アクセスフラグ)を立て、vmaが書き込み可能ならpteを書き込み可能にしてentryに設定する
  • ページテーブルエントリpteに更新(dirty)フラグを立てる
  • 引数で渡されたページテーブルエントリのアクセスフラグを立てる
  • 引数で渡されたメモリリージョンが書き込み可能であれば、引数で渡されたページテーブルエントリを書き込み可能に設定する
			ptep_set_access_flags(vma, address, page_table, entry, 1);
  • 最後に引数に1が設定されている場合、page_tableにentryを設定し、vmaを参照しているCPUへTLB無効要求を送信する
			update_mmu_cache(vma, address, entry);
			pte_unmap(page_table);
  • CONFIG_HIGHPTEが無効である場合、処理を行わない
			spin_unlock(&mm->page_table_lock);
  • スピンロックmm->page_table_lockの開放を行う。プリエンプション機能を有効にし、可能であれば自ら積極的にプリエンプション(実行権の移譲を行うこと)する
			return VM_FAULT_MINOR;
  • VM_FAULT_MINORは副ページフォルト(カレントプロセスがブロックされることなくページフォルト処理できた)を意味する
		}
	}
	pte_unmap(page_table);
  • CONFIG_HIGHPTEが無効である場合、処理を行わない
	/*
	 * Ok, we need to copy. Oh, well..
	 */
	if (!PageReserved(old_page))
		page_cache_get(old_page);
  • old_pageが予約されているか調べる
    • 予約されている場合1を返し、予約されていない場合は0を返す
    • 詳細はPageReserved()/linux2.6を参照
  • old_pageの参照数をカウントアップ(+1)する
	spin_unlock(&mm->page_table_lock);
  • スピンロックmm->page_table_lockの開放を行う。プリエンプション機能を有効にし、可能であれば自ら積極的にプリエンプション(実行権の移譲を行うこと)する
	if (unlikely(anon_vma_prepare(vma)))
		goto no_new_page;
  • anonymousメモリ(無名メモリ)の準備を行う
    • 引数で渡されたメモリリージョンにマッピングされたanonymousメモリが無い場合は確保し、anonymousメモリのリストに追加する
    • 問題が発生しなかった場合は0を返し、問題が発生した場合はエラーコードを返す
    • anonymousメモリ(無名メモリ)とはファイルと直接関連づけられていないページ(ページ群)を指す
      • プロセスのヒープ領域、スタック、Copy-On-Writeページに対して使用される
      • Copy-On-Writeページ:ゼロページを割り当てておいて、実際に使用する際に例外を発生させ、ページの割り当てを遅延する
    • 詳細はanon_vma_prepare()/linux2.6を参照
	new_page = alloc_page_vma(GFP_HIGHUSER, vma, address);
	if (!new_page)
		goto no_new_page;
  • new_pageの取得に失敗した場合はno_new_pageへジャンプ
	copy_cow_page(old_page,new_page,address);
  • 古いページold_pageから新しいページnew_pageにデータをコピーする
	/*
	 * Re-check the pte - we dropped the lock
	 */
	spin_lock(&mm->page_table_lock);
  • スピンロックmm->page_table_lockの取得を試みる
    • ロックの取得に失敗した場合は待ち状態に入る
    • 詳細はspin_lock()/linux2.6を参照
	page_table = pte_offset_map(pmd, address);
	if (likely(pte_same(*page_table, pte))) {
  • page_tableとpteの2つのページテーブルエントリ(PTE)の物理アドレスを比較する
    • 同じである場合は1を、異なる場合は0を返す
    • 詳細はpte_same()/linux2.6を参照
		if (PageAnon(old_page))
			mm->anon_rss--;
  • old_pageがユーザ仮想メモリにマッピングされている場合、1を返して終了
		if (PageReserved(old_page))
			++mm->rss;
  • old_pageが予約されているか調べる
    • 予約されている場合1を返し、予約されていない場合は0を返す
    • 詳細はPageReserved()/linux2.6を参照
		else
			page_remove_rmap(old_page);
		break_cow(vma, new_page, address, page_table);
  • new_pageに対応するページテーブルエントリをpage_tableへ複製する
		lru_cache_add_active(new_page);
  • new_pageを回収リストへ渡す
  • ページnew_pageをLRUキャッシュ(lru_add_active_pvecs)へ加える
		page_add_anon_rmap(new_page, vma, address);
		/* Free the old page.. */
		new_page = old_page;
  • new_pageを解放する
	}
	pte_unmap(page_table);
  • CONFIG_HIGHPTEが無効である場合、処理を行わない
	page_cache_release(new_page);
	page_cache_release(old_page);
	spin_unlock(&mm->page_table_lock);
  • スピンロックmm->page_table_lockの開放を行う。プリエンプション機能を有効にし、可能であれば自ら積極的にプリエンプション(実行権の移譲を行うこと)する
	return VM_FAULT_MINOR;
  • VM_FAULT_MINORは副ページフォルト(カレントプロセスがブロックされることなくページフォルト処理できた)を意味する

no_new_page:

	page_cache_release(old_page);
	return VM_FAULT_OOM;
  • VM_FAULT_OOMはメモリ不足であったことを意味する

}

呼出元


履歴

  • 作者:ひら
  • 日付:2005/10/3
  • 対象:2.6.10
    更新日更新者更新内容

コメント



トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2009-11-24 (火) 07:12:28 (2919d)