web-dev-qa-db-ja.com

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

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

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

79
Poojan

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

はい、はい。

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

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

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

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

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

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

これに対する答えについては、 waghere による優れた答えを参照してください。

96
NlightNFotis

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は何でもできます

  • ring 3は、いくつかの命令を実行して、いくつかのレジスタに書き込むことができません。

    • 自分のリングを変更することはできません!それ以外の場合、それ自体をリング0に設定する可能性があり、リングは役に立ちません。

      つまり、現在のリングを決定する現在の segment descriptor を変更することはできません。

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

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

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

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

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

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

    • IO inoutのような命令は実行できないため、任意のハードウェアアクセスが可能です。

      それ以外の場合、たとえば、プログラムがディスクから直接読み取ることができる場合、ファイルのアクセス許可は役に立ちません。

      より正確には Michael Petch に感謝します。OSが実際にリング3でIO命令を許可することは可能です)、これは実際に タスク状態セグメント

      リング3が最初にそれを持っていなかった場合、リング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はカーネルがブート時に登録した割り込みコールバックハンドラーを呼び出します。ハンドラーを登録して使用する concrete baremetalの例を次に示します

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

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

  • (ページングのため)禁止されたレジスタまたはメモリアドレスへの書き込みなど、プログラムがエッチなことを行おうとすると、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として知られていたswi命令(SuperVisor呼び出し)を使用して入力されました 統合前のアセンブリ 、これは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: hypervisors 、たとえば Xen

    hvc命令(HyperVisor Call)で入力されました。

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

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

    ハイパーバイザーは、今日のクラウドインフラストラクチャの重要な部分です。ハイパーバイザーは、単一のハードウェア上で複数のサーバーを実行できるようにし、ハードウェアの使用率を常に100%に保ち、コストを大幅に節約します。

    たとえば、AWSは2017年まで its to KVM=がニュース に移行するまでXenを使用していました。

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

    smc命令で入力(セキュアモードコール)

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

enter image description here

後知恵の恩恵により、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でテストされたコードスニペット。

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

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

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

Linuxが機能する(かつ、アドレススペースと比較してメモリが小さいシステムでも機能する)方法は、物理メモリ全体がアドレススペースのカーネル部分に永続的にマッピングされることでした。これにより、カーネルはすべての物理メモリに再マッピングせずにアクセスできましたが、物理メモリが大量にある32ビットマシンにはスケーリングしません。

したがって、低メモリと高メモリの概念が生まれました。 「低」メモリは、カーネルのアドレス空間に永続的にマッピングされます。 「ハイ」メモリではありません。

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

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

3
plugwash