プロセス制御 (Linux)

プロセス制御

フォーク

Linux では、既存のプロセスから新しいプロセスを作成するフォーク機能は非常に重要な機能です。新しいプロセスは子プロセスであり、元のプロセスは親プロセスです。

戻り値:
子プロセスでは 0 を返し、親プロセスでは子プロセスの PID を返し、子プロセスの作成に失敗した場合は -1 を返します。

プロセスが fork を呼び出し、カーネル内の fork コードに制御が移ると、カーネルは次のことを行います。

  • 新しいメモリ ブロックとカーネル データ構造を子プロセスに割り当てます。
  • 親プロセスのデータ構造の内容の一部を子プロセスにコピーします。
  • 子プロセスをシステム プロセス リストに追加します。
  • fork が戻り、スケジューラーのスケジューリングが開始されます。
    ここに画像の説明を挿入

プロセスが終了しました

プロセス終了シナリオ

私たちが作成したプログラムでは、コードが実行されるときに次の 3 つの状況が発生します。

  1. コードは最後まで実行され、正しく実行されます - コードの終わり
  2. コードは最後まで実行されますが、結果は正しくありません
  3. コードの異常終了

main関数のreturnの戻り値はプロセスの終了コードです!

ここに画像の説明を挿入

テスト概要を入力した後、return によって返される値は main 関数の終了コードです。

> [外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-Gk5jqfJM-1685794022709)(G:\Mynote\image\image-20230526182653011.png) )]
return の戻り値の意味 (他にもたくさんありますが、私が表現したいのは、0 のみが成功で、その他はエラー理由であるということです):

ここに画像の説明を挿入

コードの異常終了

平たく言えば、コードが途中で実行され、エラーが報告されて操作が終了しました。つまり、プログラムがクラッシュしたのです。
クラッシュ後のプログラムの戻り値は意味がありません。例:

ここに画像の説明を挿入

プロセスの終了方法

  • main 関数の return はプロセスが終了することを意味します
  • 非メイン関数の戻り値 - 関数の戻り値
  • どこででも exit を呼び出すことはプロセスを終了することを意味し、パラメータはすべて終了コードです。

[外部リンク画像の転送に失敗しました。ソース サイトには盗難防止リンク メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-REiidq7G-1685794022710)(G:\Mynote\image\image-20230526193200792.png) )]

  • exit はプロセスを終了する return と同じです
    ここに画像の説明を挿入ここに画像の説明を挿入

  • _exit はプロセスを終了する、プロセスを強制終了する、バッファをリフレッシュしないなどプロセスを終了しない。exit() はバッファ (ユーザーバッファ) をフラッシュします。
    ここに画像の説明を挿入

プロセスは終了時に OS レベルで何をしましたか?

  • システム レベルでは、空き PCB、空き mm_struct、空きページ テーブル、およびさまざまなマッピング関係のプロセスが 1 つ減り、コード + データ アプリケーション用のスペースも空きとして与えられます。プロセスは異常終了します

ケース 1: プロセスへのシグナルにより、プロセスが異常終了します。

たとえば、プロセスの実行中に、kill -9 シグナルがプロセスに送信されてプロセスが異常終了するか、Ctrl+C を使用してプロセスが異常終了します。

状況 2: コード エラーにより、実行中のプロセスが異常終了します。

たとえば、コード内にワイルド ポインタの問題があり、実行中にプロセスが異常終了する場合や、0 で除算する状況によって実行中にプロセスが異常終了する場合などが考えられます。

プロセス待機中

プロセス待ちとは何ですか?

fork() ;
子プロセス: 親プロセスが特定のタスクを完了できるように支援します 親プロセス: 親
プロセスが子プロセスがどのようなタスクを実行したかを知りたい場合は、wait/waitpid を使用して子プロセスが終了するのを待つ必要があります

なぜ親プロセスは子プロセスを待つ必要があるのでしょうか?
1. 子プロセスの終了情報を取得することで、プロセスの実行結果が分かる!
2. タイミングの問題、子プロセスが最初に終了し、親プロセスが後で終了することは保証できます。
3. プロセスが終了すると、まずゾンビ状態になり、メモリリークが発生するため、親プロセスの wait! を介して子プロセスが占有しているリソースを解放する必要があります。

注: プロセスがゾンビ プロセスになると、無敵になります。死んだプロセスを強制終了する方法がないため、Kill-9 ではプロセスを殺すことはできません。

ゾンビプロセスを解決するにはどうすればよいですか?

wait を使用して子プロセスをリサイクルする

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
  pid_t id = fork();
  if(id == 0)
  {
    //child
    int cnt = 5;
    while(cnt)
    {
      printf("child[%d] is running: cnt- is:%d\n",getpid(),cnt);
      cnt--;
      sleep(1);
    }
    exit(0);//子进程在这里结束
  }

  sleep(10);
  printf("fahter wait begin\n");
  pid_t ret = wait(NULL);
  if(ret > 0)
  {
  
  printf("fahter wait :%d, success\n", ret);
  }
  else{
    printf("father wait failed!\n");
  }
  sleep(10);
  return 0;
}

効果は次のとおりです。

ここに画像の説明を挿入
概要: wait によりゾンビプロセスがリサイクルされる可能性がある

ここに画像の説明を挿入

status は、子プロセスの終了時に 3 種類のフィードバックを受け取るのに役立ちます。

プログラムコードの実行結果の正誤はプロセスの終了コードで判断されますが、「始皇帝が途中で亡くなった」ではなく、コードが最後まで実行されたことをどうやって証明するのでしょう- プログラムがコード例外のために終了する必要がある場合に、プログラムによって受信されるその他のシグナル。したがって、プロセスがシグナルを受信したかどうかを判断して、プロセスが異常終了したかどうかを判断できます。次の表をどう見るか?
ここに画像の説明を挿入
一連のビット操作を通じて、ステータスに応じてプロセスの終了コードと終了シグナルを取得できます。
説明: 下 8 桁は終了ステータス (終了コード)、下 7 桁は終了信号です。したがって、終了コードを判断するだけでよく、その下位7ビットが0である場合、シグナルは受信されません。

exitCode = (ステータス >> 8) & 0xFF; //終了コード
exitSignal = ステータス & 0x7F; //終了信号

システムには、終了コードと終了信号を取得するための 2 つのマクロが用意されています。

  1. WIFEXITED(ステータス): プロセスが正常に終了したかどうかを確認するために使用されます。本質は、シグナルが受信されたかどうかを確認することです。
  2. WEXITSTATUS(ステータス): プロセスの終了コードを取得するために使用されます。

exitNormal = WIFEXITED(status); //正常に終了したか
exitCode = WEXITSTATUS(status); //終了コードを取得

プロセスが異常終了する場合、プロセスがシグナルによって強制終了されたことを意味するため、プロセスの終了コードは無意味であることに注意してください。
プロセスの待機方法

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
  pid_t id = fork();
  if(id == 0)
  {
    //child
    int cnt = 5;
    while(cnt)
    {
      printf("child[%d] is running: cnt- is:%d\n",getpid(),cnt);
      cnt--;
      sleep(1);
    }
    exit(0);
  }

  sleep(10);
  printf("fahter wait begin\n");
 // pid_t ret = wait(NULL);
 // pid_t ret = waitpid(id,NULL,0);等待指定一个进程
 // pid_t ret = waitpid(-1,NULL,0);//等待任意一个子进程
  int status = 0;
  pid_t ret = waitpid(-1,&status,0);
  if(ret > 0)
  { 
  printf("fahter wait :%d, success, status exit code:%d ,status exit signal%d\n", ret,
  (status>>8)&0xFF, status & 0x7F);//看信号位与退出码
  }
  else{
    printf("father wait failed!\n");
  }
  sleep(10);
  return 0;

ここに画像の説明を挿入

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
  printf("i am a child process!,pid :%d ,ppid: %d \n",getpid(),getppid());
  exit(10);
}

結果:

私は子プロセスです!、pid:20681、ppid:19703

ここに画像の説明を挿入

プロセス /a.out の親プロセスは実際には -bash であることがわかりました。bash はすべての起動プロセスの親プロセスです。bash はどのようにしてプロセスの終了結果を取得するのでしょうか? プロセスの終了結果は wait! を通じて取得する必要があります。したがって、echo を使用して子プロセスの終了コードを表示できます。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
  pid_t id = fork();
  if(id == 0)
  {
    //child
    int cnt = 5;
    while(cnt)
    {
      printf("child[%d] is running: cnt- is:%d\n",getpid(),cnt);
      cnt--;
      sleep(1);
    }
    exit(0);
  }

  sleep(5);
  printf("fahter wait begin\n");
 // pid_t ret = wait(NULL);
 // pid_t ret = waitpid(id,NULL,0);等待指定一个进程
 // pid_t ret = waitpid(-1,NULL,0);//等待任意一个子进程
  int status = 0;
  pid_t ret = waitpid(id,&status,0);
  if(ret>0)
  {
    if(WIFEXITED(status))//没有收到任何的退出信号的
    {
    //正常结束的,获取对应的退出码!
      printf("exit code: %d\n",WEXITSTATUS(status));
    }
    else{
      printf("error, get s signal!\n");
    }
  }
}

ホワイトピット

作用:waitpid会暂时停止进程的执行,直到有信号来到或子进程结束。
函数说明:

waitpid() が呼び出されたときに子プロセスが終了している場合、waitpid() は直ちに子プロセスの終了ステータス値を返します。
パラメータstatusには子プロセスの終了ステータス値が返され、子プロセスのプロセス識別コードも併せて返されます。
終了ステータスの値を気にしない場合は、パラメータのステータスを NULL に設定できます。

パラメータ:
パラメータ pid は待ち合わせ対象の子プロセスの識別コードで、その他の値の意味は以下のとおりです。

  1. pid<-1 プロセスグループ ID が pid の絶対値である子プロセスを待ちます。
  2. pid=-1 は、wait() と同等の子プロセスを待機します。
  3. pid=0 は、プロセスと同じプロセス グループ ID を持つ子プロセスを待ちます。
  4. pid>0 は、サブプロセス ID が pid であるサブプロセスを待ちます。

パラメータステータス: 出力パラメータ。子プロセスの終了ステータスを取得します。気にしない場合は、NULL に設定できます。
パラメータオプション: WNOHANG に設定した場合、待機中の子プロセスが終了していない場合、waitpid 関数は待機せずに直接 0 を返します。正常終了した場合は子プロセスのpidが返されます。

ウェイトピットを理解する

ここに画像の説明を挿入

ブロック

ブロッキングとノンブロッキング

*ブロックとは、平たく言えば、ガールフレンドと一緒に出かけるが、彼女が化粧をしたいので、あなたは彼女が待たなければならないことを意味します。

ノンブロッキングなので、ガールフレンドと一緒に過ごすことができますが、彼女は化粧が必要で、あなたは彼女を待たなければなりませんが、彼女が降りるまで5分以内に電話して尋ねてください。——この方法はノンブロッキングポーリング方式に基づいています。*

注: ブロッキングとノンブロッキングはどちらも待機方法です。

ブロッキングの本質: 実際には、プロセスの PCB が待機キューに入れられ、プロセスの状態が S 状態に変更される リターンの本質: プロセスの PCB が待機キューから取得されて、S 状態に変更さ
れるR キューに追加され、CPU によってスケジュールされます。

ノンブロッキング待機

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
  pid_t id = fork();
  if(id == 0)
  {
    //child
    int cnt = 3;
    while(cnt)
    {
      printf("child[%d] is running: cnt- is:%d\n",getpid(),cnt);
      cnt--;
      sleep(1);
    }
    exit(0);
  }

  int status = 0;
  while(1)
  {
     pid_t ret = waitpid(id,&status,WNOHANG);                                                                                                                                                                  
      if(ret == 0)                            
      {                                                                   
        // 子进程没有退出,但是waitpid等待是成功的,需要父进程重新进行等待
        printf("父进程运行");
      }                                                                   
      else if (ret > 0)                                                   
      {                                                                   
        // 子进程退出,waitpid也成功l,获取到对应的结果了
        printf("获得子进程的退出码%d,子进程的退出信号%d",(status>>8)&0xFF,status&0x7F);
        break;
      }                                                     
      else                                               
      {                                                  
        // 等待进程失败
        perror("waitpid");
        break;
      }
  }
  sleep(1);
}

プロセスプログラムの置き換え

fork を使用して子プロセスを作成した後、子プロセスは親プロセスと同じプログラムを実行します (ただし、別のコード分岐が実行される場合があります)。子プロセスに別のプログラムを実行させたい場合は、多くの場合、exec 関数を呼び出す必要があります。

プロセスが exec 関数を呼び出すと、プロセスのユーザー空間コードとデータは新しいプログラムによって完全に置き換えられ(復活)、新しいプログラムのスタートアップ ルーチンから実行が開始されます。

ここに画像の説明を挿入

上の図を見ると、子プロセスのアドレス空間(仮想メモリ)やページテーブルは変更されておらず、物理メモリのみが変更されており、物理メモリのデータとコードを変更することで、子プロセスが実行されるように変更できます新しい内容(復活)になりますが注意してください 本来、子プロセスと親プロセスは共通の空間(コードとデータ)を共有していますが、現実的には変更後は子プロセスのデータがコピーされます。子プロセスと親プロセスの関係はなくなりました** (父親は教師で、息子は会社を経営しています)* *

どうやって交換するのかと疑問に思う人もいるでしょう。? (子供たち、クエスチョンマークがたくさんありますか?)

置換機能(execシリーズ機能)

exec で始まる置換関数が 6 つあり、総称して exec 関数と呼ばれます。

実行

int execl(const char *path, const char *arg, ...);

最初のパラメータはプログラムを実行するパスで、2 番目のパラメータはプログラムの実行方法を示す変数パラメータ リストで、NULL で終わります。

たとえば、ls プログラムが実行されます。

execl("/usr/bin/ls", "ls", "-a", "-i", "-l", NULL);

こともできます

execl("/usr/bin/ls", "ls", "-ali", NULL);

execlp

int execlp(const char *file, const char *arg, ...);

最初のパラメータは実行するプログラムの名前で、2 番目のパラメータは変数パラメータのリストで、プログラムの実行方法を示し、NULL で終わります。

たとえば、ls プログラムが実行されます。

execlp("ls", "ls", "-a", "-i", "-l", NULL);

実行する

int execle(const char *path, const char *arg, ..., char *const envp[]);

最初のパラメータは実行するプログラムのパス、2 番目のパラメータはプログラムの実行方法を示す変数パラメータ リストで、NULL で終わります。3 番目のパラメータは自分で設定した環境変数です。

たとえば、MYVAL 環境変数を設定すると、その環境変数を mycmd プログラム内で使用できます。

char* myenvp[] = { "MYVAL=qwe", NULL };
execle("./mycmd", "mycmd", NULL, myenvp);

実行

int execv(const char *path, char *const argv[]);

最初のパラメータはプログラムを実行するパスで、2 番目のパラメータはポインタの配列で、配列の内容はプログラムの実行方法を示し、配列は NULL で終わります。

たとえば、ls プログラムが実行されます。

char* myargv[] = { "ls", "-a", "-i", "-l", NULL };
execv("/usr/bin/ls", myargv);
12

execvp

int execvp(const char *file, char *const argv[]);

最初のパラメータは実行するプログラムの名前、2 番目のパラメータはポインタの配列です。配列の内容はプログラムの実行方法を示し、配列は NULL で終わります。

たとえば、ls プログラムが実行されます。

char* myargv[] = { "ls", "-a", "-i", "-l", NULL };

執行する

int execve(const char *path, char *const argv[], char *const envp[]);

最初のパラメータはプログラムを実行するパス、2 番目のパラメータはポインタの配列、配列の内容はプログラムの実行方法を示し、配列は NULL で終わり、3 番目のパラメータは設定した環境変数です。あなた自身。

たとえば、MYVAL 環境変数を設定すると、その環境変数を mycmd プログラム内で使用できます。

char* myargv[] = {
    
     "mycmd", NULL };
char* myenvp[] = {
    
     "MYVAL=qwe", NULL };
execve("./mycmd", myargv, myenvp);

おすすめ

転載: blog.csdn.net/qq_45591898/article/details/131025132