ユーザー空間とカーネル空間の違いは何ですか?


72

カーネルがユーザープログラム、つまりシステムコールに代わって実行されているときに、カーネルスペースが使用されていますか?それとも、すべてのカーネルスレッド(スケジューラなど)のアドレススペースですか?

それが最初のものである場合、それは通常のユーザープログラムが3GB以上のメモリを持つことができないことを意味します(分割が3GB + 1GBの場合)?また、その場合、カーネルはどのようにしてハイメモリを使用できますか?なぜなら、1GBのカーネルスペースが論理的にマッピングされるため、ハイメモリのページはどの仮想メモリアドレスにマップされるのですか?

回答:


93

カーネルがユーザープログラム、つまりシステムコールに代わって実行されているときに、カーネルスペースが使用されていますか?それとも、すべてのカーネルスレッド(スケジューラなど)のアドレススペースですか?

はい、はい。

さらに先に進む前に、記憶についてこれを述べる必要があります。

メモリは2つの異なる領域に分けられます。

  • ユーザー空間。通常のユーザープロセスが実行される場所のセットです(つまり、カーネル以外のすべて)。カーネルの役割は、このスペースで実行されているアプリケーションが互いに干渉したり、マシンを混乱させないようにすることです。
  • カーネルのスペース。これは、カーネルのコードが格納され、その下で実行される場所です。

ユーザー空間で実行されるプロセスはメモリの限られた部分にのみアクセスできますが、カーネルはすべてのメモリにアクセスできます。ユーザー空間で実行されているプロセスも、カーネル空間にアクセスできません。ユーザー空間プロセスは、カーネルによって公開されたインターフェースを介してカーネルのごく一部にしかアクセスできません - システムコール。プロセスがシステムコールを実行すると、ソフトウェア割り込みがカーネルに送信され、カーネルは適切な割り込みハンドラーをディスパッチし、ハンドラーが終了した後も作業を継続します。

カーネルスペースコードには、「カーネルモード」で実行するプロパティがあります。これは(通常のデスクトップ-x86-コンピューターで)リング0の下で実行するコードと呼ばれるものです。通常、x86アーキテクチャには4つの保護リングがあります。リング0(カーネルモード)、リング1(仮想マシンハイパーバイザーまたはドライバーで使用される場合があります)、リング2(ドライバーで使用される場合がありますが、それについてはよくわかりません)。リング3は、典型的なアプリケーションが実行されるものです。これは最小特権のリングであり、その上で実行されるアプリケーションは、プロセッサの命令のサブセットにアクセスできます。リング0(カーネルスペース)は最も特権的なリングであり、マシンのすべての命令にアクセスできます。たとえば、「プレーンな」アプリケーション(ブラウザなど)はx86アセンブリ命令を使用できませんlgdtグローバル記述子テーブルをロードするかhlt、プロセッサを停止します。

それが最初のものである場合、それは通常のユーザープログラムが3GB以上のメモリを持つことができないことを意味します(分割が3GB + 1GBの場合)?また、その場合、カーネルはどのようにしてハイメモリを使用できますか?なぜなら、1GBのカーネルスペースが論理的にマッピングされるため、ハイメモリのページはどの仮想メモリアドレスにマップされるのですか?

これに対する答えのために、ことにより、優れた解答を参照してください振る こちら


4
どこかで間違いを犯したかどうかをtellしないでください。私はカーネルプログラミングが初めてで、ここで学んだことと、ウェブ上で見つけた他の情報をここにダンプしました。これは、本文に示されている概念に対する私の理解に欠陥がある可能性があることを意味します。
NlightNFotis

ありがとう!今ではもっとよく理解できたと思います。正しく取得するために、もう1つ質問があります。繰り返しますが、最初の3GBがユーザースペースに使用され、128MBのカーネルスペースが高メモリに使用されることを考慮すると、残りの896MB(低メモリ)はブート時に静的にマッピングされますか?
プージャン

1
@NlightNFotis私は、ほぼ15人が、あなたが言ったことは何でも正しいと信じていると言います(または、あなたが私たちに考えさせる;))
Braiam

x86リング-1はハイパーバイザー用だと思いましたか?en.wikipedia.org/wiki/Protection_ring
Dori

1
仮想メモリと物理メモリの違いに注意してください。質問のほとんどは、仮想メモリに関するものです。これは物理メモリにマッピングされ、物理メモリが3GBに近づくにつれて複雑になり、PAEが使用されます。64ビットカーネルを使用すると、再び単純になります。この場合、負のアドレスはカーネル用に予約され、正のアドレスはユーザー空間用に予約されます。32ビットプロセスは4GBの仮想スペースを使用できるようになりました。64ビットプロセスはさらに多くを使用できます。通常は48ビットの価値があります(現時点ではx86-64)。
ctrl-alt-delor

16

CPUリングは最も明確な区別です

x86保護モードでは、CPUは常に4つのリングのいずれかにあります。Linuxカーネルは0と3のみを使用します。

  • カーネルの場合は0
  • ユーザー向け3

これは、カーネルとユーザーランドの最もハードで高速な定義です。

Linuxがリング1と2を使用しない理由:https : //stackoverflow.com/questions/6710040/cpu-privilege-rings-why-rings-1-and-2-arent-used

現在のリングはどのように決定されますか?

現在のリングは、次の組み合わせで選択されます。

  • グローバル記述子テーブル:GDTエントリのメモリ内テーブル。各エントリにはPrivlリングをエンコードするフィールドがあります。

    LGDT命令は、アドレスを現在の記述子テーブルに設定します。

    参照:http : //wiki.osdev.org/Global_Descriptor_Table

  • セグメントはGDTのエントリのインデックスを指すCS、DSなどを登録します。

    たとえばCS = 0、GDTの最初のエントリが実行中のコードに対して現在アクティブであることを意味します。

各リングは何ができますか?

CPUチップは、次のように物理的に構築されています。

  • リング0は何でもできます

  • リング3は、複数の命令を実行して複数のレジスタに書き込むことはできません。

    • 独自のリングを変更することはできません!そうしないと、リング自体をリング0に設定し、リングを使用できなくなります。

      つまり、現在のリングを決定する現在のセグメント記述子を変更できません。

    • ページテーブルを変更できません:https : //stackoverflow.com/questions/18431261/how-does-x86-paging-work

      つまり、CR3レジスタを変更できず、ページング自体がページテーブルの変更を妨げます。

      これにより、1つのプロセスが他のプロセスのメモリをセキュリティ/プログラミングの容易さの理由で見ることができなくなります。

    • 割り込みハンドラーを登録できません。それらはメモリ位置への書き込みによって設定されますが、これもページングによって防止されます。

      ハンドラーはリング0で実行され、セキュリティモデルを破壊します。

      つまり、LGDTおよびLIDT命令を使用できません。

    • inやなどのIO命令を実行できないoutため、任意のハードウェアアクセスが可能です。

      そうしないと、たとえば、プログラムがディスクから直接読み取ることができる場合、ファイルのアクセス許可が役に立たなくなります。

      Michael Petchのおかげです。実際には、OSはリング3でIO命令を許可することが実際に可能であり、これは実際にはタスク状態セグメントによって制御されます

      不可能なのは、リング3がそもそもそれを持っていなかった場合にそれを許可することです。

      Linuxは常にそれを許可しません。参照:https : //stackoverflow.com/questions/2711044/why-doesnt-linux-use-the-hardware-context-switch-via-the-tss

プログラムとオペレーティングシステムはリング間でどのように移行しますか?

  • CPUがオンになると、リング0で初期プログラムの実行が開始されます(それでもいいですが、おおよその近似値です)。この初期プログラムはカーネルであると考えることができます(ただし、通常はブートローダーであり、リング0のままカーネルを呼び出します)。

  • ユーザーランドプロセスがカーネルにファイルへの書き込みなどの処理を行わせる場合、カーネルは、int 0x80またはsyscallカーネルに信号を送るなどの割り込みを生成する命令を使用します。x86-64 Linux syscall hello worldの例:

    .data
    hello_world:
        .ascii "hello world\n"
        hello_world_len = . - hello_world
    .text
    .global _start
    _start:
        /* write */
        mov $1, %rax
        mov $1, %rdi
        mov $hello_world, %rsi
        mov $hello_world_len, %rdx
        syscall
    
        /* exit */
        mov $60, %rax
        mov $0, %rdi
        syscall
    

    コンパイルして実行:

    as -o hello_world.o hello_world.S
    ld -o hello_world.out hello_world.o
    ./hello_world.out
    

    GitHubアップストリーム

    これが発生すると、CPUはブート時にカーネルが登録した割り込みコールバックハンドラーを呼び出します。ハンドラを登録して使用する具体的なベアメタルの例を次に示します。

    このハンドラはリング0で実行され、カーネルがこのアクションを許可し、アクションを実行し、リング3のユーザーランドプログラムを再起動するかどうかを決定します。x86_64

  • ときにexecシステムコールを使用する(またはカーネルときに開始されます/init)、カーネルはレジスタおよびメモリ準備新しいユーザーランドプロセスが、それはエントリポイントにジャンプし、リング3にCPUを切り替えます

  • プログラムが(ページングのため)禁止されたレジスタまたはメモリアドレスへの書き込みのようないたずらをしようとすると、CPUはリング0でカーネルコールバックハンドラーも呼び出します。

    しかし、ユーザーランドはいたずらだったので、カーネルは今回プロセスを強制終了するか、シグナルで警告を出すかもしれません。

  • カーネルが起動すると、一定の周波数でハードウェアクロックが設定され、定期的に割り込みが生成されます。

    このハードウェアクロックは、リング0を実行する割り込みを生成し、起動するユーザーランドプロセスをスケジュールできるようにします。

    このように、プロセスがシステムコールを行っていない場合でも、スケジューリングを行うことができます。

複数のリングを持つことのポイントは何ですか?

カーネルとユーザーランドを分離することには、2つの大きな利点があります。

  • 一方が他方に干渉しないことがより確実であるため、プログラムを作成する方が簡単です。たとえば、あるユーザーランドプロセスは、ページングのために別のプログラムのメモリを上書きしたり、別のプロセスのハードウェアを無効な状態にすることを心配する必要はありません。
  • より安全です。たとえば、ファイルのアクセス許可とメモリの分離により、ハッキングアプリによる銀行データの読み取りが妨げられる可能性があります。もちろん、これはカーネルを信頼していることを前提としています。

それをいじる方法は?

リングを直接操作するのに適したベアメタルセットアップを作成しました:https : //github.com/cirosantilli/x86-bare-metal-examples

残念ながらユーザーランドの例を作成する忍耐はありませんでしたが、ページングのセットアップまで行ったので、ユーザーランドを実現できるはずです。プルリクエストをご覧ください。

あるいは、Linuxカーネルモジュールはリング0で実行されるため、それらを使用して特権操作を試すことができます。たとえば、制御レジスタを読み取ります。https//stackoverflow.com/questions/7415515/how-to-access-the-control-registers -cr0-cr2-cr3-from-a-program-getting-segmenta / 7419306#7419306

ここで便利なQEMU + Buildrootセットアップあなたのホストを殺すことなく、それを試しては。

カーネルモジュールの欠点は、他のkthreadが実行されており、実験に干渉する可能性があることです。しかし、理論的には、カーネルモジュールですべての割り込みハンドラを引き継いでシステムを所有することができます。これは実際には興味深いプロジェクトです。

負のリング

ネガティブリングは実際にはIntelマニュアルでは参照されていませんが、リング0自体よりもさらに機能があるCPUモードが実際に存在するため、「ネガティブリング」の名前に適しています。

1つの例は、仮想化で使用されるハイパーバイザーモードです。

詳細については、https//security.stackexchange.com/questions/129098/what-is-protection-ring-1を参照してください。

ARMでは、リングは代わりに例外レベルと呼ばれますが、主な考え方は同じです。

ARMv8には4つの例外レベルがあり、一般的に次のように使用されます。

  • EL0:ユーザーランド

  • EL1:カーネル(ARM用語では「スーパーバイザー」)。

    svc命令(SuperVisor Call)とともに入力されます。これは、swi 以前は統一アセンブリの前として知られており、Linuxシステム呼び出しを行うために使用される命令です。Hello world ARMv8の例:

    .text
    .global _start
    _start:
        /* write */
        mov x0, 1
        ldr x1, =msg
        ldr x2, =len
        mov x8, 64
        svc 0
    
        /* exit */
        mov x0, 0
        mov x8, 93
        svc 0
    msg:
        .ascii "hello syscall v8\n"
    len = . - msg
    

    GitHubアップストリーム

    Ubuntu 16.04のQEMUでテストします。

    sudo apt-get install qemu-user gcc-arm-linux-gnueabihf
    arm-linux-gnueabihf-as -o hello.o hello.S
    arm-linux-gnueabihf-ld -o hello hello.o
    qemu-arm hello
    

    SVCハンドラ登録し、SVC呼び出しを行う具体的なベアメタルの例を次に示します

  • EL2:ハイパーバイザー、たとえばXen

    入力したhvc命令(ハイパーバイザコール)。

    ハイパーバイザーはOSに対するものであり、OSはユーザーランドに対するものです。

    たとえば、Xenを使用すると、LinuxやWindowsなどの複数のOSを同じシステム上で同時に実行でき、Linuxがユーザーランドプログラムで実行するのと同じように、セキュリティとデバッグを容易にするためにOSを互いに分離します。

    ハイパーバイザーは、今日のクラウドインフラストラクチャの重要な部分です。複数のサーバーを単一のハードウェアで実行できるため、ハードウェアの使用率を常に100%近くに保ち、多くのお金を節約できます。

    たとえば、AWSは2017年までKVMへの移行がニュースになるまでXenを使用していました

  • EL3:さらに別のレベル。TODOの例。

    smc指示とともに入力(セキュアモードコール)

ARMv8アーキテクチャリファレンスモデルDDI 0487C.a -章D1 - AArch64システムレベルプログラマモデル-図D1-1が美しく、これを示しています。

ここに画像の説明を入力してください

おそらく後知恵の恩恵によるARMの特権レベルの命名規則が、負のレベルを必要とせずにx86よりも優れていることに注意してください。0は下位、3は最高です。高いレベルは、低いレベルよりも頻繁に作成される傾向があります。

現在のELは、MRS命令で照会できます:https : //stackoverflow.com/questions/31787617/what-is-the-current-execution-mode-exception-level-etc

ARMでは、チップ領域を節約する機能を必要としない実装を可能にするために、すべての例外レベルが存在する必要はありません。ARMv8の「例外レベル」には次のように記載されています。

実装には、すべての例外レベルが含まれない場合があります。すべての実装には、EL0とEL1を含める必要があります。EL2とEL3はオプションです。

たとえばQEMUはデフォルトでEL1ですが、EL2とEL3はコマンドラインオプションで有効にできます:https ://stackoverflow.com/questions/42824706/qemu-system-aarch64-entering-el1-when-emulating-a53-power-up

Ubuntu 18.10でテストされたコードスニペット


3

それが最初のものである場合、それは通常のユーザープログラムが3GB以上のメモリを持つことができないことを意味します(分割が3GB + 1GBの場合)?

はい、これは通常のLinuxシステムの場合です。一時的にユーザーとカーネルのアドレス空間を完全に独立させた「4G / 4G」パッチのセットがありました(カーネルがユーザーのメモリにアクセスするのを難しくしたため、パフォーマンスが低下しました)。それらはアップストリームにマージされ、x86-64の台頭により関心が衰えました。

また、その場合、カーネルはどのようにしてハイメモリを使用できますか?なぜなら、1GBのカーネルスペースが論理的にマッピングされるため、ハイメモリのページはどの仮想メモリアドレスにマップされるのですか?

Linuxの動作方法(およびアドレススペースに比べてメモリが小さいシステムでも)は、物理メモリ全体がアドレススペースのカーネル部分に永続的にマッピングされていました。これにより、カーネルは再マッピングなしですべての物理メモリにアクセスできましたが、明らかに大量の物理メモリを備えた32ビットマシンには対応していません。

それで、低記憶と高記憶の概念が生まれました。「低」メモリは、カーネルのアドレス空間に永続的にマップされます。「高」メモリはそうではありません。

プロセッサがシステムコールを実行しているときは、カーネルモードで実行されていますが、現在のプロセスのコンテキストで実行されています。そのため、現在のプロセスのカーネルアドレス空間とユーザーアドレス空間の両方に直接アクセスできます(前述の4G / 4Gパッチを使用していない場合)。これは、「高」メモリがユーザーランドプロセスに割り当てられても問題ないことを意味します。

カーネルの目的で「高」メモリを使用することは、より多くの問題です。現在のプロセスにマップされていないハイメモリにアクセスするには、カーネルのアドレス空間に一時的にマップする必要があります。これは、余分なコードとパフォーマンスの低下を意味します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.