カーネルがユーザープログラム(システムコールなど)に代わって実行されるときにカーネルスペースが使用されますか?それとも、すべてのカーネルスレッド(スケジューラなど)のアドレス空間ですか?
まず、一般ユーザープログラムが3 GB(3 GB + 1 GBで割った場合)以上のメモリを持つことができないという意味ですか?また、この場合、カーネルは1GBのカーネル空間が論理的にマッピングされるので、高いメモリのページがどの仮想メモリアドレスにマッピングされるかについて、カーネルの高いメモリをどのように使用しますか?
答え1
カーネルがユーザープログラム(システムコールなど)に代わって実行されるときにカーネルスペースが使用されますか?それとも、すべてのカーネルスレッド(スケジューラなど)のアドレス空間ですか?
はいはい。
さらに進む前に、記憶についていくつかの言葉を言う必要があります。
記憶は2つの領域に分けられます。
- ユーザースペース、これは通常のユーザープロセス(つまり、カーネルを除くすべてのプロセス)が実行される場所のセットです。カーネルの役割は、この空間で実行されているアプリケーションが互いに干渉せず、マシンに干渉しないように管理することです。
- カーネル空間、カーネルのコードとデータが保存され実行される場所です。
ユーザー空間で実行されるプロセスは、限られたメモリ部分にのみアクセスできますが、カーネルはすべてのメモリにアクセスできます。プロセスはユーザー空間でも実行されます。いいえカーネル空間にアクセスできます。ユーザー空間プロセスはカーネルの小さな部分にのみアクセスカーネルを介して公開されるインターフェース -システムコール。プロセスがシステムコールを実行すると、ソフトウェア割り込みがカーネルに送信され、カーネルは適切な割り込みハンドラをスケジュールし、ハンドラが完了した後にタスクを続行します。
カーネル空間コードには「カーネルモード」で実行される属性があり、これは(通常のデスクトップx86コンピュータで)必要です。リング 0 で実行される呼び出しコード。通常、x86アーキテクチャには4つの保護リングがあります。。リング0(カーネルモード)、リング1(おそらくハイパーバイザーまたはドライバーによって使用されます)、リング2(おそらくドライバーによって使用されますがわかりません)。リング3は、一般的なアプリケーションが実行される環境です。実行中のアプリケーションがプロセッサ命令のサブセットにアクセスできる最小特権リング。リング0(カーネルスペース)は最も特権的なリングであり、すべての機械命令にアクセスできます。たとえば、「一般」アプリケーション(ブラウザなど)は、x86アセンブリコマンドを使用してグローバル記述子lgdt
テーブルをロードできず、hlt
プロセッサを停止することもできません。
まず、一般ユーザープログラムが3 GB(3 GB + 1 GBで割った場合)以上のメモリを持つことができないという意味ですか?また、この場合、カーネルは1GBのカーネル空間が論理的にマッピングされるので、高いメモリのページがどの仮想メモリアドレスにマッピングされるかについて、カーネルの高いメモリをどのように使用しますか?
この質問に対する回答については、以下をご覧ください。wagの素晴らしい答え到着Linuxでは、高メモリと低メモリとは何ですか?。
答え2
CPUリングが最も顕著な違いです。
x86保護モードでは、CPUは常に4つのリングのうちの1つにあります。 Linuxカーネルは0と3のみを使用します。
- 0 はカーネルを意味します。
- 3 ユーザーの場合
これはカーネルとユーザースペースの最も厳密で迅速な定義です。
Linuxがリング1とリング2を使用しない理由:https://stackoverflow.com/questions/6710040/cpu-privilege-rings-why-rings-1-and-2-arent-used
現在のリングはどのように決定されますか?
現在のリングは次の組み合わせで選択されます。
Privl
グローバル記述子テーブル:各リングをエンコードするフィールドを持つGDTエントリのメモリ内テーブル。LGDT命令はアドレスを現在の記述子テーブルに設定します。
セグメントレジスタCS、DSなどはGDTエントリのインデックスを指します。
たとえば、
CS = 0
GDT の最初のエントリが現在のコード実行のために有効になっていることを示します。
各リングは何ができますか?
CPU チップの物理構成により、次のことが可能になります。
リング0は何でもできる
リング 3 は複数の命令を実行できず、複数のレジスタに書き込むことはできません。特に次のようになります。
リングは変えられません!それ以外の場合はリング 0 に設定され、リングが役に立たなくなる可能性があります。
つまり、現在セグメント記述子、現在のリングを決定します。
ページテーブルを変更できません。https://stackoverflow.com/questions/18431261/how-does-x86-paging-work
つまり、CR3レジスタは変更できず、ページング自体がページテーブルの変更を防ぎます。
セキュリティ/プログラミングを容易にするため、これにより、あるプロセスが別のプロセスのメモリを見ることができなくなります。
割り込みハンドラを登録できません。これはメモリ位置に書き込むことで構成され、ページングを介して回避することもできます。
ハンドラはリング0で実行され、セキュリティモデルを破ります。
つまり、LGDT、LIDT命令は使用できません。
in
などのIO命令は実行できないため、out
任意のハードウェアアクセスが可能です。それ以外の場合、たとえば、あるプログラムがディスクから直接読み取ることができる場合、ファイル権限は役に立たなくなります。
もっと正確に言えば、ありがとうマイケルページ:オペレーティングシステムは実際にはリング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 システムコール 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
これが発生すると、CPUは起動時にカーネルに登録されている割り込みコールバックハンドラを呼び出します。ここに一つあります。ハンドラの登録と使用に関する具体的なベアメタルの例。
このハンドラはリング 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が実行されているため、実験を妨げる可能性があることです。しかし、理論的には、カーネルモジュールですべての割り込みハンドラを引き継ぐことができるシステムがあります。これは実際に興味深いプロジェクトになります。
負のループ
ネガティブリングは実際にはインテルのマニュアルでは参照されていませんが、実際にはリング0自体よりも多くの機能を持つCPUモードがあるため、「ネガティブリング」の指定に適しています。
一例は、仮想化に使用されるハイパーバイザーパターンです。
詳細については、次を参照してください。
- https://security.stackexchange.com/questions/129098/what-is-protection-ring-1
- https://security.stackexchange.com/questions/216527/ring-3-exploits-and-existence-of-other-rings
腕
ARMではリングを例外レベルと呼びますが、主なアイデアは同じです。
ARMv8には、一般的に使用される4つの例外レベルがあります。
EL0:ユーザーエリア
EL1:カーネル(ARM用語で「ハイパーバイザー」)。
svc
以前に既知のコマンド(SuperVisor呼び出し)入力を使用する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
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呼び出しの実行。
-
コマンド(HyperVisor呼び出し)を使用して入力します
hvc
。ハイパーバイザーは、ユーザースペースのオペレーティングシステムと同様に、オペレーティングシステムの役割を果たします。
たとえば、Xenを使用すると、同じシステム上でLinuxやWindowsなどの複数のオペレーティングシステムを同時に実行でき、Linuxがユーザーのために実行するのと同じように、セキュリティとデバッグを容易にするためにオペレーティングシステムを互いに分離できます。モードプログラム。
ハイパーバイザーは今日のクラウドインフラストラクチャの重要な部分です。これにより、単一のハードウェアで複数のサーバーを実行することができ、ハードウェア使用率を100%に近づけ、大幅なコストを節約できます。
たとえば、AWSは2017年までにXenを使用しました。KVMへの移行がニュースを作る。
EL3:別のレベル。 ToDoリストの例。
コマンド入力の使用
smc
(セーフモード通話)
これARMv8 アーキテクチャ参照モデル DDI 0487C.a- D1章 - AArch64システムレベルのプログラマモデル - 図D1-1はこれを美しく示しています。
ARMの出現により状況が少し変わりました。ARMv8.1仮想化ホスト拡張(VHE)。この拡張により、カーネルをEL2で効率的に実行できます。
VHEは、ほとんどのクライアントがLinux VMのみを必要とするため、KVMなどのLinuxカーネル仮想化ソリューションがXenを上回ったために作成されました(上記のAWSはKVMに移動を参照)。想像できるように、これはすべて単一の仮想マシンプロジェクトでKVMです。 Xenよりもシンプルで潜在的に効率的です。したがって、ホストLinuxカーネルはこの状況でハイパーバイザーとして機能します。
おそらく振り返ってみると、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-emulate-a53-power-up
Ubuntu 18.10でテストされたコードスニペット。
答え3
まず、一般ユーザープログラムが3 GB(3 GB + 1 GBで割った場合)以上のメモリを持つことができないという意味ですか?
はい、これは通常のLinuxシステムの場合です。ユーザーとカーネルのアドレス空間を完全に独立させる「4G / 4G」パッチセットがある時点で浮かんでいましたが(カーネルがユーザーメモリにアクセスするのが難しくなるためパフォーマンスが低下します)、私はそうではありません。アップストリームにマージされたことがないと思います。 x86-64が上昇すると、関心が弱くなります。
また、この場合、カーネルは1GBのカーネル空間が論理的にマッピングされるので、高いメモリのページがどの仮想メモリアドレスにマッピングされるかについて、カーネルの高いメモリをどのように使用しますか?
Linuxが動作していた方法(アドレス空間と比較してメモリが小さいシステムでまだ機能しています)は、物理メモリ全体がアドレス空間のカーネル部分に永続的にマッピングされることです。これにより、カーネルは再マップせずにすべての物理メモリにアクセスできますが、明らかに大量の物理メモリを持つ32ビットシステムには拡張されません。
だから、低メモリと高メモリの概念が生まれました。 「低」メモリはカーネルアドレス空間に永続的にマッピングされます。 「高い」メモリではそうではありません。
プロセッサがシステムコールを実行すると、カーネルモードで実行されますが、まだ現在のプロセスのコンテキスト内で実行されます。したがって、現在のプロセスのカーネルアドレス空間とユーザーアドレス空間に直接アクセスできます(前述の4G / 4Gパッチを使用しないと仮定)。これは、ユーザースペースプロセスに「高い」メモリを割り当てることに問題がないことを意味します。
カーネル目的で「高」メモリを使用することははるかに問題です。現在のプロセスにマップされていない高メモリにアクセスするには、一時的にカーネルのアドレス空間にマップする必要があります。これは、追加のコードとパフォーマンスの損失を意味します。