Linux 入門 --- シグナルの理解

コンピューターの信号を理解する方法

運動会のスタート号砲、夜寝る前にセットされた目覚まし時計、信号機など、私たちは生活の中でたくさんの信号に遭遇します。道路を横断するときや車を運転するときに携帯電話が信号を受信して​​いることを確認します。メッセージから発せられる音も信号です。携帯電話から発せられる音も信号であり、私たち人間はその信号を認識できます。ここでの認識とは、人間が信号を認識することを意味します人はこの信号を認識でき、この信号のおかげで私たちはそれに応じて行動できるのですが、なぜ信号機を認識できるのでしょうか? 携帯電話の呼び出し音を聞くと、なぜ何らかの情報を受信したことがわかるのでしょうか? その理由は、誰かがあなたを教育し、経験を通じて、これらの信号が生成されるときは、何かを処理する必要があることを意味していると教えてくれたためです。教育と過去の経験の蓄積を通じて、いくつかの信号の属性を脳内で思い出すことができます(なぜ?そのような信号が生成されるのか?) とそれに対応する処理動作 (人間はこの信号をどのように処理すべきか)? たとえば、運転中に赤信号が点灯していることに気づいたら、適切な場所で車を停止しなければなりません。赤信号は信号であり、車を停止することはこの信号の処理であるのに、なぜ赤信号があるのでしょうか? その理由は社会交通の利便性を維持するためですが、なぜ赤信号は信号であり、赤信号に出会ったら停止しなければならないことがわかっているのでしょうか。その理由は、私たちは幼い頃から親や先生から「赤信号に出会ったら、赤信号が青に変わるまで進むのをやめなさい」と教えられ、「赤信号は危険信号だ」という教育を受けてきたからです。信号とその信号の処理方法。行動、たとえば、携帯電話の音は信号です。携帯電話が音を出すと、誰かまたはソフトウェアがメッセージを送ってきたことがわかり、電話を開いて次のことを行うことができます。では、携帯電話から音が鳴っていることはどのようにしてわかるのでしょうか? それは信号なのでしょうか? その理由は、以前は誰かやソフトウェアからメッセージが送られてくると電話から音が鳴り、電話を使わずに脇に置いていても、メッセージを受信すると音が鳴ってしまっていたからです。私たちは経験を積んでおり、携帯電話から音が鳴るとメッセージを受信したことを意味し、そのメッセージを処理する必要があることを知っています。これが信号を認識する理由であり、信号を受信したら、すぐに処理しなければなりませんか?答えは「いいえ」です。なぜなら、信号は何気なく生成される可能性があるからです。しかし、信号が生成されると、私たちはもっと重要なことをしなければならないかもしれません。たとえば、携帯電話の音が鳴ったら、すぐに携帯電話の電源を入れて確認する必要があるでしょうか? ? 右!必要ありません。完了するまでこの信号を無視することもできます。この信号を再度確認しても遅くはありません。信号が来ると、私たちは違うのです この信号はすぐに処理する必要があります。現時点ではさらに重要なことを行う必要があるためです。信号が生成されてから信号が処理されるまでには時間枠があり、この時間枠内で信号を覚えておく必要があります。シグナルの保存方法は後で学習します。シグナルが何であるかは誰かが教えてくれたので知っています。シグナルの処理は修正されていますか? この信号を単に無視することはできますか? たとえば、赤信号が点灯しているときでも、私たちは前に進むことを選択し、電話が鳴り続けていますが、それでも自分が行っていることを続けることを選択します。答えは「はい」です。信号 信号を直接無視することを選択できますが、同様に、信号処理の以前の動作を変更できるでしょうか? たとえば、赤信号に遭遇したら前進を停止しなければならないと誰かがいつも私たちに教えてきました。これはデフォルトの信号処理動作です。この動作は変更できます。赤信号が点灯しているとき、私たちはそれに基づいて行動しますが、この動作により、前進から後進への動きが変わります。同様に、電話が鳴ったときのデフォルトの動作は、電話を開いてメッセージを確認することです。その後、この動作を変更できます。電話が鳴ったら、直接電源を切ることができます。電話です。その場合、これは信号のデフォルトの動作の変更です。これを見れば、誰もが信号を理解できるはずですが、誰もが疑問を抱くはずです。人々は教育を受け、経験から結果を要約することができますでは、コンピューターはどのように信号を理解するのでしょうか? プロセスがシグナルを認識する方法は、シグナルの認識 + シグナルの処理であり、シグナルはプロセスに送信されます。では、プロセスはどのようにしてシグナルを認識するのでしょうか? 人間は教育を受けているため信号を認識でき、プロセス自体はプログラマーによって記述された属性とロジックの集合であるため、プロセスは信号を認識できます。これらはすべてプログラマーによってコード化されているため、私たちが作成したプログラムは信号を認識できます。プロセスが信号を受信したとき、信号の場合、プロセスはより重要なコードを実行している可能性があるため、信号はすぐには処理されない可能性があるため、プロセス自体には信号を保存する機能が必要です。プロセスには通常、信号を処理するときに 3 つのアクションがあります (デフォルト、カスタム、無視) 、信号を処理する動作をシグナルキャプチャと呼びます。次に、信号を段階的に理解しましょう。この信号は無視してください。たとえば、赤信号が点灯しているときでも、私たちは前に進むことを選択します。電話は鳴り続けますが、それでも私たちは今やっていることを続けることを選択します。答えはイエスです。信号を処理するときに、次のことを選択できます。信号を直接無視する場合、同じ理由で信号処理の以前の動作を変更できますか? たとえば、赤信号に遭遇したら前進を停止しなければならないと誰かがいつも私たちに教えてきました。これはデフォルトの信号処理動作です。この動作は変更できます。赤信号が点灯しているとき、私たちはそれに基づいて行動しますが、この動作により、前進から後進への動きが変わります。同様に、電話が鳴ったときのデフォルトの動作は、電話を開いてメッセージを確認することです。その後、この動作を変更できます。電話が鳴ったら、直接電源を切ることができます。電話です。その場合、これは信号のデフォルトの動作の変更です。これを見れば、誰もが信号を理解できるはずですが、誰もが疑問を抱くはずです。人々は教育を受け、経験から結果を要約することができますでは、コンピューターはどのように信号を理解するのでしょうか? プロセスがシグナルを認識する方法は、シグナルの認識 + シグナルの処理であり、シグナルはプロセスに送信されます。では、プロセスはどのようにしてシグナルを認識するのでしょうか? 人間は教育を受けているため信号を認識でき、プロセス自体はプログラマーによって記述された属性とロジックの集合であるため、プロセスは信号を認識できます。これらはすべてプログラマーによってコード化されているため、私たちが作成したプログラムは信号を認識できます。プロセスが信号を受信したとき、信号の場合、プロセスはより重要なコードを実行している可能性があるため、信号はすぐには処理されない可能性があるため、プロセス自体には信号を保存する機能が必要です。プロセスには通常、信号を処理するときに 3 つのアクションがあります (デフォルト、カスタム、無視) 、信号を処理する動作をシグナルキャプチャと呼びます。次に、信号を段階的に理解しましょう。この信号は無視してください。たとえば、赤信号が点灯しているときでも、私たちは前に進むことを選択します。電話は鳴り続けますが、それでも私たちは今やっていることを続けることを選択します。答えはイエスです。信号を処理するときに、次のことを選択できます。信号を直接無視する場合、同じ理由で信号処理の以前の動作を変更できますか? たとえば、赤信号に遭遇したら前進を停止しなければならないと誰かがいつも私たちに教えてきました。これはデフォルトの信号処理動作です。この動作は変更できます。赤信号が点灯しているとき、私たちはそれに基づいて行動しますが、この動作により、前進から後進への動きが変わります。同様に、電話が鳴ったときのデフォルトの動作は、電話を開いてメッセージを確認することです。その後、この動作を変更できます。電話が鳴ったら、直接電源を切ることができます。電話です。その場合、これは信号のデフォルトの動作の変更です。これを見れば、誰もが信号を理解できるはずですが、誰もが疑問を抱くはずです。人々は教育を受け、経験から結果を要約することができますでは、コンピューターはどのように信号を理解するのでしょうか? プロセスがシグナルを認識する方法は、シグナルの認識 + シグナルの処理であり、シグナルはプロセスに送信されます。では、プロセスはどのようにしてシグナルを認識するのでしょうか? 人間は教育を受けているため信号を認識でき、プロセス自体はプログラマーによって記述された属性とロジックの集合であるため、プロセスは信号を認識できます。これらはすべてプログラマーによってコード化されているため、私たちが作成したプログラムは信号を認識できます。プロセスが信号を受信したとき、信号の場合、プロセスはより重要なコードを実行している可能性があるため、信号はすぐには処理されない可能性があるため、プロセス自体には信号を保存する機能が必要です。プロセスには通常、信号を処理するときに 3 つのアクションがあります (デフォルト、カスタム、無視) 、信号を処理する動作をシグナルキャプチャと呼びます。次に、信号を段階的に理解しましょう。誰もが信号を理解できるはずですが、誰もが疑問を抱いているはずです。人々は教育を受け、経験から結果を要約することができますが、コンピューターはどのようにして信号を理解するのでしょうか? プロセスがシグナルを認識する方法は、シグナルの認識 + シグナルの処理であり、シグナルはプロセスに送信されます。では、プロセスはどのようにしてシグナルを認識するのでしょうか? 人間は教育を受けているため信号を認識でき、プロセス自体はプログラマーによって記述された属性とロジックの集合であるため、プロセスは信号を認識できます。これらはすべてプログラマーによってコード化されているため、私たちが作成したプログラムは信号を認識できます。プロセスが信号を受信したとき、信号の場合、プロセスはより重要なコードを実行している可能性があるため、信号はすぐには処理されない可能性があるため、プロセス自体には信号を保存する機能が必要です。プロセスには通常、信号を処理するときに 3 つのアクションがあります (デフォルト、カスタム、無視) 、信号を処理する動作をシグナルキャプチャと呼びます。次に、信号を段階的に理解しましょう。誰もが信号を理解できるはずですが、誰もが疑問を抱いているはずです。人々は教育を受け、経験から結果を要約することができますが、コンピューターはどのようにして信号を理解するのでしょうか? プロセスがシグナルを認識する方法は、シグナルの認識 + シグナルの処理であり、シグナルはプロセスに送信されます。では、プロセスはどのようにしてシグナルを認識するのでしょうか? 人間は教育を受けているため信号を認識でき、プロセス自体はプログラマーによって記述された属性とロジックの集合であるため、プロセスは信号を認識できます。これらはすべてプログラマーによってコード化されているため、私たちが作成したプログラムは信号を認識できます。プロセスが信号を受信したとき、信号の場合、プロセスはより重要なコードを実行している可能性があるため、信号はすぐには処理されない可能性があるため、プロセス自体には信号を保存する機能が必要です。プロセスには通常、信号を処理するときに 3 つのアクションがあります (デフォルト、カスタム、無視) 、信号を処理する動作をシグナルキャプチャと呼びます。次に、信号を段階的に理解しましょう。

コンピューターからの信号を表示する方法

kill -l はすべてのシグナルを表示できます。
ここに画像の説明を挿入します
数字はシグナルの番号を表します。数字の後の名前はその数字に対応するマクロなので、将来的には数字またはマクロのいずれかを使用してシグナルを制御できるようになります。信号の場合、1 ~ 31 を通常の信号、34 ~ 64 をリアルタイム信号と呼びます (これについては学習しません)。

シグナルの保存と送信についての予備的な理解

前の紹介で、信号は送信後すぐに処理されない可能性があるため、信号を保存する必要があることがわかりました。答えは、task_struct に保存することですが、何を保存すればよいでしょうか? 答えは、指定されたシグナルが受信されたかどうかを保存することです。指定されたシグナルが受信された場合は 1 を保存し、そうでない場合は 0 を保存します。すると、ここには 31 個のシグナルがあるため、task_struct にはシグナルが受信されたかどうかを保存する unsigned int シグナルがあります。ビットの位置はシグナルの番号を表し、ビットの内容はシグナルを受信したかどうかを表します。0はいいえ、1ははいを意味します。シグナルの保存方法はビットマップを通じて保存するため、 PCB 内で信号を送信する方法。本質は、PCB 内の信号ビットマップを変更し、ビットマップ内の特定のビットを 0 から 1 に変更することです。PCB はカーネルによって維持されるデータ構造オブジェクトであるため、マネージャーPCB の内容はオペレーティング システムですが、PCB を変更する権利は誰にありますか? 内容はどうですか? 答えはオペレーティング システムです。したがって、将来シグナルを送信する方法がどれほど多くなったとしても、本質はオペレーティング システムを通じてターゲット プロセスにシグナルを送信することです。そのため、オペレーティング システムはシグナル送信処理に関連するシステム コールを提供する必要があります。使用する kill コマンド 最下層は対応するシステム コールを呼び出す必要があります。たとえば、次のプログラムを提供します。

#include<iostream>
#include<unistd.h>
using namespace std;
int main()
{
    
    
    while(1)
    {
    
    
        cout<<"我是一个死循环的进程"<<endl;
        sleep(1);
    }
    return 0;
}

実行可能ファイルを生成し
ここに画像の説明を挿入します
、プログラムを実行すると、次のようなシーンが表示されます:
ここに画像の説明を挿入します
無限ループで print ステートメントが表示されます。プロセスを終了したい場合は、キーボードの ctrl+c を押すと、プログラムが実行されます。終了:
ここに画像の説明を挿入します
Ctrl+C はホット キーであり、本質的にはキーの組み合わせです。オペレーティング システムは、このキーの組み合わせをシグナル No. 2 として認識し、プロセスに送信します。man 7 シグナルを通じて、次の情報および対応する情報を表示できます。すべてのシグナル。デフォルトの操作:
ここに画像の説明を挿入します
signal はシグナルの名前、value はシグナルの値、action はシグナルのアクション、comment はシグナルの説明です。この図から、名前がシグナルNo.2のシグナルはsigint、アクションはTermで、キーボードを使用して実行中のプロセスを中断するという記述なので、プログラムが画面への出力を停止していることがわかります。 , 判断するには、シグナル関数を使用できます. シグナル関数の機能は、信号処理アクションを変更することです. シグナル関数の
ここに画像の説明を挿入します
最初のパラメーターは、変更したいシグナルの番号を示します. のタイプ2 番目のパラメータは singhandler_t です。上の表示から、singhandler は名前が変更された型であることがわかります。この型の本質は関数ポインタです。関数の戻り値の型は void で、パラメータは整数です。このパラメータの関数これは、信号の元のデフォルトのアクションを、関数ポインターが指す関数に変更することです。たとえば、次のプログラムです。

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
using namespace std;
void handler(int sign)
{
    
    
    cout<<"我收到了信号,信号的编号为:"<<sign<<endl;
}

int main()
{
    
    
    signal(2,handler);
    while(1)
    {
    
    
        cout<<"我是一个死循环的进程,我的pid为:"<<getpid()<<endl;
        sleep(1);
    }
    return 0;
}

操作の結果は次のようになります:
ここに画像の説明を挿入します
プログラムの実行に問題がないことがわかります。その後、ctrl c を押してプロセスにシグナルを送信すると、プログラムは終了せず、代わりに、プログラムが実行した内容が実行されます。前に設定して文を印刷します。 :
ここに画像の説明を挿入します
この文を印刷した後、無限ループを実行します:
ここに画像の説明を挿入します
これにより、キーボードを使用して Ctrl C を入力すると、基本的にシグナル 2 番が現在のプロセスに送信されていることも検証されます。信号番号 2 は、現在のプログラムを終了に送信することです。

ターゲットプロセスにシグナルを送信する方法

シナリオ 1: キーボードを使用して信号を送信する

これまでの研究により、ctrl c キーの組み合わせを使用すると、フォアグラウンド プロセスにシグナルを送信してプロセスを終了できることがわかりました。
ここに画像の説明を挿入します
また、ctrl \ のショートカット キーもあり、その機能はシグナル 3 をフォアグラウンド プロセスに送信することです。フォアグラウンド プロセスでプロセスを終了し、ここでコードは次のとおりです。
ここに画像の説明を挿入します
したがって、これはキーボードを介してプロセスに信号を送信しています。

シナリオ 2: シグナルを送信するためのシステム コール

1.kill 関数
最初に学習する関数は kill です. この関数の宣言形式は次のとおりです: 最初
ここに画像の説明を挿入します
のパラメータはシグナルがどのプロセスに送信されるかを示し、2 番目のパラメータはどのシグナル番号がプロセスに送信されるかを示します。この関数をもう一度見てください。戻り値:
ここに画像の説明を挿入します
シグナルの送信が成功すると 0 が返され、失敗すると -1 が返されることがわかります。これがこの関数の関数です。その後、個人的なメッセージを書くことができます。指定されたプロセスにシグナルを送信するためのプログラムです。プログラムで使用されるメソッドは、プログラムの実行時に 2 つのパラメーターを渡すことです。最初のパラメーターはプロセスの PID を表します。2 番目のパラメーターは、送信される特定のシグナルを表すために使用されます。例: の場合、プログラムは次のように設計できます。./myproc 进程的pid 信号的编号プログラムの実行時にパラメータが渡されるため、このプログラムの main 関数には 2 つのパラメータが含まれている必要があります。

#include<iostream>
using namespace std;
int main(int argc,char*argv[])
{
    
    
    
}

次に、プログラムの開始時に、プログラムが正しく使用されているかどうかを判断する必要があります。渡された argc の値が 3 に等しくない場合は、プログラムの正しい使用方法をユーザーに伝える関数を呼び出す必要があります。関数にはパラメータが必要です。どのプロセスが使用されるかをユーザーに伝えるには:

#include<iostream>
#include<string>
using namespace std;
void usage(const string& proc)
{
    
    
    cout<<"\nusage:"<<proc<<"pid signo"<<endl;
}
int main(int argc,char*argv[])
{
    
    
    if(argc!=3)
    {
    
    
        usage(argv[0]);
        exit(1);
    }
}

次に、main 関数の 2 番目のパラメーターを通じて対応する pid とシグナルを取得し、それを整数に変換し、最後に kill 関数を呼び出して対応するシグナルを送信します。

#include<iostream>
#include<string>
#include<signal.h>
#include<stdlib.h>
#include<sys/types.h>
using namespace std;
void usage(const string& proc)
{
    
    
    cout<<"\nusage:"<<proc<<"pid signo\n"<<endl;
}
int main(int argc,char*argv[])
{
    
    
    if(argc!=3)
    {
    
    
        usage(argv[0]);
        exit(1);
    }
    pid_t pid=atoi(argv[1]);
    int signo=atoi(argv[2]);
    kill(pid,signo);
    return 0;
}

次に、まず myproc.cc ファイルが正常に実行されているかどうかをテストします。
ここに画像の説明を挿入します
現在のプログラムが正常に実行できることがわかります。そのため、ここで最初に無限ループ プログラムを実行できます。次に、セッションを開いて
ここに画像の説明を挿入します
myproc を実行します。プログラムはシグナル 20261 と 20261 を渡します。 2:
ここに画像の説明を挿入します
次に、プログラムが自動的に終了することがわかります。同様に、シグナル 3 をプロセスに送信して、「quit」という単語が出力されるのを確認できます。これが kill 関数です
ここに画像の説明を挿入します

2 番目:
raise 関数の宣言は次のとおりです:
ここに画像の説明を挿入します
この関数はプロセスにシグナルを送信することもできますが、プロセスを指定することはできません。対応するシグナルを自分自身に送信することしかできません (たとえば、次のコード)。

int main()
{
    
    
    int cnt=1;
    while(true)
    {
    
    
        cout<<"cnt的值为: "<<cnt<<endl;
        cnt++;
        if(cnt==4)
        {
    
    
            raise(2);
        }
    }
    return 0;
}

ここでは、画面に 3 つの文が表示された後、プロセスが自動的に終了することがわかります。実行結果は次のとおりです
ここに画像の説明を挿入します

3 番目: abort
kill 関数は任意のプロセスに任意のシグナルを送信でき、raise 関数は任意のシグナルをこのプロセスに送信できます。その後、abort 関数は指定されたシグナルのみをこのプロセスに送信できます。これは新しい番号 6 です。それでは、実行しましょうこの関数の導入を見てみましょう:
ここに画像の説明を挿入します
新しい数値 6 の名前を見てみましょう:
ここに画像の説明を挿入します
信号番号 6 に対応するマクロが SIGABRT であることがわかります。したがって、ここでは紹介しません。誰もがそれを紹介できます。それを理解します。

シナリオ 3: ハードウェア例外により信号が生成される

シグナルの生成は、必ずしもユーザーが明示的に送信する必要はありません。たとえば、0 で除算するとプロセスが終了します。たとえば、次のコード:

int main()
{
    
    
    int i=10;
    int j=0;
    int c=i/0;
    while(true)
    {
    
    
        cout<<"如果没有收到信号就会死循环"<<endl;
    }
    return 0;
}

プログラムを実行すると、次のようなシーンが表示されます:
ここに画像の説明を挿入しますprint ステートメントの無限ループはありませんが、例外の内容が報告されていることがわかります。これは、0 による除算が発生すると、プロセスがシグナルを受け取り、次に Who が報告されることを意味します。この信号を送信しましたか?答えは、現在のプロセスがオペレーティング システムからシグナルを受信し、それが SIGFPF シグナル番号 8 であるため、ここではシグナル関数を使用して対応するシグナル番号 8 を変更できます。変更操作はシグナルを終了することではありません。そして情報を出力します:

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
using namespace std;
void handler(int sign)
{
    
    
    cout<<"我收到了信号,信号的编号为:"<<sign<<endl;
}

int main()
{
    
    
    signal(8,handler);
    int i=10;
    int j=0;
    int c=i/0;
    while(true)
    {
    
    
        cout<<"如果没有收到信号就会死循环"<<endl;
    }
    return 0;
}

プログラムの実行コードは次のとおりです。
ここに画像の説明を挿入します
/0 エラーが発生すると、実際に新しい数値 8 が受信され、信号番号 8 の処理メソッドが継続的に実行されることがわかります。ここで問題が発生します。0 は 1 回しか削除しないのに、なぜそうするのでしょうか。私たちはそれを捕らえ続けます?? オペレーティング システムは、シグナル 8 を現在のプロセスに送信する必要があることをどのようにして知るのでしょうか? この問題を解決するには、CPU の構造を理解する必要があります。CPU には多数のレジスタがあり、これらのレジスタにはさまざまな式や関数の計算結果などの大量のデータが格納されます。レジスタは計算結果を保存するだけではなく、式の計算状態も保存する必要があるため、ステータスレジスタと呼ばれるレジスタがあり、このレジスタにはオーバーフローフラグビットと呼ばれるビットがあります。 10 x 0 は無限大です。すると、無限大の結果によりステータス レジスタが 0 から 1 にオーバーフローが変更されます。今回は式の計算に問題がありますが、レジスタにはまだデータが格納されます (オーバーフロー フラグ ビットの役割がここに反映されており、一部の値が保存されるため、この値が正しいかどうかはステータス レジスタによって決まります) CPU に演算例外がある場合、オペレーティング システムはそれを認識する必要があります。では、オペレーティング システムはステータス レジスタのフラグ ビットを通じてどのような例外が発生したかを知ることができるのでしょうか? 次に、現在どのプロセスが実行されているか、どこでプロセスが実行されているかを照会して、どのプロセスに例外があるかを知ることができます。その後、オペレーティング システムはフラグ ビットを変更してシグナルを送信します。では、なぜ情報を出力し続けるのでしょうか? プロセスはシグナルを受信した後に必ずしも終了するとは限らないと前に述べました。終了しない場合は、プロセスがまだスケジュールされていることを意味します。CPU 内にはレジスタが 1 つだけありますが、レジスタ内の内容は CPU に属します。現在のプロセスのコンテキスト 例外が発生すると、変更されたアクションにはこの問題を修正する機能または動作があるか? 答えは「いいえ」なので、プロセスが複数回切り替わり、対応するレジスタの内容が複数回保存または復元されるため、オペレーティング システムは復元のたびにステータス レジスタのオーバーフロー フラグ ビットを認識することになります。次に、プロセス PCB のマーク ビットを変更してシグナルを送信します。信号 8 番の動作を変更すると、信号 8 番の処理動作が引き続き実行されることがわかります。ワイルドポインタを逆参照する場合も同様で、ワイルドポインタを逆参照するとクラッシュが発生し、処理が終了します。たとえば、nullptr アドレスを逆参照すると、このときシグナル 11 番が送信されます。では、なぜオペレーティング システムはワイルド ポインタ エラーが発生したことを認識するのでしょうか? その答えは、仮想アドレスを物理アドレスに変換するには、ページ テーブルに加えて、CPU に統合されたハードウェアであるメモリ管理ユニットである mmu が必要であるということです。

シナリオ 4: ソフトウェア条件により信号が生成される

以前にプロセス間の通信を研究しており、そこではパイプ ファイルの特性がわかっていました。パイプの読み取り端が閉じられ、書き込みセグメントの書き込みが継続されると、オペレーティング システムは sigpipe シグナルを送信してプロセスを終了します。これは古典的なソフトウェアの状態によって引き起こされる例外であるため、ここでは別の例としてアラーム関数を示します。
ここに画像の説明を挿入します

アラーム機能の機能は、プログラムを一定時間実行させることです。時間が経過すると、14 番のシグナルを送信してプロセスを終了します。アラームのパラメーターは、シグナルが何秒後に発生するかを示します。が送信されるので、目覚まし時計を使用して次のコードを実装できます。

int main()
{
    
    
    alarm(1);//程序1秒后会被信号终止
    int cnt=0;
    while(true)
    {
    
    
        cout<<"cnt: "<<cnt++<<endl;
    }
    return 0;
}

プログラムの実行結果は次のようになります。
ここに画像の説明を挿入します
ループが 6w 回実行されたことがわかります。つまり、このコードの意味は、コンピューターが約 1 秒間にデータを蓄積できる回数をカウントすることですが、難しいことではありません。 1 秒間に 60,000 回以上のアクセスがあることがわかりました。実際にはそうは思えませんが、その理由は、ループ内で周辺表示画面に継続的にアクセスする必要があるため、実行速度が非常に遅いためです。コードを変更して cnt をグローバル変数に変更できるため、ループ内で周辺機器のディスプレイにアクセスする必要がなくなります。データを画面に出力しますが、変数 ++ に直接出力します。

int cnt=0;
void handler(int sign)
{
    
    
    cout<<"我收到了信号,信号的编号为:"<<sign<<endl;
    cout<<"cnt的值为: "<<cnt<<endl;
    exit(1);
}
int main()
{
    
    
    signal(SIGALRM,handler);
    alarm(1);//程序1秒后会被信号终止
    while(true)
    {
    
    
        ++cnt;
    }
    return 0;
}

コードの実行結果は次のとおりです。
ここに画像の説明を挿入します
この時点でプログラムが非常に高速に実行されていることがわかりますが、この関数によって発行される例外をソフトウェア例外と呼ぶのはなぜでしょうか。目覚まし時計は実際にはソフトウェアで実装されているため、どのプロセスでもアラーム システム コールを通じてカーネル内に目覚まし時計を設定できます。OS には多数の目覚まし時計が存在する可能性がありますが、オペレーティング システムはこれらの目覚まし時計を管理しますか? 答えは「はい」です。管理方法は、最初に記述してから整理することです。そのため、オペレーティング システムには、目覚まし時計を記述するための対応する構造があり、その後、リンク リストなど、これらの構造を管理するためのデータ構造がオペレーティング システム内に存在します。 、ヒープ、操作 システムは、これらのオブジェクト内のアラーム クロックのいずれかがタイムアウトしたかどうかを常にチェックします。タイムアウトが発生した場合、オペレーティング システムは対応するプロセスにシグナルを送信します。アラーム クロックがタイムアウトしたかどうかのチェックは、次の方法で実行されます。オペレーティング システムなどのソフトウェアの状態です。状態は現在のタイムアウト状態です。つまり、目覚まし時計はソフトウェアの状態です。

コアダンプ

ここに画像の説明を挿入します
シグナルの動作では、Term と Core の両方がプロセスを終了することがわかっていますが、これら 2 つの終了方法の違いは何でしょうか? 期間の終了は正常な終了を示し、オペレーティング システムは追加の操作を実行しませんが、コアの終了は、それが正常な終了ではなく、オペレーティング システムが追加の操作を実行することを示します。たとえば、SIGFPF はコア タイプです。このようなコードが発行されると、オペレーティング システムは次のタイプの例外を送信します。

int main()
{
    
    
    while(true)
    {
    
    
        int arr[10];
        arr[10000]=100;
    }
    return 0}

実行結果は以下の通りです:
ここに画像の説明を挿入します
ここではセグメンテーション違反が発生していることがわかりますが、クラウドサーバー上ではプロセスがコアによって終了されると、当面は明らかな現象が確認できません。クラウド サーバーを表示するには、次のオプションを開く必要があります: ulimit -a
ここに画像の説明を挿入します
ここでのさまざまなオプションにより、パイプラインのサイズや開くことができるファイルの最大数など、現在のクラウド サーバーのさまざまなプロパティが表示されます。属性はコア ファイル サイズです。コア ダンプのサイズを示します。サイズが 0 の場合は、クラウド サーバーがデフォルトでコア ダンプをオフにしていることを意味します。それを確認したい場合は、ulimit -c 1024 を実行してください。コア ダンプをオンにして、サイズ 1024 のデータ ブロックを設計することを意味します。現在のプログラムを再度実行すると、コア ダンプされたロゴが表示されます
ここに画像の説明を挿入します

ここに画像の説明を挿入します

このコア ダンプはコア ダンプを意味し、現在のパスの下に追加のファイルが存在します。ファイル名の後ろにある数字の列は、コア ダンプを発生させたプロセスの PID を示しています。

ここに画像の説明を挿入します

コア ダンプの意味は、例外が発生したときに、プロセスに対応するメモリ上の有効なデータをディスクにダンプすることです。これをコア ダンプと呼びます。ファイルの内容を確認できます。

ここに画像の説明を挿入します

ファイルの内容が理解できないのに、なぜコア ダンプがあるのでしょうか? その理由は、プロセスがクラッシュした場合、プロセスがクラッシュした理由と、どこでクラッシュしたかを知りたいためです。そのため、プログラム クラッシュの原因を確認しやすくするために、オペレーティング システムはプロセス コンテキスト データをディスクに保存します。デバッグをサポートしてください。では、このデータを表示するにはどうすればよいでしょうか? 答えは、最初にファイルをデバッグすることです
ここに画像の説明を挿入します

次に、「core-file」と入力して、新しく生成されたコア ダンプ ファイルを作成します。

ここに画像の説明を挿入します

Enter を押すと、現在のエラーの原因が表示されます。

ここに画像の説明を挿入します
よく見ると、上記はプログラムの 18 行目で例外が発生したことを示していることがわかります。18 行目の内容は arr[10000]=100 であり、これがコア ダンプの関数であることがわかります。プログラムが入っていること 何か異常が発生しました。

信号に関する 2 つの問題

質問 1

ここに画像の説明を挿入します
上の図から、多くの信号処理動作がプロセスを直接終了していることがわかります。では、処理動作はすべて終了であるのに、なぜこれほど多くの種類の信号があるのでしょうか? どういう意味ですか?答えは、異なるシグナルは異なるイベントを表しますが、異なるイベントを処理するアクションは同じである可能性があるということです。これは、プロセスが直接終了しても、異なるシグナルの発生によってその原因を知ることができることを意味します。シグナルは発行され、そして、これらの理由に基づいてプログラムを変更できます。これがさまざまな信号の意味です。

質問2

すべての信号をカスタム キャプチャすることはできますか? たとえば、次のようなプログラムです。

void handler(int sign)
{
    
    
    cout<<"我收到了信号,信号的编号为:"<<sign<<endl;
}
int main()
{
    
    
    for(int i=0;i<=31;i++)
    {
    
    
        signal(i,handler);
    }
    while(true)
    {
    
    
        cout<<"我是一个进程,我的pid为:"<<getpid()<<endl;
    }
    return 0;
}

すべてのシグナルをキャッチした場合、プロセスを強制終了することはできないでしょうか? プログラムを実行した結果は次のとおりです:
ここに画像の説明を挿入します
新しいアカウント No. 2 と新しいアカウント No. 3 の両方がプロセスを終了できると以前に述べましたが、まだ終了できますか? 試してみましょう:
ここに画像の説明を挿入します
機能しないことがわかります。また、他のシグナルも機能しません
ここに画像の説明を挿入します
。これは、プロセスを終了できないことを意味しますか? いいえ、シグナル No. 9 を使用して終了することはできます。
ここに画像の説明を挿入します
したがって、悪意のあるプロセスを終了するために、オペレーティング システムは特定のシグナルの処理方法の変更を許可していません。このようなシグナルを管理者シグナルと呼びます。皆様にも内容を十分にご理解いただけると幸いです。

おすすめ

転載: blog.csdn.net/qq_68695298/article/details/133466288