web-dev-qa-db-ja.com

セグメンテーションフォールトは内部でどのように機能しますか?

「CPUのMMUはシグナルを送信する」および「カーネルが問題のあるプログラムにシグナルを送信し、それを終了させる」以外に、これに関する情報を見つけることができないようです。

おそらくシェルに信号を送信し、シェルは問題のあるプロセスを終了して_"Segmentation fault"_を出力することによって信号を処理すると想定しました。したがって、私はcrsh(がらくたシェル)と呼ぶ非常に最小限のシェルを作成することによって、この仮定をテストしました。このシェルは、ユーザー入力を取得してsystem()メソッドにフィードする以外は何もしません。

_#include <stdio.h>
#include <stdlib.h>

int main(){
    char cmdbuf[1000];
    while (1){
        printf("Crap Shell> ");
        fgets(cmdbuf, 1000, stdin);
        system(cmdbuf);
    }
}
_

したがって、私はこのシェルを裸のターミナルで実行しました(bashを下で実行せずに)。次に、segfaultを生成するプログラムを実行しました。私の想定が正しかった場合、これは、a)crshがクラッシュし、xtermが閉じられるか、b)_"Segmentation fault"_が出力されないか、c)両方になります。

_braden@system ~/code/crsh/ $ xterm -e ./crsh
Crap Shell> ./segfault
Segmentation fault
Crap Shell> [still running]
_

スクエアワンに戻ると思います。これを行うのはシェルではなく、その下のシステムであることを示したところです。 「セグメンテーション違反」はどのように印刷されますか? 「誰」がやってるの?カーネル?他に何か?信号とそのすべての副作用は、ハードウェアからプログラムの最終的な終了までどのように伝播しますか?

264
Braden Best

最近のすべてのCPUには、現在実行中のマシン命令 interrupt を実行する能力があります。それらは十分な状態(通常、常にではないが、スタック上)を保存して、後でresume実行できるようにします。 、何も起こらなかったかのように(通常、中断された命令は最初から再開されます)次に、割り込みハンドラ)の実行を開始しますが、これは単なるマシンコードですが、特別な場所に配置され、CPUがその場所を認識します。割り込みハンドラーは常にオペレーティングシステムのkernelの一部です。最大の特権で実行され、他のすべてのコンポーネントの実行を監視するコンポーネントです。1,2

割り込みは同期、つまり、現在実行中の命令が行った何かへの直接の応答としてCPU自体によってトリガーされることを意味します。または非同期、外部のために予測できない時間に発生することを意味しますネットワークポートに到着するデータのようなイベントです。一部の人々は非同期割り込みのために「割り込み」という用語を予約し、代わりに同期割り込みを「トラップ」、「フォールト」、または「例外」と呼びますが、これらの単語はすべて他の意味を持つので、 mは「同期割り込み」に固執します。

現在、ほとんどの最新のオペレーティングシステムはプロセスの概念を持っています。最も基本的に、これはコンピュータが同時に複数のプログラムを実行できるメカニズムですが、オペレーティングシステムの方法の重要な側面でもあります。 configure メモリ保護 、これはほとんどの(しかし、残念ながらall)現代のCPUではありません。 virtual memory と連動します)、これは、メモリアドレスとRAM内の実際の場所との間のマッピングを変更する機能です。メモリ保護により、オペレーティングシステムは、各プロセスにアクセス可能な独自のRAMチャンクを各プロセスに与えることができます。また、オペレーティングシステムに代わって(オペレーティングシステムに代わって) RAMの領域を読み取り専用、実行可能、協調プロセスのグループ間で共有などとして指定します。カーネルによってのみアクセス可能なメモリのチャンクも存在します) 。

CPUが許可するように構成されている方法でのみ各プロセスがメモリにアクセスする限り、メモリ保護は見えません。プロセスがルールに違反すると、CPUは同期割り込みを生成し、カーネルに処理を依頼します。プロセスが実際に本当にルールに違反していないことが定期的に発生します。プロセスを続行できるようにするには、カーネルのみがいくつかの作業を行う必要があります。たとえば、プロセスのメモリのページを「解放する必要がある場合"RAM他の何かのために領域を解放するために、スワップファイルに追加します。カーネルはそのページにアクセス不可とマークします。次にプロセスがページを使用しようとすると、CPUはメモリを生成します-保護割り込み;カーネルはスワップからページを取得し、ページを元の場所に戻し、再びアクセス可能としてマークし、実行を再開します。

しかし、プロセスが実際にルールを破ったとしましょう。 RAMがマップされていないページにアクセスしようとしたか、マシンコードが含まれていないなどのマークが付けられたページを実行しようとしました。オペレーティングシステムのファミリーは通常「Unix」として知られているすべては、この状況に対処するためにシグナルを使用します。4 信号は割り込みに似ていますが、ハードウェアによって生成され、カーネルによってフィールド化されるのではなく、カーネルによって生成され、プロセスによってフィールド化されます。プロセスは、独自のコードでシグナルハンドラーを定義し、それらの場所をカーネルに通知できます。これらのシグナルハンドラーが実行され、必要に応じて通常の制御フローを中断します。シグナルにはすべて、1つに番号と2つの名前があります。これは不可解な頭字語であり、もう1つは少し不可解なフレーズです。プロセスがメモリ保護ルールに違反したときに生成される信号は(慣例により)番号11であり、その名前はSIGSEGVおよび "Segmentation fault"です。5,6

シグナルと割り込みの重要な違いは、すべてのシグナルにデフォルトの動作があることです。オペレーティングシステムがすべての割り込みのハンドラーを定義できない場合、それはOSのバグであり、コンピューター全体がクラッシュすると、 CPUは欠落しているハンドラーを呼び出そうとしますが、プロセスはすべてのシグナルのシグナルハンドラーを定義する義務はありません。カーネルがプロセスのシグナルを生成し、そのシグナルがデフォルトの動作のままになっている場合、カーネルは先に進み、ほとんどのシグナルのデフォルトの動作は、「何もしない」または「このプロセスを終了し、コアダンプを生成することもあります」SIGSEGVは、後者の1つです。

つまり、要約すると、メモリ保護ルールに違反するプロセスがあります。 CPUがプロセスを中断し、同期割り込みを生成しました。カーネルはその割り込みを処理し、プロセスのSIGSEGVシグナルを生成しました。プロセスがnotSIGSEGVのシグナルハンドラーをセットアップしたと仮定して、カーネルがプロセスを終了するというデフォルトの動作を実行することを想定します。これは _exit システムコール:開いているファイルが閉じられている、メモリが割り当て解除されている、など。

この時点まで、人間が見ることができるメッセージは何も出力されておらず、シェル(または、より一般的には、終了したプロセスの親プロセス)はまったく関与していません。SIGSEGVは、ルールを破ったプロセスnotその親。ただし、シーケンスのnextステップは、子が終了したことを親プロセスに通知することです。これは、いくつかの異なる方法で発生する可能性があります。最も簡単なのは、親がすでにこの通知を待っているときで、 wait システムコール(waitwaitpidwait4など)。その場合、カーネルはそのシステムコールに戻り、終了ステータス)と呼ばれるコード番号を親プロセスに提供します。7 終了ステータスは親に通知します理由子プロセスが終了しました。この場合、SIGSEGVシグナルのデフォルトの動作が原因で子が終了したことがわかります。

次に、親プロセスはメッセージを出力することにより、イベントを人間に報告します。ほとんどの場合、シェルプログラムがこれを行います。あなたのcrshにはそれを行うためのコードが含まれていませんが、Cライブラリルーチン system がフル機能のシェル/bin/sh、 "フードの下"。 crsh祖父母このシナリオでは、親プロセス通知は/bin/sh、通常のメッセージを出力します。次に/bin/sh自体は終了します。何もする必要がなく、Cライブラリのsystemの実装はthatexit notificationを受け取ります。systemの戻り値を調べると、コードでその終了通知を確認できますが、孫プロセスがsegfaultで死んだとは言わないでください。これは、segfaultが中間のShellプロセスによって消費されたためです。


脚注

  1. 一部のオペレーティングシステムは、カーネルの一部としてデバイスドライバーを実装していません。ただし、ハードウェアが許可していないため、すべての割り込みハンドラーはカーネルの一部である必要があり、メモリ保護を構成するコードも同様です。何か以外はこれらのことを行うカーネル。

  2. 「ハイパーバイザ」または「仮想マシンマネージャ」と呼ばれるプログラムがカーネルよりもさらに特権がある場合がありますが、この回答の目的では、ハードウェアの一部と見なすことができます。

  3. カーネルはプログラムですが、それはではないプロセスです。これはライブラリのようなものです。すべてのプロセスは、独自のコードに加えて、時々カーネルのコードの一部を実行します。 のみカーネルコードを実行する「カーネルスレッド」の数である可能性がありますが、ここでは関係ありません。

  4. がUnixの実装と見なすことができないと見なすことができなくなる可能性がある唯一のOSは、もちろん、Windowsです。この状況ではシグナルを使用しません(実際には、 haveシグナル; Windowsでは<signal.h>インターフェイスはCライブラリによって完全に偽造されています。)「 構造化例外処理 」と呼ばれるものを代わりに使用します。

  5. 一部のメモリ保護違反は、SIGBUSではなくSIGSEGV"バスエラー")を生成します。 2つの間の線は十分に指定されておらず、システムごとに異なります。 SIGSEGVのハンドラーを定義するプログラムを作成した場合は、SIGBUSにも同じハンドラーを定義することをお勧めします。

  6. 「セグメンテーション違反」は、 元のUnix 、おそらく PDP-11 を実行したコンピュータの1つによってメモリ保護違反のために生成された割り込みの名前でした。 「 セグメンテーション 」はメモリ保護の_(タイプですが、現在、「セグメンテーションフォールト」)という用語は、あらゆる種類のメモリ保護違反を総称しています。

  7. すべてのotherは、子プロセスが終了したことを親プロセスに通知し、親がwaitを呼び出して終了ステータスを受け取ることになる可能性があります。それは、最初に何か他のことが起こっただけです。

248
zwol

シェルは確かにそのメッセージに何らかの関係があり、crshはおそらくbashであるシェルを間接的に呼び出します。

私は常に障害をセグメント化する小さなCプログラムを書きました:

_#include <stdio.h>

int
main(int ac, char **av)
{
        int *i = NULL;

        *i = 12;

        return 0;
}
_

デフォルトのシェルzshから実行すると、次のようになります。

_4 % ./segv
zsh: 13512 segmentation fault  ./segv
_

bashから実行すると、質問であなたが指摘したことがわかります。

_bediger@flq123:csrc % ./segv
Segmentation fault
_

私は自分のコードにシグナルハンドラーを記述するつもりでしたが、crsh execが使用するsystem()ライブラリコールは_/bin/sh_に従って_man 3 system_シェルであることに気付きました。 crshは確かにそうではないので、その_/bin/sh_はほぼ確実に「セグメンテーション違反」を出力しています。

crshを書き換えてexecve()システムコールを使用してプログラムを実行する場合、「セグメンテーション違反」文字列は表示されません。 system()によって呼び出されたシェルに由来します。

42
Bruce Ediger

「CPUのMMUはシグナルを送信する」および「カーネルがそれを問題のあるプログラムに送信し、終了させる」以外に、これに関する情報を見つけることができないようです。

これは少し文字化けした要約です。 Unixシグナルメカニズムは、プロセスを開始するCPU固有のイベントとはまったく異なります。

一般に、不正なアドレスにアクセスした場合(または読み取り専用領域に書き込んだ場合、実行不可能なセクションを実行しようとした場合など)、CPUはCPU固有のイベントを生成します(従来の非VMアーキテクチャでは、これは各「セグメント」(伝統的に、読み取り専用の実行可能な「テキスト」、書き込み可能で可変長の「データ」、および伝統的にメモリの反対側にあるスタック)はアドレス範囲が固定されていたため、セグメンテーション違反と呼ばれていました-現代のアーキテクチャでは、ページフォールト(マップされていないメモリの場合)またはアクセス違反(読み取り、書き込み、および実行のアクセス許可の問題の場合)である可能性が高く、残りの回答ではこれに焦点を当てます)。

さて、この時点で、カーネルはいくつかのことができます。ページフォールトは、有効であるが読み込まれていないメモリに対しても生成されます(たとえば、スワップアウト、またはmmappedファイルなど)。この場合、カーネルはメモリをマップし、ユーザープログラムを原因の命令から再起動します。エラー。それ以外の場合は、シグナルを送信します。シグナルハンドラーをインストールするプロセスは異なり、プログラムが割り込みハンドラーのインストールをシミュレートすることが期待されていた場合とは異なり、これは正確に「[元のイベント]を問題のプログラムに直接送る」わけではありません。

ユーザープログラムにシグナルハンドラーがインストールされている場合、これはスタックフレームを作成し、ユーザープログラムの実行位置をシグナルハンドラーに設定することを意味します。同じことがすべてのシグナルに対して行われますが、セグメンテーション違反の場合、シグナルハンドラーが戻った場合にエラーの原因となった命令を再起動するように、一般的に調整されます。ユーザープログラムがエラーを修正した可能性があります。メモリを問題のアドレスにマッピングすることによって-これが可能かどうかはアーキテクチャに依存します)。シグナルハンドラは、プログラムの別の場所にジャンプして(通常は longjmp を使用するか、例外をスローすることによって)、不正なメモリアクセスを引き起こした操作を中止することもできます。

ユーザープログラムにシグナルハンドラーがインストールされていない場合は、単に終了します。一部のアーキテクチャでは、信号が無視されると、命令が何度も再起動され、無限ループが発生する可能性があります。

21
Random832

セグメンテーション違反は、許可されていないメモリアドレスへのアクセスです(プロセスの一部ではない、または読み取り専用データの書き込みを試みている、または非実行可能データを実行している、など)。これは、MMU(今日のCPUの一部であるメモリ管理ユニット)によってキャッチされ、割り込みを引き起こします。割り込みは、SIGSEGFAULT信号を送信するカーネルによって処理されます(問題のプロセスへのsignal(2)を参照)このシグナルのデフォルトハンドラーはコアをダンプし(core(5)を参照)、プロセスを終了します。

シェルはこれには絶対に手を貸しません。

18
vonbrand