11

カーネル 2.6.11.5 では、除算ゼロ例外ハンドラは次のように設定されます。

set_trap_gate(0,&divide_error);

「Understanding The Linux Kernel」によると、Intel トラップ ゲートにはユーザー モード プロセスからアクセスできません。ただし、ユーザー モード プロセスでもdivide_error. では、なぜ Linux はこのように実装するのでしょうか?

[編集] IDT エントリの DPL 値を 0 に設定するため、質問はまだ未解決であると思いますset_trap_gate()。これは、CPL=0 (カーネルを読み取る) コードのみがそれを実行できることを意味します。ユーザーモード:

#include<stdio.h>

int main(void)
{
    int a = 0;
    int b = 1;

    b = b/a;

    return b;
}

でコンパイルされましたgcc div0.c。そしての出力./a.outは次のとおりです。

浮動小数点例外 (コアダンプ)

したがって、これは 0 除算のトラップ コードによって処理されたようには見えません。

4

5 に答える 5

8

私はLinux カーネル 3.7.1のソースを手元に持っているので、それらのソースに基づいてあなたの質問に答えようと思います。コードにあるもの。には、次のコード行が見つかるarch\x86\kernel\traps.c関数があります。early_trap_init()

set_intr_gate(X86_TRAP_DE, &divide_error);

ご覧のとおり、set_trap_gate()は に置き換えられましたset_intr_gate()。次のターンでこの呼び出しを展開すると、次のことが達成されます。

_set_gate(X86_TRAP_DE, GATE_INTERRUPT, &divide_error, 0, 0, __KERNEL_CS);

_set_gateは、次の 2 つのことを担当するルーチンです。

  1. IDT記述子の構築
  2. 構築された記述子を IDT 記述子配列のターゲット セルにインストールします。2 つ目は単なるメモリのコピーであり、私たちにとっては興味深いものではありません。しかし、指定されたパラメーターから記述子を構築する方法を見ると、次のことがわかります。

    struct desc_struct{
        unsigned int a;
        unsigned int b;
    };
    
    desc_struct gate;
    
    gate->a = (__KERNEL_CS << 16) | (&divide_error & 0xffff);
    gate->b = (&divide_error & 0xffff0000) | (((0x80 | GATE_INTERRUPT | (0 << 5)) & 0xff) << 8); 
    

または最後に

gate->a = (__KERNEL_CS << 16) | (&divide_error & 0xffff);
gate->b = (&divide_error & 0xffff0000) | (((0x80 | 0xE | (0 << 5)) & 0xff) << 8);

記述子の構築の最後でわかるように、メモリには次の 8 バイトのデータ構造があります。

[0xXXXXYYYY][0xYYYY8E00], where X denotes digits of kernel code segment selector number, and Y denotes digits of address of the divide_error routine.

これらの 8 バイトのデータ構造は、プロセッサ定義の割り込み記述子です。特定のベクトルでの割り込みの受け入れに応答して、どのアクションを実行する必要があるかを識別するためにプロセッサによって使用されます。Intel がx86プロセッサ ファミリ用に定義した割り込み記述子の形式を見てみましょう。

                              80386 INTERRUPT GATE
31                23                15                7                0
+-----------------+-----------------+---+---+---------+-----+-----------+
|           OFFSET 31..16           | P |DPL|  TYPE   |0 0 0|(NOT USED) |4
|-----------------------------------+---+---+---------+-----+-----------|
|             SELECTOR              |           OFFSET 15..0            |0
+-----------------+-----------------+-----------------+-----------------+

この形式では、SELECTOR:OFFSETのペアは、割り込みの受け入れに応答して制御を取得する関数のアドレスを (長い形式で) 定義します。私たちの場合、これは__KERNEL_CS:divide_errorであり、divide_error()ゼロによる除算例外の実際のハンドラーです。 Pフラグは、ディスクリプタが OS によって正しくセットアップされた有効なディスクリプタと見なされることを指定します。DPL -divide_error()ソフト割り込みを使用して機能をトリガーできるセキュリティ リングを指定します。その分野の役割を理解するには、ある程度の背景が必要でした。

一般に、割り込みソースには次の 3 種類があります。

  1. OS からサービスを要求する外部デバイス。
  2. プロセッサ自体が異常な状態に陥ったことが判明すると、OS にその状態からの脱出を支援するように要求します。
  3. OS の制御下でプロセッサ上で実行されるプログラムで、OS に特別なサービスを要求します。

最後のケースには、専用命令 int XX の形式でプロセッサからの特別なサポートがあります。プログラムは、OSサービスを必要とするたびに、リクエストを記述したパラメータを設定し、OSがサービス提供のために使用する割り込みベクタを記述したパラメータでint命令を発行します。int 命令の発行により発生する割り込みをソフト割り込みと呼びます。したがって、ここでは、プロセッサはソフト割り込みを処理する場合にのみ DPL フィールドを考慮し、プロセッサ自体または外部デバイスによって生成された割り込みの場合は完全に無視します。DPL は、アプリケーションがデバイスをシミュレートすることを禁止し、これによってシステムの動作を暗示するため、非常に重要な機能です。

たとえば、あるアプリケーションが次のようなものを作成すると想像してください。

for(;;){
    __asm int 0xFF; 

  //where 0xFF is vector used by system timer, to notify the kernel that the 
   another one timer tick was occurred
}

その場合、コンピューターの時間は実際の生活よりもはるかに速く進み、あなたが期待し、システムが期待します。その結果、システムは非常に誤動作します。ご覧のとおり、プロセッサと外部デバイスは信頼できると見なされますが、ユーザー モード アプリケーションの場合はそうではありません。ゼロ除算例外の場合、Linux は、この例外がリング 0 からのみ、つまりカーネルからのみソフト割り込みによってトリガーされるように指定します。その結果、int 0 命令がカーネル空間で実行される場合、プロセッサは制御をdivide_error()ルーティーン。同じ命令がユーザー空間で実行される場合、カーネルはこれを保護違反として処理し、一般保護違反例外ハンドラーに制御を渡します (これはすべての無効なソフト割り込みのデフォルト アクションです)。ただし、プロセッサ自体が何らかの値をゼロで除算しようとしてゼロによる除算例外が生成された場合、divide error()誤った除算が発生したスペースに関係なく、制御はルーチンに切り替えられます。一般に、アプリケーションがソフト割り込みによって Division By Zero 例外をトリガーできるようにしても、大きな害はないようです。しかし、最初は醜いデザインになり、2 番目はロジックが舞台裏にある可能性があります。これは、0 による除算例外が実際の不正な除算操作によってのみ生成されるという事実に依存しています。

TYPEフィールドは、割り込みの受け入れに応答してプロセッサが実行する必要がある補助アクションを指定します。実際には、割り込み記述子トラップ記述子の 2 種類の例外記述子のみが使用されます。それらは一面だけが異なります。割り込み記述子は、プロセッサに将来の割り込みの受け入れを無効にするように強制しますが、トラップ記述子はそうしません。正直なところ、Linux カーネルがゼロ除算の例外処理に割り込み記述子を使用することにした理由がわかりません。トラップ記述子は、私にとってより合理的に聞こえます。

テストプログラムの紛らわしい出力に関する最後の注意

Floating point exception (core dumped)  

歴史的な理由により、Linux カーネルは、ゼロ除算を試行したプロセスにSIGFPE (SIGnal Floating Point Exception の読み取り) シグナルを送信することで、除算ゼロ例外に応答します。はい、SIGDBZ ではありません( SIGnal Division By Zero を読んでください)。私はこれが十分に混乱していることを知っています。このような動作の理由は、Linux が元のUNIXの動作を模倣しており (この動作は POSIX で凍結されていたと思います)、元のUNIXが「ゼロによる除算」例外を「浮動小数点例外」と見なす理由です。どうしてか分かりません。

于 2013-03-19T14:17:47.307 に答える
5

IDT の DPL ビットは、int 命令でソフトウェア割り込みが呼び出された場合にのみ参照されます。ゼロによる除算は、CPU によってトリガーされるソフトウェア割り込みであるため、この場合 DPL は効果がありません。

于 2012-06-13T18:38:52.147 に答える
3

ユーザー モード コードには、セグメントや割り込み記述子テーブルなどのシステム テーブルにアクセスするビジネスはありません。これらは、OS カーネルの外部で操作することを意図しておらず、その必要もありません。ゼロ除算、一般保護例外、ページ フォールトなどの例外の Linux ハンドラーは、ユーザー モード コードとカーネル モード コードの両方から発生する例外をインターセプトします。それらは発生元に基づいて異なる方法で処理する場合がありますが、割り込み記述子テーブルには、すべての種類の例外 (上記など) に対して 1 つのハンドラーのアドレスが含まれています。そして、すべてのハンドラーは、その例外を処理する方法を知っています。

于 2011-12-16T07:40:14.227 に答える
2

カーネルがユーザー モードで実行されていません。ユーザーモードプログラム (ユーザーランドの Linux プロセスなど) によって生成されたトラップを処理する必要があります。カーネル コードは、0 で除算することは想定されていません。

あなたの質問がよくわかりません。そうでなければ、どのように実装しますか?

于 2011-12-16T07:02:04.517 に答える
1

質問の一部に対する回答は、「Intel(R) 64 and IA-32 Architectures and Software Developer's Manual, Volume 3A」のセクション 6.12.1.1 にあります。

プロセッサは、例外または割り込みが INT n、INT 3、または INTO 命令で生成された場合にのみ、割り込みまたはトラップ ゲートの DPL をチェックします。ここで、CPL はゲートの DPL 以下でなければなりません。この制限により、特権レベル 3 で実行されているアプリケーション プログラムまたはプロシージャは、ソフトウェア割り込みを使用して、ページ フォールト ハンドラーなどの重要な例外ハンドラーにアクセスできなくなります。ただし、これらのハンドラーがより特権的なコード セグメント (数値的に低い特権レベル) に配置されている場合に限ります。ハードウェアで生成された割り込みとプロセッサで検出された例外の場合、プロセッサは割り込みゲートとトラップ ゲートの DPL を無視します。

それはアレックス・クライマーが答えたものです

メッセージに関して。よくわかりませんが、OSがSIGFPEシグナルをプロセスに送信しているようです。

于 2012-08-02T08:35:43.520 に答える