x86 と x64 のアセンブリの違いについて読んでいます。
x86 では、システム コール番号が に配置されeax
、int 80h
実行されてソフトウェア割り込みが生成されます。
ただし、x64 では、システム コール番号が に配置されてrax
からsyscall
実行されます。
syscall
ソフトウェア割り込みを生成するよりも軽くて速いと言われています。
x86 より x64 の方が速いのはなぜint 80h
ですか?
カーネルを呼び出す (システム コールを行う) ときに必要なことが 3 つあります。
明らかに、カーネルの内部に入ると、カーネルコードは実際にカーネルに何をさせたいかを知る必要があるため、EAXに何かを入れ、「開きたいファイルの名前」または「ファイルからデータを読み込むバッファ」など。
プロセッサが異なれば、上記の 3 つの手順を実行する方法も異なります。x86 にはいくつかの選択肢がありますが、手書きの asm で最も一般的なのはint 0xnn
(32 ビット モード) またはsyscall
(64 ビット モード) の 2 つです。( sysenter
AMD が : の 32 ビット モード バージョンを導入したのと同じ理由で、 Intel によって導入された 32 ビット モードもあります。syscall
これは、低速の代替としてより高速int 0x80
です。32 ビット glibc は、利用可能な効率的なシステム コール メカニズムを使用し、より良いものが利用できない場合は遅くなりint 0x80
ます。)
命令の 64 ビット バージョンは、syscall
システム コールに入る高速な方法として x86-64 アーキテクチャで導入されました。これには、ジャンプ先のアドレス RIP、CS および SS にロードするセレクタ値、および Ring3 から Ring0 への遷移を実行するための一連のレジスタ (x86 MSR メカニズムを使用) があります。また、リターン アドレスを ECX/RCX に格納します。[この命令のすべての詳細については、命令セットのマニュアルをお読みください - それは完全に簡単ではありません!]. プロセッサはこれが Ring0 に切り替わることを知っているので、正しいことを直接行うことができます。
重要なポイントの 1 つは、syscall
レジスターのみを操作することです。ロードもストアも行いません。 (これが、保存された RIP で RCX を上書きし、保存された RFLAGS で R11 を上書きする理由です)。メモリ アクセスはページ テーブルに依存し、ページ テーブル エントリには、ユーザー空間ではなくカーネルに対してのみ有効にするビットがあるため、特権レベルを変更しながらメモリ アクセスを行うには、レジスタを書き込むだけではなく、待機する必要がある場合があります。カーネル モードになると、カーネルは通常、swapgs
または他の方法でカーネル スタックを見つけます。( RSP は変更さsyscall
れません。カーネルへのエントリでは、まだユーザー スタックを指しています。)
SYSRET 命令を使用して戻る場合、値はレジスタ内の所定の値から復元されるため、プロセッサはいくつかのレジスタをセットアップするだけでよいため、これも高速です。プロセッサは、Ring0 から Ring3 に変更されることを認識しているため、適切な処理を迅速に行うことができます。
(AMD CPUsyscall
は 32 ビット ユーザー空間からの命令をサポートしますが、Intel CPU はサポートしません。x86-64 は元々 AMD64 でした。これが 64 ビット モードになっている理由syscall
です。AMD は 64 ビット モード用に のカーネル側を再設計したsyscall
ため、 64 ビットsyscall
カーネルのエントリ ポイントは、64 ビット カーネルの 32 ビット エントリ ポイントとは大きく異なりsyscall
ます。)
32 ビット モードで使用されるint 0x80
バリアントは、メモリからの読み取りを意味する割り込み記述子テーブルの値に基づいて何をすべきかを決定します。そこで、新しい CS と EIP/RIP の値を見つけます。新しい CS レジスタは、新しい「リング」レベル (この場合は Ring0) を決定します。次に、新しい CS 値を使用して (TR レジスタに基づいて) タスク状態セグメントを調べ、どのスタック ポインター (ESP/RSP および SS) を見つけ、最後に新しいアドレスにジャンプします。これは直接的ではなく、より一般的なソリューションであるため、速度も遅くなります。古い EIP/RIP および CS は、SS および ESP/RSP の古い値とともに、新しいスタックに格納されます。
戻るとき、IRET 命令を使用して、プロセッサはスタックから戻りアドレスとスタック ポインタ値を読み取り、スタックから新しいスタック セグメントとコード セグメント値もロードします。繰り返しますが、このプロセスは一般的なものであり、かなりの数のメモリ読み取りが必要です。これは一般的なものであるため、プロセッサは「Ring0 から Ring3 にモードを変更するか、変更する場合はこれらを変更するか」も確認する必要があります。
つまり、要約すると、そのように動作することを意図していたため、高速です。
32 ビット コードのint 0x80
場合、必要に応じて低速で互換性のあるコードを確実に使用できます。
64 ビット コードの場合、int 0x80
は 32 ビットへのポインターよりも遅くsyscall
、ポインターが切り捨てられるため、使用しないでください。64 ビット コードで 32 ビット int 0x80 Linux ABI を使用するとどうなりますか?を参照してください。 さらに、すべてのカーネルで 64 ビット モードで使用できるわけではないため、ポインター引数を取らないint 0x80
に対しても安全ではありません。無効にすることができ、特にLinux の Windows サブシステムでは無効になっています。sys_exit
CONFIG_IA32_EMULATION