Linux カーネルのページ テーブル管理に混乱を感じます。
Linux カーネル空間では、ページ テーブルがオンになる前。カーネルは 1 対 1 のマッピング メカニズムを使用して仮想メモリで実行されます。ページ テーブルがオンになった後、カーネルはページ テーブルを参照して仮想アドレスを物理メモリ アドレスに変換します。質問は次のとおりです。
この時点で、ページ テーブルをオンにした後、カーネル空間はまだ 1GB (0xC0000000 - 0xFFFFFFFF) ですか?
また、カーネル プロセスのページ テーブルでは、0xC0000000 - 0xFFFFFFFF の範囲のページ テーブル エントリ (PTE) のみがマップされます。この範囲外の PTE は、カーネル コードがそこにジャンプすることはないため、マップされません。
ページ テーブルを有効にする前と有効にした後のアドレスのマッピングは同じですか?
たとえば、ページ テーブルを有効にする前は、仮想アドレス 0xC00000FF が物理アドレス 0x000000FF にマッピングされていますが、ページ テーブルを有効にした後、上記のマッピングは変更されません。仮想アドレス 0xC00000FF は、引き続き物理アドレス 0x000000FF にマッピングされています。異なる点は、ページ テーブルを有効にした後、CPU がページ テーブルを参照して仮想アドレスを物理アドレスに変換することです。これは、以前は必要ありませんでした。カーネル空間のページ テーブルはグローバルであり、ユーザー プロセスを含むシステム内のすべてのプロセス間で共有されます。
このメカニズムは x86 32 ビットと ARM で同じですか?
ベストアンサー1
以下の説明は 32 ビット ARM Linux に基づいており、カーネル ソース コードのバージョンは 3.9 です。
初期ページ テーブル (後で関数によって上書きされます) を設定し、MMU をオンにする手順を実行すれば、すべての質問に回答できますpaging_init
。
カーネルがブートローダによって初めて起動されると、アセンブリ関数stext
(arch\arm\kernel\head.s 内) が最初に実行されます。この時点では MMU はまだオンになっていないことに注意してください。
とりわけ、この機能によって実行される 2 つのインポート ジョブは次のとおりstext
です。
- 初期ページテーブルを作成します(これは後で関数によって上書きされます
paging_init
) - MMUをオンにする
- カーネル初期化コードのC部分にジャンプして続行する
質問について深く掘り下げる前に、次の点を知っておくと役立ちます。
- MMUがオンになる前は、CPUが発行するすべてのアドレスは実在住所
- MMUがオンになると、CPUが発行するすべてのアドレスは仮想アドレス
- MMU をオンにする前に適切なページ テーブルを設定する必要があります。そうしないと、コードが「吹き飛ばされてしまう」ことになります。
- 慣例により、Linuxカーネルは仮想アドレスの上位1GB部分を使用し、ユーザーランドは下位3GB部分を使用する。
さて、ここからが難しい部分です。
最初のトリック: 位置非依存コードを使用します。アセンブリ関数 stext はPAGE_OFFSET + TEXT_OFFSET
仮想アドレスであるアドレス " "(0xCxxxxxxx) にリンクされていますが、MMU がまだオンになっていないため、アセンブリ関数 stext が実行されている実際のアドレスは物理PHYS_OFFSET + TEXT_OFFSET
アドレスである " " (実際の値は実際のハードウェアによって異なります) です。
つまり、関数のプログラムは、stext
0xCxxxxxxxxx のようなアドレスで実行されていると「考え」ますが、実際にはアドレス (0x00000000 + some_offeset) で実行されています (ハードウェアが 0x00000000 を RAM の開始点として構成しているとします)。したがって、MMU をオンにする前に、アセンブリ コードを非常に慎重に記述して、実行手順中に問題が起こらないようにする必要があります。実際には、位置独立コード (PIC) と呼ばれる技術が使用されます。
上記をさらに説明するために、いくつかのアセンブリ コード スニペットを抜粋します。
ldr r13, =__mmap_switched @ address to jump to after MMU has been enabled
b __enable_mmu @ jump to function "__enable_mmu" to turn on MMU
上記の「ldr」命令は、「関数__mmap_switchedの(仮想)アドレスを取得してr13に格納する」ことを意味する疑似命令であることに注意してください。
そして、関数 __enable_mmu は関数 __turn_mmu_on を呼び出します: (関数 __turn_mmu_on から、関数に必須の命令であるがここでは重要ではないいくつかの命令を削除したことに注意してください)
ENTRY(__turn_mmu_on)
mcr p15, 0, r0, c1, c0, 0 @ write control reg to enable MMU====> This is where MMU is turned on, after this instruction, every address issued by CPU is "virtual address" which will be translated by MMU
mov r3, r13 @ r13 stores the (virtual) address to jump to after MMU has been enabled, which is (0xC0000000 + some_offset)
mov pc, r3 @ a long jump
ENDPROC(__turn_mmu_on)
2番目のトリック: MMU をオンにする前に初期ページ テーブルを設定するときに、同一のマッピングが行われます。具体的には、カーネル コードが実行されている同じアドレス範囲が 2 回マッピングされます。
- 最初のマッピングは、予想どおり、アドレス範囲 0x00000000 (このアドレスはハードウェア構成によって異なります) から (0x00000000 + オフセット) までを 0xCxxxxxxx から (0xCxxxxxxx + オフセット) までマッピングします。
- 興味深いことに、2 番目のマッピングでは、アドレス範囲 0x00000000 から (0x00000000 + オフセット) までがそれ自体にマッピングされます (つまり、0x00000000 --> (0x00000000 + オフセット))
なぜそうするのでしょうか? MMU がオンになる前は、CPU によって発行されるすべてのアドレスは物理アドレス (0x00000000 から始まる) であり、MMU がオンになった後は、CPU によって発行されるすべてのアドレスは仮想アドレス (0xC0000000 から始まる) であることを覚えておいてください。ARM は
パイプライン構造であるため、MMU がオンになった瞬間、MMU がオンになる前に CPU によって生成された (物理) アドレスを使用している命令が ARM のパイプラインにまだ存在します。これらの命令が爆発するのを回避するには、それらに対応するために同一のマッピングを設定する必要があります。
さて、あなたの質問に戻ります。
- この時点で、ページ テーブルをオンにした後、カーネル空間はまだ 1GB (0xC0000000 - 0xFFFFFFFF) ですか?
A: MMU をオンにするという意味だと思います。答えはイエスです。カーネル スペースは 1GB です (実際には 0xC0000000 より下の数メガバイトも占有しますが、これは私たちの関心事ではありません)。
- また、カーネル プロセスのページ テーブルでは、0xC0000000 - 0xFFFFFFFF の範囲のページ テーブル エントリ (PTE) のみがマップされます。この範囲外の PTE は、カーネル コードがそこにジャンプすることはないため、マップされません。
A: この質問に対する回答は、特定のカーネル構成に関する多くの詳細が含まれるため、かなり複雑です。この質問に
完全に答えるには、初期ページ テーブルを設定するカーネル ソース コードの部分 (アセンブリ関数__create_page_tables
) と最終ページ テーブルを設定する関数 (C 関数 paging_init) を読む必要があります。
簡単に言うと、ARM には 2 つのレベルのページ テーブルがあり、最初のページ テーブルは PGD で、16KB を占めます。カーネルは、初期化プロセス中に最初にこの PGD をゼロにして、アセンブリ関数 で初期マッピングを行います__create_page_tables
。関数 では__create_page_tables
、アドレス空間のごく一部のみがマップされます。
その後、関数 で最終ページ テーブルが設定されpaging_init
、この関数では、アドレス空間のかなり大きな部分がマップされます。たとえば、512M の RAM しかない場合、最も一般的な構成では、この 512M の RAM はカーネル コードによってセクションごとにマッピングされます (1 セクションは 1MB)。RAM が非常に大きい場合 (2GB など)、RAM の一部のみが直接マップされます。 (質問2については詳細が多すぎるのでここでやめておきます)
- ページ テーブルをオンにする前と後のマッピング アドレスは同じですか?
A: この質問については、「2 番目のトリック: MMU をオンにする前に初期ページ テーブルを設定するときに同一のマッピングを行う」の説明で既に回答していると思います。
4. カーネル空間のページ テーブルはグローバルであり、ユーザー プロセスを含むシステム内のすべてのプロセス間で共有されます。
A: はい、またいいえです。はい。すべてのプロセスがカーネル ページ テーブル (上位 1 GB 部分) の同じコピー (コンテンツ) を共有するためです。いいえ。各プロセスが独自の 16 KB メモリを使用してカーネル ページ テーブルを格納するためです (上位 1 GB 部分のページ テーブルの内容はすべてのプロセスで同一です)。
5. このメカニズムは x86 32 ビットと ARM で同じですか?
異なるアーキテクチャでは異なるメカニズムが使用される