回答:
カーネルがユーザープログラム、つまりシステムコールに代わって実行されているときに、カーネルスペースが使用されていますか?それとも、すべてのカーネルスレッド(スケジューラなど)のアドレススペースですか?
はい、はい。
さらに先に進む前に、記憶についてこれを述べる必要があります。
メモリは2つの異なる領域に分けられます。
ユーザー空間で実行されるプロセスはメモリの限られた部分にのみアクセスできますが、カーネルはすべてのメモリにアクセスできます。ユーザー空間で実行されているプロセスも、カーネル空間にアクセスできません。ユーザー空間プロセスは、カーネルによって公開されたインターフェースを介してカーネルのごく一部にしかアクセスできません - システムコール。プロセスがシステムコールを実行すると、ソフトウェア割り込みがカーネルに送信され、カーネルは適切な割り込みハンドラーをディスパッチし、ハンドラーが終了した後も作業を継続します。
カーネルスペースコードには、「カーネルモード」で実行するプロパティがあります。これは(通常のデスクトップ-x86-コンピューターで)リング0の下で実行するコードと呼ばれるものです。通常、x86アーキテクチャには4つの保護リングがあります。リング0(カーネルモード)、リング1(仮想マシンハイパーバイザーまたはドライバーで使用される場合があります)、リング2(ドライバーで使用される場合がありますが、それについてはよくわかりません)。リング3は、典型的なアプリケーションが実行されるものです。これは最小特権のリングであり、その上で実行されるアプリケーションは、プロセッサの命令のサブセットにアクセスできます。リング0(カーネルスペース)は最も特権的なリングであり、マシンのすべての命令にアクセスできます。たとえば、「プレーンな」アプリケーション(ブラウザなど)はx86アセンブリ命令を使用できませんlgdt
グローバル記述子テーブルをロードするかhlt
、プロセッサを停止します。
それが最初のものである場合、それは通常のユーザープログラムが3GB以上のメモリを持つことができないことを意味します(分割が3GB + 1GBの場合)?また、その場合、カーネルはどのようにしてハイメモリを使用できますか?なぜなら、1GBのカーネルスペースが論理的にマッピングされるため、ハイメモリのページはどの仮想メモリアドレスにマップされるのですか?
-1
はハイパーバイザー用だと思いましたか?en.wikipedia.org/wiki/Protection_ring
CPUリングは最も明確な区別です
x86保護モードでは、CPUは常に4つのリングのいずれかにあります。Linuxカーネルは0と3のみを使用します。
これは、カーネルとユーザーランドの最もハードで高速な定義です。
Linuxがリング1と2を使用しない理由:https : //stackoverflow.com/questions/6710040/cpu-privilege-rings-why-rings-1-and-2-arent-used
現在のリングはどのように決定されますか?
現在のリングは、次の組み合わせで選択されます。
グローバル記述子テーブル:GDTエントリのメモリ内テーブル。各エントリにはPrivl
リングをエンコードするフィールドがあります。
LGDT命令は、アドレスを現在の記述子テーブルに設定します。
セグメントは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
これが発生すると、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
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呼び出しを行う具体的なベアメタルの例を次に示します。
入力した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でテストされたコードスニペット
それが最初のものである場合、それは通常のユーザープログラムが3GB以上のメモリを持つことができないことを意味します(分割が3GB + 1GBの場合)?
はい、これは通常のLinuxシステムの場合です。一時的にユーザーとカーネルのアドレス空間を完全に独立させた「4G / 4G」パッチのセットがありました(カーネルがユーザーのメモリにアクセスするのを難しくしたため、パフォーマンスが低下しました)。それらはアップストリームにマージされ、x86-64の台頭により関心が衰えました。
また、その場合、カーネルはどのようにしてハイメモリを使用できますか?なぜなら、1GBのカーネルスペースが論理的にマッピングされるため、ハイメモリのページはどの仮想メモリアドレスにマップされるのですか?
Linuxの動作方法(およびアドレススペースに比べてメモリが小さいシステムでも)は、物理メモリ全体がアドレススペースのカーネル部分に永続的にマッピングされていました。これにより、カーネルは再マッピングなしですべての物理メモリにアクセスできましたが、明らかに大量の物理メモリを備えた32ビットマシンには対応していません。
それで、低記憶と高記憶の概念が生まれました。「低」メモリは、カーネルのアドレス空間に永続的にマップされます。「高」メモリはそうではありません。
プロセッサがシステムコールを実行しているときは、カーネルモードで実行されていますが、現在のプロセスのコンテキストで実行されています。そのため、現在のプロセスのカーネルアドレス空間とユーザーアドレス空間の両方に直接アクセスできます(前述の4G / 4Gパッチを使用していない場合)。これは、「高」メモリがユーザーランドプロセスに割り当てられても問題ないことを意味します。
カーネルの目的で「高」メモリを使用することは、より多くの問題です。現在のプロセスにマップされていないハイメモリにアクセスするには、カーネルのアドレス空間に一時的にマップする必要があります。これは、余分なコードとパフォーマンスの低下を意味します。