Linux カーネルが使用する仮想アドレスには、次の 2 種類があります。
- 「カーネルメモリ(仮想)は物理メモリに直接対応します(0xC000_0000でオフセットするだけで物理アドレスが得られます)」という行ですでに述べたこと。これは、連続した物理アドレスにマップされます。
- vmalloc を使用します。
最初のものは MACRO を使用して行われます。
include/asm-x86/page_32.h
#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))
_pa(x) は、仮想から物理への変換を行います。この変換はコンパイル時にインラインで行われることに注意してください。ページ テーブルの変換は行われません。この最後の文は非常に重要です。
一方、2 番目の方法を使用すると、仮想メモリでは連続しているメモリを割り当てることができますが、物理メモリではそうではない場合があります。この場合、初めて仮想アドレスにアクセスするときに、ページ テーブル全体の変換が必要になります。問題は、誰がこれを行うかです。
CISC マシン (x86 など) の場合、TLB ミス (仮想アドレスへの初回アクセス) の場合、MMU (ハードウェア) がそれを行い、ページ テーブルを更新します。カーネルの仮想アドレス (vmalloc で取得) は、TLB エントリとして保持されます。それらはグローバル エントリと呼ばれ、プロセス コンテキスト スイッチが発生すると、ほとんど無視され、残りのプロセス アドレス空間エントリのようにフラッシュされません。ただし、関連付けられている仮想メモリを解放するために vfree を実行すると、それらのエントリは削除されます。
RISC マシン (MIPS など) の場合、ページ変換はソフトウェアによって処理されます。TLB ミスの後、ハードウェアは例外を発生させます。トラップ ハンドルはカーネル モードで実行され、変換を行い、特別な命令を使用して TLB を更新します。トラップ ハンドラから戻った後、同じコード行が実行され、TLB ヒットが発生します。
参照してください: http://pages.cs.wisc.edu/~remzi/OSFEP/vm-tlbs.pdf
肝心なのは、すべてのカーネルアドレスがあなたが説明した方法でマップされているわけではないということです。あなたの場合、物理アドレスはコンパイル時に生成されます。では、なぜ TLB エントリを追加するのでしょうか。vmalloc からのアドレスの場合、TLB エントリが存在します。プロセス間でコンテキストの切り替えが発生した場合、TLB 全体をフラッシュする必要はなく、カーネルの vmalloc によって作成されたグローバル エントリを保持できます。vfree を使用すると、対応するグローバル エントリがフラッシュされます。