18

Linux カーネルのページテーブル管理に混乱を感じていますか?

Linux カーネル空間で、ページ テーブルがオンになる前。カーネルは、1 対 1 のマッピング メカニズムを使用して仮想メモリで実行されます。ページ テーブルがオンになると、カーネルはページ テーブルを参照して、仮想アドレスを物理メモリ アドレスに変換します。質問は次のとおりです。

  1. この時点で、ページ テーブルをオンにした後、カーネル スペースはまだ 1GB (0xC0000000 から 0xFFFFFFFF まで) ですか?

  2. また、カーネル プロセスのページ テーブルでは、0xC0000000 ~ 0xFFFFFFFF の範囲のページ テーブル エントリ (PTE) のみがマップされます。カーネル コードがそこにジャンプしないため、PTE がこの範囲外にある場合はマップされませんか?

  3. ページテーブルをオンにする前後のマッピングアドレスは同じですか?

    例えば。ページ テーブルをオンにする前に、仮想アドレス 0xC00000FF が物理アドレス 0x000000FF にマップされ、ページ テーブルをオンにした後、上記のマッピングは変更されません。仮想アドレス 0xC00000FF は引き続き物​​理アドレス 0x000000FF にマップされます。異なる点は、ページ テーブルをオンにした後、CPU がページ テーブルを参照して仮想アドレスを物理アドレスに変換することだけです。これは以前は必要ありませんでした。

  4. カーネル空間のページ テーブルはグローバルであり、ユーザー プロセスを含むシステム内のすべてのプロセスで共有されますか?

  5. このメカニズムは、x86 32 ビットと ARM で同じですか?

4

2 に答える 2

19

以下の議論は 32 ビット ARM Linux に基づいており、カーネル ソース コードのバージョンは 3.9
です。最初のページ テーブルを設定し (関数によって後で上書きされますpaging_init)、 MMUで。

カーネルがブートローダーによって最初に起動されるとき、アセンブリ関数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" です (実際の値は、実際のアドレスによって異なります)。これは物理アドレスです。

関数のプログラムは、stext0xCxxxxxxx のようなアドレスで実行されていると「考え」ていますが、実際にはアドレス (0x00000000 + some_offeset) で実行されています (ハードウェアが RAM の開始点として 0x00000000 を構成しているとします)。そのため、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 のパイプラインに存在します。これらの指示が大げさになるのを避けるには、同じマッピングを設定して対応する必要があります。

質問に戻ります。

  1. この時点で、ページ テーブルをオンにした後、カーネル スペースはまだ 1GB (0xC0000000 から 0xFFFFFFFF まで) ですか?

A: MMU をオンにするという意味だと思います。答えはイエスです。カーネル空間は 1GB です (実際には 0xC0000000 より下の数メガバイトも占有しますが、それは私たちの関心事ではありません)

  1. また、カーネル プロセスのページ テーブルでは、0xC0000000 ~ 0xFFFFFFFF の範囲のページ テーブル エントリ (PTE) のみがマップされます。カーネル コードがそこにジャンプしないため、PTE がこの範囲外にある場合はマップされませんか?

A: この質問への回答は非常に複雑ですが、特定のカーネル構成に関する多くの詳細が含まれているためです。
この質問に完全に答えるには、カーネル ソース コードの初期ページ テーブルを設定する部分 (アセンブリ関数__create_page_tables) と最終ページ テーブルを設定する関数 (C 関数 paging_init) を読む必要があります。
簡単に言うと、ARM には 2 つのレベルのページ テーブルがあり、最初のページ テーブルは PGD で、16KB を占有します。カーネルは、初期化プロセス中に最初にこの PGD をゼロにし、アセンブリ関数で初期マッピングを行います__create_page_tables。function__create_page_tablesでは、アドレス空間のごく一部のみがマップされます。
その後、最終的なページテーブルが関数に設定されますpaging_initであり、この関数では、アドレス空間のかなりの部分がマップされます。512M RAM しかない場合、最も一般的な構成では、この 512M-RAM はカーネル コード セクションごとにマッピングされます (1 セクションは 1MB)。RAM が非常に大きい場合 (たとえば 2GB)、RAM の一部のみが直接マップされます。(設問2については詳細が多すぎるのでここで止めます)

  1. ページテーブルをオンにする前後のマッピングアドレスは同じですか?

A: この質問については、「第 2 のトリック: MMU を有効にする前に初期ページ テーブルを設定する際の同一マッピング」の説明で既に回答済みだと思います。

4 . カーネル空間のページ テーブルはグローバルであり、ユーザー プロセスを含むシステム内のすべてのプロセスで共有されますか?

A: はい、いいえ。はい、すべてのプロセスがカーネル ページ テーブル (上位 1GB 部分) の同じコピー (コンテンツ) を共有するためです。いいえ、各プロセスは独自の 16KB メモリを使用してカーネル ページ テーブルを格納するためです (ただし、上位 1GB 部分のページ テーブルの内容はすべてのプロセスで同じです)。

5. このメカニズムは、x86 32 ビットと ARM で同じですか?

異なるアーキテクチャは異なるメカニズムを使用します

于 2014-12-03T07:50:36.963 に答える