kmap
kmap()は取得したページの仮想アドレスを返します。ZONE_DMA/ZONE_NORMALおよびZONE_HIGHMEM(ハイメモリ)から取得したページにより処理が異なります。カーネル空間は物理アドレス、0から864Mまでをストーレートにマップしていきます(x86-32)。864M以降は、動的にZONE_HIGHMEMに割り当てられることになります。
予約されているページテーブル数は、LAST_PKMAPで定義されていて、CONFIG_X86_PAEに応じて、512ないし1024となります。PAEは物理アドレス拡張機能というもので、物理ページが64Gとなるようにしたものです。従ってページテーブルはアライメント単位ということで、ページテーブルは従来の倍の64ビットになるようです。なお、ページ長を拡張できる機能もPentium Proには導入されたらしいのですが、Linuxではこの機能は使っておらず、ページ長を4Kとして実装しているとのことです。
従ってPAEを有効とした場合、割り当てられるページテーブルは4096/8=512ケ、PAE無効の場合、4096/4=1024ケ。ページサイズが4Kですから、2M,4Mの空間を利用できるということです。カーネルは896Mをストレートマップとし(かかる仮想アドレスも)、そうなると200M以上も空きがるのにと・・・。これはkmap_atomic()用として使われるからだと思います。kmap_atomic()はマップを要求したプロセスをウエイトさせません。この実装のため、CPU毎にこの仮想アドレス空間を,、それぞれに予約しているみたいです。(たぶん)
PageHighMem()でページがZONE_HIGHMEMから取得されたものかどうかチェックします。struct pageにはflagにどのゾーンから取得したかの情報が設定されています。
もし、ZONE_HIGHMEMでない場合、pageはストレートマップされているページです。page_address()でその仮想アドレスを取得します。ZONE_HIGHMEMの場合、kmap_high()でその処理を行います。
もし、ページに仮想アドレスがまだ設定されていないなら、map_new_virtual()で仮想アドレスを設定します。なおmap_new_virtual()から復帰した時、先のpkmap_count[]には1が設定されていて、kmap()で新規にページに仮想アドレスを割り当てた場合、プロセスに復帰した時点で、pkmap_count[]は2になるわけです。
last_pkmap_nrは前回検索した位置で、そこから検索します。検索する毎にインクリメントし、LAST_PKMAP_MASKで&としているのは、リングバッファーのインデックスの処理のように、LAST_PKMAPを越えた時の対応です。
検索位置が、先頭になったとき、flush_all_zero_pkmaps()をコールします。この処理はpkmap_count[]の1の箇所をクリアすることにあります。
pkmap_count[]の0があれば、仮想アドレスが割り当てられます。そしてすべて走査して割り当てられなかったら、pkmap_map_waitウエイトキューにプロセスをリストし、schedule()で実行を開け渡します。
他のプロセスがkunmap()を呼び出した時に、schedule()から復帰します。remove_wait_queue()で先にウエイトリストした自プロセスを切離し、まず目的とするページが仮想アドレスを有しているかチェックします。これは同じページでウエイトしている他のスレッドが先に起床されて、すでに同じページに仮想アドレスを割り当てられたケースだと思います(たぶん)。
もし、まだ仮想アドレスが割り当てられていないなら、goto startで再度同じ処理を行います。今回はkunmap()で、アンマップされたpkmap_count[]が1になっているはずですから、flush_all_zero_pkmaps()がコールされ、そこでpkmap_count[]が0となり、結果的に仮想アドレスが割り当てられるわけです。
ループを抜けると、割り当てられたpkmap_count[]のインデックスlast_pkmap_nから、仮想アドレスをPKMAP_ADDRマクロで取得し、set_pte_at()でその物理アドレスおよび仮想アドレスをページテーブルに設定します。
割り当てたpkmap_count[]を1とします。これはページは仮想アドレスを有しているが、どのプロセスも参照していない。と言うことです。最後にset_page_address()でこのページをpage_address_poolをヘッドとするリストから取得し、page_address_htableのリストに繋ぎます。このリストはページが仮想アドレスを有しているかどうかのチェックに使われます。
予約されているページテーブル数は、LAST_PKMAPで定義されていて、CONFIG_X86_PAEに応じて、512ないし1024となります。PAEは物理アドレス拡張機能というもので、物理ページが64Gとなるようにしたものです。従ってページテーブルはアライメント単位ということで、ページテーブルは従来の倍の64ビットになるようです。なお、ページ長を拡張できる機能もPentium Proには導入されたらしいのですが、Linuxではこの機能は使っておらず、ページ長を4Kとして実装しているとのことです。
補足
これでは、PAEの利点がないのでは?と思われるかも知れませんが、654Gのメモリをプロセスに割り当てられる事が可能となり、たくさんのプロセスを動作する事のへ恩恵が受けられるわけです。ただ、仮想アドレスが増えるわけではありません。従ってPAEを有効とした場合、割り当てられるページテーブルは4096/8=512ケ、PAE無効の場合、4096/4=1024ケ。ページサイズが4Kですから、2M,4Mの空間を利用できるということです。カーネルは896Mをストレートマップとし(かかる仮想アドレスも)、そうなると200M以上も空きがるのにと・・・。これはkmap_atomic()用として使われるからだと思います。kmap_atomic()はマップを要求したプロセスをウエイトさせません。この実装のため、CPU毎にこの仮想アドレス空間を,、それぞれに予約しているみたいです。(たぶん)
#ifdef CONFIG_X86_PAE #define LAST_PKMAP 512 #else #define LAST_PKMAP 1024 #endifまず、might_sleep()で他に動作する必要にあるプロセスがあるかどうかチェックします。あるなら実行権を譲渡します。kmap()は呼び出したプロセスがウエイトする前提のマップ処理ですから問題ありません。
PageHighMem()でページがZONE_HIGHMEMから取得されたものかどうかチェックします。struct pageにはflagにどのゾーンから取得したかの情報が設定されています。
もし、ZONE_HIGHMEMでない場合、pageはストレートマップされているページです。page_address()でその仮想アドレスを取得します。ZONE_HIGHMEMの場合、kmap_high()でその処理を行います。
void *kmap(struct page *page) { might_sleep(); if (!PageHighMem(page)) return page_address(page); return kmap_high(page); }page_address()からlowmem_page_address()呼ぶことで、ストレートマップされてページの仮想アドレスを取得します。ページからページフレーム番号(物理アドレスの下位からページサイズ毎に振られた番号)に、ページサイズをかけた物にPAGE_OFFSET=0xC0000000を加算すれば、それが仮想アドレスとなるわけです。
#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET)) #define PFN_PHYS(x) ((phys_addr_t)(x) << PAGE_SHIFT) static __always_inline void *lowmem_page_address(const struct page *page) { return __va(PFN_PHYS(page_to_pfn(page))); }ZONE_HIGHMEMから取得したページはkmap_high()で処理されます。まずpage_address()でページがすでに仮想アドレスを取得しているかチェックします。これはマルチスレッドでメモリを共有しているケースだと思います。もし取得されているなら、仮想アドレスに対するインデックスのら、参照カウンターとしてpkmap_count[]++いたします。
もし、ページに仮想アドレスがまだ設定されていないなら、map_new_virtual()で仮想アドレスを設定します。なおmap_new_virtual()から復帰した時、先のpkmap_count[]には1が設定されていて、kmap()で新規にページに仮想アドレスを割り当てた場合、プロセスに復帰した時点で、pkmap_count[]は2になるわけです。
void *kmap_high(struct page *page) { unsigned long vaddr; lock_kmap(); vaddr = (unsigned long)page_address(page); if (!vaddr) vaddr = map_new_virtual(page); pkmap_count[PKMAP_NR(vaddr)]++; BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2); unlock_kmap(); return (void*) vaddr; }long map_new_virtual()で仮想アドレスをページに割り当てます。LAST_PKMAPは割り当てられる数です。処理としてはpkmap_count[]で0となってりうエントリを探すことにあります。
last_pkmap_nrは前回検索した位置で、そこから検索します。検索する毎にインクリメントし、LAST_PKMAP_MASKで&としているのは、リングバッファーのインデックスの処理のように、LAST_PKMAPを越えた時の対応です。
検索位置が、先頭になったとき、flush_all_zero_pkmaps()をコールします。この処理はpkmap_count[]の1の箇所をクリアすることにあります。
pkmap_count[]の0があれば、仮想アドレスが割り当てられます。そしてすべて走査して割り当てられなかったら、pkmap_map_waitウエイトキューにプロセスをリストし、schedule()で実行を開け渡します。
他のプロセスがkunmap()を呼び出した時に、schedule()から復帰します。remove_wait_queue()で先にウエイトリストした自プロセスを切離し、まず目的とするページが仮想アドレスを有しているかチェックします。これは同じページでウエイトしている他のスレッドが先に起床されて、すでに同じページに仮想アドレスを割り当てられたケースだと思います(たぶん)。
もし、まだ仮想アドレスが割り当てられていないなら、goto startで再度同じ処理を行います。今回はkunmap()で、アンマップされたpkmap_count[]が1になっているはずですから、flush_all_zero_pkmaps()がコールされ、そこでpkmap_count[]が0となり、結果的に仮想アドレスが割り当てられるわけです。
ループを抜けると、割り当てられたpkmap_count[]のインデックスlast_pkmap_nから、仮想アドレスをPKMAP_ADDRマクロで取得し、set_pte_at()でその物理アドレスおよび仮想アドレスをページテーブルに設定します。
割り当てたpkmap_count[]を1とします。これはページは仮想アドレスを有しているが、どのプロセスも参照していない。と言うことです。最後にset_page_address()でこのページをpage_address_poolをヘッドとするリストから取得し、page_address_htableのリストに繋ぎます。このリストはページが仮想アドレスを有しているかどうかのチェックに使われます。
static int pkmap_count[LAST_PKMAP]; static inline unsigned long map_new_virtual(struct page *page) { unsigned long vaddr; int count; start: count = LAST_PKMAP; for (;;) { last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK; if (!last_pkmap_nr) { flush_all_zero_pkmaps(); count = LAST_PKMAP; } if (!pkmap_count[last_pkmap_nr]) break; /* Found a usable entry */ if (--count) continue; { DECLARE_WAITQUEUE(wait, current); __set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&pkmap_map_wait, &wait); unlock_kmap(); schedule(); remove_wait_queue(&pkmap_map_wait, &wait); lock_kmap(); if (page_address(page)) return (unsigned long)page_address(page); goto start; } } vaddr = PKMAP_ADDR(last_pkmap_nr); set_pte_at(&init_mm, vaddr, &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot)); pkmap_count[last_pkmap_nr] = 1; set_page_address(page, (void *)vaddr); return vaddr; }flush_all_zero_pkmaps()はpkmap_count[i]=1を、0にして、対応するページテーブルをクリアします。そしてこのページを管理しているpage_address_htableから、page_address_poolへと戻しています。これで名実共にそのページの仮想アドレスは消滅したことになります。
static void flush_all_zero_pkmaps(void) { int i; int need_flush = 0; flush_cache_kmaps(); for (i = 0; i < LAST_PKMAP; i++) { struct page *page; if (pkmap_count[i] != 1) continue; pkmap_count[i] = 0; BUG_ON(pte_none(pkmap_page_table[i])); page = pte_page(pkmap_page_table[i]); pte_clear(&init_mm, (unsigned long)page_address(page), &pkmap_page_table[i]); set_page_address(page, NULL); need_flush = 1; } if (need_flush) flush_tlb_kernel_range(PKMAP_ADDR(0), PKMAP_ADDR(LAST_PKMAP)); }