ハイメモリ(その2)
ハイメモリは永続的カーネルマッピングで、そのページフレームを使用するプロセスの遅延を許すものでしたが、遅延を許されない使用方として、一時的カーネルマッピングと言うのがあります。これはまさにバンク切り替えで、バンク切り替えをページエントリテーブルを書き換える事で実現しています。なお、一時カーネルマッピングして、速やかにそのページフレームにデータを書き込むなり、読み込むなりして、アンマップする必要(マップしている状態で、他のプロセスがマップしにいくと、そのまま何も考えずに、新しいアドレスでマップしに行くみたいです...)があります。
一時カーネルマッピングのページエントリテーブルとして、固定マップ用リニアアドレスとして、enum fixed_addressesが定義されており、そのFIX_KMAP_BEGINからFIX_KMAP_ENDの間になります。__fix_to_virtマクロは固定マップ用リニアアドレスに変換します。引数のxはfixed_addressesのメンバーです。そのマクロはFIXADDR_TOP - ((x) << PAGE_SHIFT)となっています(+でなく-)。すなわち固定マップ用リニアアドレスアドレスは、FIXADDR_TOP=0xfffff000から低位に向かって配置されていきます。
FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1となっています。KM_TYPE_NRは一時カーネルマッピングのエントリの数で、NR_CPUSはCPUの数です。すなわちそのエントリはCPU毎に独自に有していると言うことです。
一時カーネルマッピングの特性故(すぐに読み書きして解放する。)、ということでしょうか?
pageがハイメモリでないなら、page_addressでリニアアドレスを取得してお終いです。ハイメモリならenum km_type typeから、idx = type + KM_TYPE_NR*smp_processor_id()でそのエントリを求めます。そのエントリをset_pte関数で、pteに書き込んで、arch_flush_lazy_mmu_mode関数でTLBを無効にしてお終いです。
一時カーネルマッピングのページエントリテーブルとして、固定マップ用リニアアドレスとして、enum fixed_addressesが定義されており、そのFIX_KMAP_BEGINからFIX_KMAP_ENDの間になります。__fix_to_virtマクロは固定マップ用リニアアドレスに変換します。引数のxはfixed_addressesのメンバーです。そのマクロはFIXADDR_TOP - ((x) << PAGE_SHIFT)となっています(+でなく-)。すなわち固定マップ用リニアアドレスアドレスは、FIXADDR_TOP=0xfffff000から低位に向かって配置されていきます。
FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1となっています。KM_TYPE_NRは一時カーネルマッピングのエントリの数で、NR_CPUSはCPUの数です。すなわちそのエントリはCPU毎に独自に有していると言うことです。
#define __fix_to_virt(x) (FIXADDR_TOP - ((x) << PAGE_SHIFT)) enum fixed_addresses { FIX_HOLE, FIX_VDSO, FIX_DBGP_BASE, FIX_EARLYCON_MEM_BASE, : FIX_KMAP_BEGIN, /* reserved pte's for temporary kernel mappings */ FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1, : __end_of_fixed_addresses };enum km_typeがFIX_KMAP_BEGINからのエントリとなります。KM_BOUNCE_READならそのエントリはFIX_KMAP_BEGIN+KM_BOUNCE_READ,KM_SKB_SUNRPC_DATAならFIX_KMAP_BEGIN+KM_SKB_SUNRPC_DATAという具合です。エントリはその使用方法でカテゴリ化されており、できるだけ異なる処理でエントリの競合が起きないよう配慮されています。ただこのカテゴリによる処理の違いはなさそうです。基本的にはどれを使ってもよさそうみたいです。
enum km_type { D(0) KM_BOUNCE_READ, D(1) KM_SKB_SUNRPC_DATA, D(2) KM_SKB_DATA_SOFTIRQ, D(3) KM_USER0, D(4) KM_USER1, D(5) KM_BIO_SRC_IRQ, D(6) KM_BIO_DST_IRQ, D(7) KM_PTE0, D(8) KM_PTE1, D(9) KM_IRQ0, D(10) KM_IRQ1, D(11) KM_SOFTIRQ0, D(12) KM_SOFTIRQ1, D(13) KM_TYPE_NR };マップ処理はkmap_atomic関数から、kmap_atomic_prot関数をコールすることで行われます。kmap_protは__PAGE_KERNELと定義されており、従って_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | |
_PAGE_ACCESSED | _PAGE_GLOBAL |
_PAGE_PRESENT | ページが存在している |
_PAGE_RW | 読み書き可能 |
_PAGE_DIRTY | ページが汚れている |
_PAGE_ACCESSED | ページにアクセスあり |
_PAGE_GLOBAL | ? |
_PAGE_NX | 実行不可 |
#define kmap_prot _PAGE_KERNEL #define __PAGE_KERNEL (__PAGE_KERNEL_EXEC | _PAGE_NX) #define __PAGE_KERNEL_EXEC \ (_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_ACCESSED | _PAGE_GLOBAL) void *kmap_atomic(struct page *page, enum km_type type) { return kmap_atomic_prot(page, type, kmap_prot); }kmap_atomic_prot関数でマップします。まず、pagefault_disable関数はプリエンプションカウンターをインクリメントし、プリエンプションを不可とします(他のカーネルパスによりマップされないため)。
pageがハイメモリでないなら、page_addressでリニアアドレスを取得してお終いです。ハイメモリならenum km_type typeから、idx = type + KM_TYPE_NR*smp_processor_id()でそのエントリを求めます。そのエントリをset_pte関数で、pteに書き込んで、arch_flush_lazy_mmu_mode関数でTLBを無効にしてお終いです。
void *kmap_atomic_prot(struct page *page, enum km_type type, pgprot_t prot) { enum fixed_addresses idx; unsigned long vaddr; pagefault_disable(); if (!PageHighMem(page)) return page_address(page); debug_kmap_atomic_prot(type); idx = type + KM_TYPE_NR*smp_processor_id(); vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx); BUG_ON(!pte_none(*(kmap_pte-idx))); set_pte(kmap_pte-idx, mk_pte(page, prot)); arch_flush_lazy_mmu_mode(); return (void *)vaddr; }実装そのものは単純故、負荷の軽い処理としての利用という事のようです。