目次
プロセス制御関連API
ps プロセス制御の状態遷移に関連する API は、ユーザーが使用することはほとんどないため、ここでは説明しません。
一般に、これらのカーネル標準 API は、実行エラー (リソース不足、権限不足など) が発生した場合に負の値 (-1 など) を返し、errno 値を設定します。
親プロセスは子プロセス fork() を作成します
Linux では、子プロセスを作成するために、親プロセスは fork() システム コールを使用して子プロセスを作成します。fork() は実際には親プロセスのコピーを作成します (子プロセスには、識別、ステータス、データ領域 (スタック領域とデータ領域) などの独自の特性があります (これらは子プロセスに固有です)。プロセスと親プロセス プログラム コードの共通使用、共有タイム スライス (これらは共有されるなど)。
通常、fork 関数を呼び出した後、プログラムは if 選択構造を設計します。PID が 0 に等しい場合、プロセスが子プロセスであることを意味し、exec ライブラリ関数 (ライブラリ関数) を使用して別のプログラム ファイルを読み取り、現在のプロセス空間で実行するなど、特定の命令を実行させます。これは実際には、fork を使用する主な目的の 1 つは、特定のプログラムのプロセスを作成することです); PID が正の整数の場合、それが親プロセスであることを意味し、他の命令が実行されます。このことから、子プロセスが確立された後は、親プロセスとは異なる機能を実行できます。
pid_t fork();
, fork() は、子プロセスの場合は 0 を返し、親プロセスの場合は子プロセスの ID を返します。0 未満の値を返すとエラーになります。#include<stdio.h> #include<unistd.h> int main() { int p_num = 0; int c_num = 0; int pid = fork(); if(pid == 0) //返された pid は 0 であり、これは子プロセスです { c_num++; } それ以外 { p_num++; //親プロセスとして返される pid は 0 より大きい } printf("p_num=%d, c_num=%d\n",p_num,c_num); printf("pid=%d\n",pid); 0を返します。 } //実行結果は以下の通り p_num=1、c_num=0 pid=36101 p_num=0、c_num=1 pid=0子プロセスは、常にその PPID を照会して、その親プロセスが誰であるかを知ることができるため、親プロセスと子プロセスのペアがいつでも相互に照会できます。
他の:
-
fork() のコピーオンライトの概念はオンラインで見つけることができます。つまり、リソースは使用されたときにのみコピーされます。変更する必要のないリソースはコピーされません。システム消費量が多い操作は、必要になるまで延期するようにしてください。
-
vfork() は一般的には使用されず、実装に完全に問題がないわけではないため、概念を理解するにはオンラインで検索してください。
プロセス分離実行ファミリー関数
フォークを通過した後、子プロセスは親プロセスから独立しておらず、同じコードを使用します。もう一つ問題があるのですが、このとき子プロセスのタイムスライスは2つに分割され、親プロセスと共有されます。親プロセスと子プロセスを完全に分離するには、別のプログラムファイルを読み込み、現在のプロセス空間で実行するシステムコールのexecファミリー関数を使用します。プロセスを作成した後、通常は子プロセスを新しいプロセス イメージに置き換えます。これは exec 一連の関数を使用して実行でき、新しいプロセスは元のプロセスと同じ PID を持ちます。
Linux での exec 関数ファミリー (execl、execv、execle、execve、execlp、execvp、fexecve) の使用と比較を参照してください。Leumber のブログ - CSDN ブログ exec および execv、exec シリーズ関数 (execl、execlp、execle) の使用、 execv、 execvp ) _gauss のブログ - CSDN ブログ各 API の使用例、 exec と execv-CSDN の違いがあります。
各 API プロトタイプ: #include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
受信パラメータ: path パラメータは、パス名を含む起動するプログラムの名前を示し、file パラメータは、起動するプログラム/ファイルのファイル名を示します (システムは環境変数 PATH からプログラムを探します。したがって、完全なパス名を含める必要はありません); arg パラメータ スタートアップ プログラムによって運ばれるパラメータを示します。通常、最初のパラメータはパスではなく、実行されるコマンドの名前であり、arg は NULL で終わる必要があります;
戻り値: 成功した場合は 0、失敗した場合は -1 を返します。
exec ファミリ関数名では、l はリストを表し、v は配列を表します。
execl、execlp、および execle は、新しいプログラムのコマンド ライン パラメータごとに個別のパラメータを使用します。このパラメータ リストは NULL で終了します。
execv、execvp、execve、fexecve の場合は、まず各パラメータを指すポインタの配列を構築し、次に配列アドレスをパラメータとして渡す必要があります。
exec ファミリ関数名の末尾の p は、関数の最初のパラメータがファイル名であることを示します。
他の関数の execlp と execvp の違いは、最初のパラメータが filename であり、実行可能ファイルの検索に PATH 環境変数が使用されることです。filename には、ファイル パスとプログラム名のいずれか、または /sbin: / を指定できます。 bin: PATH 環境変数の下の /usr /bin: シェルコマンド。
exec ファミリ関数名の末尾の e は、環境テーブル情報 environ を渡すことができることを示します。
execle、execve、fexecve には、環境文字列ポインターの配列へのポインターを渡すことができます。
// プロセス.c #include<stdio.h> #include<unistd.h> int main() { int pid = fork(); if(pid == 0) { execv("./test.o",NULL); // test.o はコンパイルされた C 言語ファイルです。ここに test.o の絶対パスを忘れずに入力してください。 } printf("これは親プロセスです\n"); 0を返します。 } // テスト.c #include<stdio.h> int main() { printf("これは子プロセスです"); 0を返します。 } //実行結果は以下の通り これは親プロセスです これは子プロセスですexecファミリー関数の使用例
/* exec.c */ #include <unistd.h> 主要() { char *envp[]={"PATH=/tmp","USER=lei","STATUS=testing",NULL}; /* 配列は NULL で終わる必要があります*/ char *argv_execv[]={"echo", "execv によって実行", NULL}; char *argv_execvp[]={"echo", "execvp によって実行", NULL}; char *argv_execve[]={"env", NULL}; if(fork()==0) if(execl("/bin/echo", "echo", "executed by execl", NULL)<0) /* パスの完全名。渡されたすべてのパラメータを書き込み、NULL で終わります*/ perror("execl でエラー"); if(fork()==0) if(execlp("echo", "echo", "executed by execlp", NULL)<0) /* 実行可能プログラムのファイル名のみを書き込みます。システムはそれを PATH 環境変数で検索します*/ pererror("execlp でエラー"); if(fork()==0) if(execle("/usr/bin/env", "env", NULL, envp)<0) /* 環境変数を渡すことができます*/ pererror("実行時のエラー"); if(fork()==0) if(execv("/bin/echo", argv_execv)<0) /* v が付いたパラメータはポインタ データ (文字列データ) として渡され、他の API は上記と同じです*/ pererror("execv でエラー"); if(fork()==0) if(execvp("echo", argv_execvp)<0) perror("execvp でエラー"); if(fork()==0) if(execve("/usr/bin/env", argv_execve, envp)<0) pererror("実行時のエラー"); } /* ./exec 実行後にリターン: execlによって実行される パス=/tmp ユーザー=レイ ステータス=テスト中 execlp によって実行される execvによって実行される execvpによって実行される パス=/tmp ユーザー=レイ ステータス=テスト中 */exec ファミリ関数の一般的なエラーが返されます (exec は -1 を返し、errno を次の値に設定します)。
ファイルまたはパスが見つからず、errno が ENOENT に設定されています。
配列 argv と envp は NULL で終わることを忘れており、この時点では errno は EFAULT に設定されています。
実行対象のファイルには実行権限がなく、errnoがEACCESに設定されています。
待ってください。エラー戻りにはさまざまな種類があります。
さらに注意すべき点:
実際の操作では、通常、exec 関数を呼び出す前に、開いているすべてのファイルが閉じられます。fcntl() を通じてカーネルに実行させることもできます。
プロセス終了 return/exit()
process_Leon_George のブログ - CSDN ブログ、オペレーティング システム - プロセス終了 (exit) Dawn_sf のブログ - CSDN ブログ、終了プロセス exit、 終了関数、および return_panda19881 のブログ - CSDN ブログのいくつかの終了メカニズムを参照してください。
いくつかの終了方法
通常終了
main() 関数で return を実行します (renturn が実行された後、制御は呼び出し元の関数に渡されます)。
exit() 関数を呼び出します (exit が実行された後、制御はシステムに渡されます)。
_exit() 関数を呼び出します (上記と同じ)。
突然辞める
abort 関数を呼び出します (exit はプロセスの正常終了を意味し、abort は異常終了を意味し、例外を強調表示します)。
プロセスは、プログラムを終了させるシグナルを受け取ります。
exit()
_exit()
違い:このexit()
関数は_exit()
関数の最上位にあるラッパーであり、関数が呼び出され、_exit()
呼び出す前にストリーム (stdin、stdout、stderr...) をフラッシュします。つまり、ファイル バッファーの内容をファイルに書き戻します。exit はヘッダー ファイル stdlib.h で宣言され、_exit() はヘッダー ファイル unistd.h で宣言されます。を使用する方がexit()
安全です。exit のパラメータ exit_code が 0 の場合は、プロセスが正常に終了したことを意味しますexit(0);
。それ以外の値の場合は、プログラムの実行中にエラーが発生したことを意味します。
exit
return
との違い: main 関数内に return または exit がある場合、2 つの関数は同じです (つまり、return 0;
とexit(0);
同じです)。サブルーチン内に return が表示される場合、それは戻ることを意味します (単に、それが配置されている関数またはサブプロセスを終了/終了することを意味します)。一方、exit が子プロセス内に表示される場合、これは子プロセスの終了を意味します。ただし、どの終了方法が使用されても、システムは最終的にカーネル内で特定のコードを実行します。このコードは、プロセスによって使用されている開いているファイル記述子を閉じ、プロセスが占有しているメモリおよびその他のリソースを解放するために使用されます。
親プロセスと子プロセスの終了順序が異なると、異なる結果が生じます。
子プロセスは親プロセスより前に終了し、親プロセスは wait 関数を呼び出します。子プロセスは終了し、親プロセスによってリサイクルされます (良好)
このとき、親プロセスは子プロセスが終了するまで待機します。
親プロセスが子プロセスより前に終了する:子プロセスが孤立プロセスになる (中)
この状況は、以前に使用した孤立プロセスです。親プロセスが最初に終了すると、システムは init プロセスに子プロセスを引き継がせます。孤立プロセスは init プロセスに採用され、init プロセスがプロセスの親プロセスになります。init プロセスは、子プロセスが終了するときに wait 関数を呼び出す責任があります。init プロセスは、子プロセスが終了すると wait 関数を呼び出します。
子プロセスが親プロセスより先に終了し、親プロセスが wait 関数を呼び出さない場合、子プロセスはゾンビプロセスになります (悪い)
この場合、子プロセスはゾンビ状態になり、システムが再起動されるまでゾンビ状態のままになります。子プロセスがゾンビ状態にある場合、カーネルは親プロセスのプロセスに関する必要な情報のみを保存します。このとき、子プロセスは常にリソースを占有するため、システムが作成できるプロセスの最大数も減少します。
したがって、この状況が起こらないようにする必要があります。そうしないと、ゾンビ プロセスがリソースを占有するだけでなく、ますます多くのリソースが蓄積されてしまいます。また、プログラムが悪いと、子プロセスの終了情報がカーネル内に残る(親プロセスが子プロセスの wait 関数を呼び出さない)場合もあり、この場合、子プロセスはゾンビプロセスとなります。ゾンビプロセスが大量に蓄積するとメモリ空間が占有されてしまいます。
ゾンビ状態: プロセスは終了したが、その親プロセスがまだ処理 (終了した子プロセスに関する情報を取得し、まだ占有しているリソースを解放) していないプロセスは、ゾンビ プロセス (ゾンビ) と呼ばれます。
ゾンビプロセスと孤立プロセスの定義:
要約: これら 3 つのプロセスが終了するとき、最も良い状況は、親プロセスが wait を呼び出し、終了した子プロセスが正常にリサイクルされることです。2つ目は、親プロセスが早期に終了する / 子プロセスが孤立プロセスと呼ばれる / 子プロセスが正常にリサイクルされることです。 initプロセスに採用/initプロセスが存在する 子プロセスが終了するとwait関数が呼び出される最悪の場合、親プロセスがwaitを呼ばずに子プロセスが終了/子プロセスがゾンビプロセスになる
プロセスの終了時に呼び出される関数を登録します。#include <stdlib.h> int atexit (void (*function)(void));
登録された関数は、exit();
main の呼び出し時または main からの終了時、またはプロセスを終了するシグナル (SIGTERM または SIGKILL) の受信時に 1 回呼び出されます。登録された関数を再度呼び出すことはできませんexit()
。呼び出すと、無限再帰が発生します。
プロセスのブロック wait()
実行状態のプロセスは、キーボード入力の待機、ディスクデータの送信完了の待機、他のプロセスからの情報の送信の待機など、実行中のプロセス中に何らかのイベントが発生することを期待しますが、待機時間が発生しない場合は、プロセス自体がブロッキングを実行し、実行状態をブロッキング状態に変更するためのプリミティブです。
つまり、シグナル (シグナル) を待機するスリープ/ブロック、または子プロセスの終了 (リソースの再利用) を待機する親プロセスです。
ps 信号待ちについては以下のセクションで紹介し进程间通讯(IPC)
ます信号(Signal)
。ここでは、子プロセスが終了するのを待っている (そしてそのリソースを再利用する) 親プロセスについてのみ説明します。
exec と execv の違いを引用- CSDN (振り返ってみると、もっと美しくフォーマットを整理していたことに気づきました...)。
親プロセスの待機ブロックは、子プロセスが終了するまで待機します (その後、そのリソースが再利用されます)。
子プロセスが終了すると、カーネルは SIGCHILD シグナルを親プロセスに送信します。
子プロセスが終了すると、親プロセスに通知し、子プロセスが占有しているメモリをクリアし、自身の終了情報をカーネルに残します (終了コード。スムーズに実行された場合は 0、エラーまたは異常な状態が発生した場合は、 > 0 の整数です)。親プロセスは、子プロセスが終了したことを認識すると、子プロセスに対して wait システム コールを使用する責任があります (そうしないと、子プロセスがゾンビ プロセスになるため、可能な限り回避する必要があります)。この待機関数は、カーネルから子プロセスの終了情報を取得し、カーネル内でこの情報が占めていた領域をクリアできます。
#include <sys/types.h> /* タイプ pid_t の定義を提供します*/ #include <sys/wait.h> pid_t wait(int *status);プロセスが wait を呼び出すと、そのプロセスは直ちに自身をブロックします。wait は、現在のプロセスの子プロセスが終了したかどうかを自動的に分析します。ゾンビ化した子プロセスを発見した場合、wait は子プロセスに関する情報を収集し、それを破棄します。戻る前に完全に完了し、そのような子プロセスが見つからない場合は、子プロセスが現れるまで待機します。
status パラメータは、収集されたプロセスの終了時にステータスを保存するために使用され、int 型へのポインタです。ただし、子プロセスがどのようにダンプされるかは気にせず、ゾンビ プロセスを破棄したいだけの場合は、このパラメータを NULL に設定できます。
成功した場合、wait は収集された子プロセスのプロセス ID を返します。呼び出したプロセスに子プロセスがない場合、呼び出しは失敗します。このとき、wait は -1 を返し、errno は ECHILD に設定されます。
例:
/* wait1.c */ #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <stdlib.h> 主要() { pid_t PC、PR; pc = フォーク(); if(pc < 0) /* エラーが発生した場合*/ printf("エラーが発生しました!\n"); else if(pc == 0){ /* 子プロセスの場合*/ printf("これは pid が %d の子プロセスです\n",getpid()); sleep(10); /* 10 秒間スリープします*/ } else{ /* 親プロセスの場合*/ pr = wait(NULL); /* ここでは子プロセスの終了を待ち、その終了戻り値は気にしません*/ printf("pid が %d の子プロセスをキャッチしました\n"),pr); } 終了(0); }終了戻り値をさらに分析するためのマクロ:
WIFEXITED(status) このマクロは、子プロセスが正常に終了したかどうかを示すために使用され、正常に終了した場合はゼロ以外の値を返します。(名前は同じですが、ここでのパラメータ status は、wait - status の唯一のパラメータ、整数へのポインタではなく、そのポインタが指す整数と異なることに注意してください)
WEXITSTATUS(status) WIFEXITED がゼロ以外の値を返す場合、このマクロを使用して子プロセスの戻り値を抽出できます。子プロセスが exit(5) を呼び出して終了すると、WEXITSTATUS(status) は 5 を返します。子プロセスが exit(7) を呼び出します)、WEXITSTATUS(status) は 7 を返します。プロセスが正常に終了しない場合、つまり WIFEXITED が 0 を返す場合、この値は意味がないことに注意してください。
それについては、また別の記事でお話ししましょう。
pid_t wait(int *status);
、wait システムコールにより、子プロセスが終了するか、親プロセスがシグナルを受信するまで、親プロセスはブロックされます。親プロセスがなく、子プロセスも存在しないか、その子プロセスが終了した場合は、wait はすぐに戻ります。成功した場合 (子プロセスの終了により)、wait は子プロセスの ID を返します。それ以外の場合は、-1 を返し、グローバル変数 errno を設定します。status は子プロセスの終了ステータスです。子プロセスは、exit / _exit または return を呼び出して、この値を設定します。この値を取得するために、Linux はこの戻り値をテストするいくつかのマクロを定義します。別の記事では、いくつかのマクロの違いについて具体的に説明しています。
#include <sys/wait.h> int WIFEXITED (ステータス); int WIFSIGNALED (ステータス); int WIFSTOPPED (ステータス); int WIFCONTINUED (ステータス); int WEXITSTATUS (ステータス); int WTERMSIG (ステータス); int WSTOPSIG (ステータス); int WCOREDUMP (ステータス);waitpid はブロックし、特定の PID の子プロセスが終了するのを待ちます。
#include <sys/types.h> /* タイプ pid_t の定義を提供します*/ #include <sys/wait.h> pid_t waitpid(pid_t pid,int *status,int オプション);waitpid() パラメータの説明:
pid:
pid>0 の場合、プロセス ID が pid に等しい子プロセスのみを待機します。他の子プロセスがどれだけ実行を終了して終了しても、指定された子プロセスが終了しない限り、waitpid は待機し続けます。
pid=-1 の場合、子プロセスの終了を制限なく待ちます (このとき、waitpid と wait はまったく同じ機能を持ちます)。
pid=0 の場合、同じプロセス グループ内の子プロセスを待ちます。子プロセスが別のプロセス グループに参加している場合、waitpid はそれを考慮しません。
pid<-1 の場合、指定されたプロセス グループ内の、ID が pid の絶対値と等しい子プロセスを待ちます。
status: 子プロセスの終了戻り値を受け入れ、使用しない場合は NULL を入力します。
options: options は、waitpid を制御するための追加オプションを提供します。現在、Linux では、WNOHANG と WUNTRACED の 2 つのオプションのみがサポートされています。これらは 2 つの定数です。使用したくない場合は、「|」演算子を使用して接続できます。 、オプションを 0 に設定することもできます (使用しない場合は 0 に設定します)。
WNOHANG: 子プロセスが存在しない場合でも、すぐに戻り、wait のように永遠に待機することはありません。
WUNTRACED: 追跡とデバッグに関するある程度の知識が必要ですが、ほとんど使用されません。興味のある読者は、関連する資料を自分で確認してください。
戻り値:
waitpid の戻り値は wait よりも少し複雑で、次の 3 つの状況があります。
正常に戻る場合、waitpid は収集された子プロセスのプロセス ID を返します。
オプション WNOHANG が設定されており、呼び出し中に waitpid が収集する終了した子プロセスを検出しない場合は、0 が返されます。
呼び出し中にエラーが発生した場合は、-1 が返され、errno にはエラーを示す対応する値が設定されます。
pid で示される子プロセスが存在しない場合、またはプロセスは存在するが呼び出しプロセスの子プロセスではない場合、waitpid はエラーで戻り、errno には ECHILD が設定されます。
子プロセスの終了ステータスが返され、status に保存されます。終了ステータスを決定するための以下のマクロがいくつかあります。
子プロセスが正常に終了した場合、WIFEXITED(status) はゼロ以外の値になります。
WEXITSTATUS(status) は、子プロセス exit() が返す終了コードを取得します。通常、このマクロを使用する前に、まず WIFEXITED を使用して正常終了かどうかを判断します。
WIFSIGNALED(status) シグナルによって子プロセスが終了した場合、このマクロ値は true になります。
WTERMSIG(status)はシグナルにより終了した子プロセスのシグナルコードを取得するもので、通常はWIFSIGNALEDを使用してこのマクロを使用する前に判断します。
WIFSTOPPED (ステータス) 子プロセスが中断されている場合、このマクロ値は true になります。通常、これは WUNTRACED が使用されている場合にのみ発生します。
WSTOPSIG(status) は、子プロセスを一時停止させたシグナル コードを取得します。
その他のAPI
ヘッド ファイル:
#include <unistd.h>; #include <pwd.h>; #include <sys/types.h>;
-
pid_t getpid(void);
、現在のプロセスの PID を取得します。pid_t getppid(void);
、現在のプロセスの親プロセスの pid を取得します。 -
プロセスの実際のユーザーと効果的なユーザーに関連する概念:
-
実際のユーザー API を変更します。
-
有効なユーザー API を変更します。
-
ユーザーIDを取得:
uid_t getuid(void);
、uid_t geteuid(void);
、プロセスの所有者ユーザーの ID と実効ユーザー ID をそれぞれ取得します。gid_t getgid(void);
、git_t getegid(void);
、はそれぞれグループ ID と実効グループ ID を取得します。 -
ユーザーに関する詳細情報を取得します。
struct passwd { /* この構造体はtypes.hで定義されています */ char *pw_name; /* ログイン名*/ char *pw_passwd; /* ログインパスワード */ uid_t pw_uid; /* ユーザー ID */ gid_t pw_gid; /* ユーザーグループ ID */ char *pw_gecos; /* ユーザーの本名*/ char *pw_dir; /* ユーザーのディレクトリ*/ char *pw_shell; /* ユーザーのシェル */ }; struct passwd *getpwuid(uid_t uid); /* ユーザーIDがuidであるユーザー情報のstruct passwd構造体ポインタを返す*/
-
sleep(x);
、x 秒間ブロック/遅延します。 -
strerror(errno) は、指定されたエラー番号のエラー情報の文字列を返します。
-
等