Linux | プロセスの終了とプロセスの待機中

目次

序文

1. プロセスの終了

1. プロセス終了のいくつかの可能性 

2、出口と_exit

2. プロセス待ち

1. プロセスはなぜ待機する必要があるのでしょうか?

2. 処理の待ち方

(1) ウェイト機能 

(2) waitpid関数

3. 再度待機しているプロセスを深く理解する


序文

        前にプロセスを紹介したとき、子プロセスが終了すると言いました。親プロセスは子プロセスからリソースを再利用せず、子プロセスはゾンビ状態になります。オペレーティング システムにとって、これはリソース リークであり、これは、親プロセスがプロセスを終了しない限り、オペレーティング システム レベルでもリソース リークになります。そうでない場合、子プロセスは常にゾンビ状態になります。この章では、親プロセスが子プロセスをリサイクルする方法を紹介します。

1. プロセスの終了

1. プロセス終了のいくつかの可能性 

        子プロセスのリサイクルを導入する前に、プロセスの終了についてある程度理解しておく必要があります。いわゆるプロセスの終了とは、プロセスが終了することを指します。ここでは、プロセスが終了するまで待つ必要があり、次の 3 つの可能性があります。

1. プログラムは正常に終了し、結果は正しいです。

2. プログラムは正常に終了しますが、結果は正しくありません。

3. プログラムはクラッシュしますが、結果は重要ではありません。

        おそらく、これまでプロセスの終了について何も知らなかったのでしょう。上記の 3 つの状況をどのように区別すればよいでしょうか?たとえば、仮想スタジオで C/C++ プログラムを実行していたときは、常に main 関数を記述し、通常は main 関数の最後に return 0 を記述していました。実際、この 0 は 。結果が正しいかどうかを判断するためにこれを使用します。終了コードには対応する説明があります。strerror を通じて終了コードの意味を出力できます。関数; これはケース 1 とケース 2 を区別するためです. 方法, ケース 3, プログラムがクラッシュすると、当社のソフトウェア仮想スタジオは通常、プログラムがクラッシュした場所を示すポップアップ ウィンドウをポップアップ表示します。最も一般的なものは除算です。ゼロによるエラー、null ポインタの逆参照など、プログラムがクラッシュする原因になります。クラッシュ; 終了コードを出力しましょう。次のコード;終了コード

        上記のプログラムをコンパイルして実行すると、結果は次のようになります。

        エラー コード情報は 133 番まで編集できることがわかりました。最初のいくつかの項目についてもよく知っていますが、最初の項目 0 は成功を意味し、結果が正しいことを意味します。

2、出口と_exit

        main 関数内では、return ステートメントを介してプロセスを終了させ、戻り値を返すことができると前に述べましたが、main 関数内ではない場合はどうなるでしょうか?それでは、main 関数に戻ってから return ステートメントを呼び出す必要があるのでしょうか?実際には、次のコードのように、exit 関数と _exit 関数を使用してプロセスを終了することもできます。

        コードをコンパイルすると、結果は次のようになります。これはコマンド echo $? です。最近実行したプログラムの戻り値を表示できます。-1 以外の値を入力すると、戻り値は 0 であることがわかります。これは main 関数の return ステートメントにあり、-1 を入力すると、戻り値は 14 になります。これは、exit 関数を呼び出したときの戻り値です。

        上記の exit 関数 exit を _exit に置き換えても同じ機能を実現できますが、その違いは何でしょうか?次のコードを見てください。

        exit 関数を使用すると、実行結果は次のようになります。

        _exit 関数を使用すると、実行結果は次のようになります。

        あなたには私が見えなくなりました。これは、印刷されたコンテンツがまだ C 言語バッファーにあり、_exit がシステム コールであり、終了する前にバッファーを更新できないためです。また、exit は C 言語ライブラリ関数です。バッファをリフレッシュします。

補足: C言語のバッファリフレッシュ機構は行リフレッシュですが、印刷時に改行文字がないため、exit関数を使用すると印刷されますが、_exit関数を使用すると印刷されません。

2. プロセス待ち

1. プロセスはなぜ待機する必要があるのでしょうか?

        まず、この理由は前述しましたが、子プロセスが終了すると、親プロセスは子プロセスからリソースを再利用せず、子プロセスは常にゾンビ状態になり、この状態がシステムリソースのリークを引き起こします。プロセスを待って、子プロセスの「死体を収集」する必要があります。

        次に、サブプロセスにタスクを完了させる場合、そのタスクを完了するためにサブプロセスが必要でしょうか?一定の時間、そのような要求があるため、プロセス待機のもう 1 つの機能は、サブプロセス タスクの完了ステータスを取得することです。

2. 処理の待ち方

(1) ウェイト機能 

        プロセスの待機方法については、通常、システム コール wait および waitpid を通じて実装しますが、最初に wait の関数宣言を確認します。

        この関数には出力パラメータというパラメータが 1 つだけあります。いわゆる出力パラメータとは、ポインタを渡し、関数はこのポインタによって実行された値を割り当てて、それを返します。この出力パラメータは終了コードです。と子プロセスの終了シグナルですが、ここでは一時的に NULL に設定し、waitpid が導入されるまで待ってから導入します。

        この関数の戻り値は、呼び出しが成功した場合は子プロセスの pid を返し、失敗した場合は -1 を返します。エラー コードが設定されているため、次のコードを記述します。親プロセスが子プロセスをリサイクルしたかどうかを確認します。 ;

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

int main()
{
    int id = fork();
    if(id == -1)
    {
        // fork调用失败
        exit(1);
    }
    else if(id == 0)
    {
        // 子进程
        int cnt = 5;
        while(cnt--)
        {
            printf("我是子进程,我的pid:%d,ppid:%d\n", getpid(), getppid());
            sleep(1);
        }
        exit(14); // 退出码
    }
    else  
    {
        // 父进程
        sleep(7);
        wait(NULL); // 进程等待
        sleep(2);
    }
    return 0;
}

        次に、コマンド ラインに次のスクリプト コマンドを入力して、これら 2 つのプロセスを監視します。

その間 :; ps -axj を実行します。ヘッド -1 && ps -axj | grep テスト | grep -v grep;睡眠1;エコー「-------------------」;終わり

        最初の数秒は実際に実行されていたが、予想どおり親プロセスが子プロセスよりも 2 秒長くスリープしていたため、子プロセスは途中で 2 秒間ゾンビ状態になっていることがわかりました。その後、親プロセスだけがスリープ状態になりました。プロセスは残されました。子プロセスは親プロセスの Recycle によって正常に置き換えられました。

(2) waitpid関数

        以下はマニュアルによるクエリ結果です。

パラメータpid:

ピド 効果
pid <; -1 プロセスグループ番号が pid の絶対値である子プロセスを待ちます。
pid = -1 子プロセスを待機しているとき、waitpid() 関数はこの時点で通常の wait() 関数に縮退します。
pid = 0 現在のプロセスと同じプロセス グループ番号を持つ子プロセス、つまり waitpid() 関数を呼び出しているプロセスと同じプロセス グループ内のプロセスを待ちます。
ピッド > 0 プロセス番号 pid の子プロセスを待機しています。

 

注: プロセス グループ番号については、当面はここでは説明しません。あまり使用しません。最も一般的に使用されるのは 2 と 4 です。

パラメータのステータス:

        このパラメータは wait 関数と同じ出力パラメータであり、このパラメータの使用に関しては、int 値を全体として使用することはできず、ビットごとに個別に使用する必要があります。

ケース 1:通常通り終了する

        このとき、7 ビット目から 15 ビット目までを終了コードとして使用します。この終了コードは (status >> 8) & 0xFF を通じて取得することも、マクロ関数 WEXITSTATUS を使用して取得することもできます。関数 WIFEXITED は、プロセスが正常に終了したかどうかを取得します。正常に終了した場合は true を返し、そうでない場合は false を返します。 

ケース 2:信号が終了して終了します (異常終了)

        ここでは、信号終了を示すために次のビット 0 ~ 6 を使用します。ステータス & 0x7F を使用してこの終了信号を取得できます。コア ダンプ フラグ ビットについては、ここではまだ説明しません。これは別のトピックです。

パラメータオプション:

        デフォルトでは、このパラメータには 0 が設定されており、ブロック待機を意味します。WNOHANG が入力されている場合は、非ブロック待機を意味します。

戻り値:

waitpid の戻り値は wait よりも少し複雑で、3 つの状況があります。

1. 通常どおりに戻り、子プロセスの PID を返します。

2. WNOHANG が設定されており、子プロセスが終了していない場合は 0 が返され、子プロセスが終了した場合は子プロセスの pid が返されます。

3. 呼び出しが失敗した場合は、-1 が返され、エラー コードが設定されます。

コードの練習:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

int main()
{
    int id = fork();
    if(id == -1)
    {
        // fork调用失败
        exit(1);
    }
    else if(id == 0)
    {
        // 子进程
        int cnt = 5;
        while(cnt--)
        {
            printf("我是子进程,我的pid:%d,ppid:%d\n", getpid(), getppid());
            sleep(1);
        }
        exit(14); // 退出码
    }
    else  
    {
        // 父进程
        sleep(7);
        int status = 0;
        // 进程等待
        int ret = waitpid(id, &status, 0);  // 此时与我们的wait函数功能相同
        if(WIFEXITED(status))
        {
            printf("子进程正常退出,退出码为%d\n", WEXITSTATUS(status));
        }
        else
        {
            printf("子进程异常退出,收到信号%d\n", (status) & 0x7F);
        }
        sleep(2);
    }
    return 0;
}

        コードの出力は次のとおりです。

        ゼロ除算エラーをコードに追加すると、

        実行結果は以下の通りです。

        kill -l でシグナルを確認します。シグナル 8 は浮動小数点計算の問題です。

        コードを再度変更し、次のように waitpid を非ブロック待機に変更します。

#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

int main()
{
    int id = fork();
    if(id == -1)
    {
        // fork调用失败
        exit(1);
    }
    else if(id == 0)
    {
        // 子进程
        int cnt = 5;
        // 除零错误展示
        // int a = 10/ 0;
        while(cnt--)
        {
            printf("我是子进程,我的pid:%d,ppid:%d\n", getpid(), getppid());
            sleep(1);
        }
        exit(14); // 退出码
    }
    else  
    {
        // 父进程
        int status = 0;
        // 进程等待
        while(true)
        {
            int ret = waitpid(id, &status, WNOHANG); 
            if(ret == -1)
            {
                printf("waitpid调用失败\n");
                exit(-1);
            }
            else if(ret == 0)
            {
                printf("子进程还未退出,我再干点别的\n");
                sleep(1);
            }
            else
            {
                printf("等待成功\n");
                break;
            }
        }
        
        if(WIFEXITED(status))
        {
            printf("子进程正常退出,退出码为%d\n", WEXITSTATUS(status));
        }
        else
        {
            printf("子进程异常退出,收到信号%d\n", (status) & 0x7F);
        }
        sleep(2);
    }
    return 0;
}

        実行結果は次のとおりです。この時点では、親プロセスは子プロセスをブロックして終了を待つ必要はなく、ポーリングによって子プロセスをリサイクルするだけで済みます。

3. 再度待機しているプロセスを深く理解する

        ここまではプロセス待機の実践的な部分でしたが、ここで再び理論的な部分に戻りますが、次のような疑問があります。

質問 1:グローバル変数を通じて子プロセスの終了コードを取得できますか?

        いいえ、親プロセスと子プロセスはコードを共有していますが、それぞれ独自のプロセス アドレス空間を持っています。グローバル変数を使用する場合、子プロセスがこのグローバル変数に書き込むと、コピーオンライトが発生します。したがって、終了コードは取得できません。これはプロセスの独立性でもあります。

質問 2:プロセスは独立しているため、wait と waitpid はどのようにして子プロセスの終了コードを取得しますか?

        wait と waitpid はシステム コールです。システム コールなので、もちろんオペレーティング システムの一部です。子プロセスをリサイクルすると、実際にはプロセスの PCB やその他のカーネル データが破棄され、 PCB (task_struct) コードおよび終了信号フィールド。以下は Linux ソース コードのスクリーンショットです。

        これらのフィールドは task_struct で見つかりました。興味がある場合は、公式 Web サイトからソース コードのコピーをダウンロードできます。task_strcut 構造体は include/linux/sche.h にあります。

        トピックに戻りますが、task_struct にこれらのフィールドがあるため、親プロセスが子プロセスをリサイクルするときにこれらのフィールドの情報を取得できますか?答えはもちろん「はい」です。wait と waitpid はシステム コールであり、もちろんこれらのフィールドを取得する資格があります。親プロセスは、これら 2 つのシステム コールを通じて子プロセスの終了コードを取得することもできます。

おすすめ

転載: blog.csdn.net/Nice_W/article/details/134068513