目次
序文
前にプロセスを紹介したとき、子プロセスが終了すると言いました。親プロセスは子プロセスからリソースを再利用せず、子プロセスはゾンビ状態になります。オペレーティング システムにとって、これはリソース リークであり、これは、親プロセスがプロセスを終了しない限り、オペレーティング システム レベルでもリソース リークになります。そうでない場合、子プロセスは常にゾンビ状態になります。この章では、親プロセスが子プロセスをリサイクルする方法を紹介します。
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 つのシステム コールを通じて子プロセスの終了コードを取得することもできます。