実行関数ファミリー
fork によって子プロセスが作成されると、親プロセスと同じプログラムが実行されます (ただし、別のコード分岐が実行される場合もあります)。子プロセスは、別のプログラムを実行するために exec 関数を呼び出すことがよくあります。プロセスが exec 関数を呼び出すと、プロセスのユーザー空間コードとデータは、コア プログラムのスタートアップ ルーチンから始まる新しいプログラムによって完全に置き換えられます。exec を呼び出しても新しいプロセスは作成されないため、exec を呼び出す前後でプロセス ID は変わりません。
execlp関数
PATH 環境変数を使用してプロセスをロードします
int execlp(cosnt char* file, const char* arg,...); 成功: no return 失敗: -1 パラメータ
1: ロードするプログラムの名前。この関数は環境変数 PATH を指定して使用する必要があり、PATH 内のすべてのディレクトリを検索した後、パラメーター 1 が存在しない場合はエラーが返されます。
この関数は通常、システム プログラムを呼び出すために使用されます。例: ls、date、cp、cat、その他のコマンド
Execlp は子プロセスに ls -l -a を実装します
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void){
pid_t pid;
pid = fork();
if(pid == -1){
perror("fork error");
exit(1);
}else if(pid > 0){
sleep(1);
printf("parent\n");
}else{
execlp("ls","ls","-l","-a",NULL);
}
return 0;
}
execl関数
パス + プログラム名によるプロセスのロード
int execl(const char* path, const char* arg,...); 成功: 戻りません 失敗: -1
と、-l -F パラメータを指定して "ls" コマンドをロードするような execlp の比較
execl("/bin/ls","ls","-l","-F",NULL);
execl を使用して実現します
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void){
pid_t pid;
pid = fork();
if(pid == -1){
perror("fork error");
exit(1);
}else if(pid > 0){
sleep(1);
printf("parent\n");
}else{
execl("bin/ls","ls","-l","-a",NULL);
}
return 0;
}
execvp 関数は、
カスタム環境変数 env
int execvp(const char* file, const char* argv[]); を使用してプロセスをロードします。
dup2
プロセス情報をファイルに出力する
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int main(void){
int fd;
fd = open("ps.out",O_WRONLY|O_CREAT|O_TRUNC,0644);
if(fd<0){
perror("open ps.out error");
exit(1);
}
dup2(fd, STDOUT_FILENO);
execlp("ps","ps","ax",NULL);
return 0;
}
ゾンビおよび孤立プロセス
孤立プロセス: 親プロセスが子プロセスより先に終了すると、子プロセスは孤立プロセスになり、子プロセスの親プロセスが init プロセスになります。これを init プロセスは孤立プロセスを採用します。
ゾンビプロセス: プロセスが終了し、親プロセスがリサイクルされておらず、子プロセスの残留リソース (PCB) がカーネルに格納され、ゾンビプロセスになります。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void){
pid_t pid;
pid = fork();
if(pid == -1){
perror("fork");
exit(1);
}else if(pid > 0){
sleep(1);
printf("parent pid = %d, parentID = %d\n",getpid(),getppid());
}else if(pid == 0){
printf("child pid = %d, parentID = %d\n",getpid(),getppid());
sleep(3);
printf("child pid = %d, parentID = %d\n",getpid(),getppid());
}
return 0;
}
ここで、親プロセスと子プロセスの最初の出力には問題がないことがわかりますが、3 秒間スリープした後、親プロセスは終了し、子プロセスの 2 番目の出力は孤児院に入るため、2 番目の子プロセスはprocess 出力されるparentIDは1で、
対応するディレクトリは/sbin/initです。ここでのinitプロセスは最終的に孤立プロセスをリサイクルします。
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(void){
pid_t pid;
pid = fork();
if(pid == 0){
printf("i am child, my parent = %d,going to sleep 10s\n",getppid());
sleep(10);
printf("-------child die-------");
}else if(pid > 0){
while(1){
printf("i am parent, pid= %d, myson = %d\n",getppid(),pid);
sleep(1);
}
}else{
perror("fork");
return 1;
}
return 0;
}
コンパイルして実行すると、ps aux にもう 1 つ [ ] ファイルがあることがわかります。これはゾンビ ファイルです。
待機機能
プロセスが終了すると、すべてのファイル記述子が閉じられ、ユーザー空間に割り当てられたメモリが解放されますが、PCB はまだ予約されており、カーネルはそこに情報を保存します。正常に終了した場合は終了ステータスを保存し、正常に終了した場合は終了ステータスを保存します。異常終了 どのシグナルがプロセスを終了させたかを格納します。このプロセスの親プロセスは、wait または waitpid を呼び出してこの情報を取得し、プロセスを完全にクリアできます。
親プロセスは、wait 関数を呼び出して、子プロセスの終了情報を再利用します。この関数には 3 つの機能があります。
1. サブプロセスをブロックして終了を待つ
2. サブプロセスの残存リソースを再利用する
3. サブプロセスの終了ステータス (終了理由) を取得する
pid_t wait(int *status); 成功: 子プロセス ID をクリーンアップしました 失敗: -1 (子プロセスなし)
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(void){
pid_t pid,wpid;
pid = fork();
if(pid == 0){
printf("i am child, my parent = %d,going to sleep 10s\n",getppid());
sleep(10);
printf("-------child die-------");
}else if(pid > 0){
wpid = wait(NULL);
if(wpid == -1){
perror("wait error");
exit(1);
}
while(1){
printf("i am parent, pid= %d, myson = %d\n",getppid(),pid);
sleep(1);
}
}else{
perror("fork");
return 1;
}
return 0;
}
コンパイルして実行した後、ps aux でチェックします。[ ] ファイル プロセスはなく、ゾンビ プロセスが待機によってリサイクルされたことを示しています。
waitpid関数
wait 関数呼び出しでリサイクルできる子プロセスは 1 つだけです。子プロセスが 5 つある場合は、waitpid 関数を使用する必要があります。
関数は wait と同じですが、クリーンアップする pid プロセスを指定でき、ブロックすることはできません
pid_t waitpid(pid_t pid, int* status, in options); 成功: クリーンアップされた子プロセス ID を返します 失敗: -1 (子プロセスなし)
パラメータ pid:
>0 指定された ID の子プロセスをリサイクルします
-1 任意の子プロセスをリサイクルします (wait と同等)
0 waitpid の現在の呼び出しと同じグループ内のすべての子プロセスをリサイクルします
<-1 任意の子プロセスをリサイクルします指定されたプロセスグループ内
パイプライン
IPC 方式:
Linux 環境では、プロセスのアドレス空間は互いに独立しており、各プロセスは異なるユーザー アドレス空間を持ちます。どのプロセスのグローバル変数も別のプロセスでは見ることができないため、プロセスは相互にアクセスできません。データを交換するには、カーネルを介してカーネル内にバッファを作成する必要があります。プロセス 1 はデータをユーザー空間からカーネル バッファにコピーします。プロセス 2 はカーネル バッファからデータを読み取ります。カーネルによって提供されるこのメカニズムはプロセス間通信と呼ばれます。
プロセス間のデータ転送を完了するには、ファイル、パイプ、シグナル、共有コンテンツ、メッセージ キュー、ソケット、名前付きパイプなど、オペレーティング システムによって提供される特別なメソッドが必要です。コンピュータの活発な発展に伴い、いくつかの方法は、それ自体の欠陥のために廃止または放棄されました。現在一般的に使用されているプロセス間通信方法は次のとおりです。
1. パイプライン (最も使いやすい)
2. シグナル (最小限のオーバーヘッド)
3. 共有マッピング領域 (血縁関係なし)
4. ローカル ソケット (最も安定した)
パイプラインの概念:
パイプラインは最も基本的な IPC メカニズムであり、血液関連プロセス間で機能してデータ送信を完了します。パイプはパイプ システム関数を呼び出すことで作成できます。以下の特徴があります。
1. 本質は疑似ファイル (実際にはカーネルバッファ)
2. 2 つのファイル記述子によって参照され、1 つは読み取り端を示し、もう 1 つは書き込み端を示します
。 3. データの流れを規定します。パイプラインの書き込み側、読み取り側からパイプラインに挿入されます。
パイプラインの原理: パイプラインは実際にカーネルのリング キュー メカニズムを使用し、カーネル バッファー (4k) の助けを借りて実装されます。
パイプラインの制限:
1. データを単独で書き込むことができない
2. 一度読み出したデータはパイプライン内に存在せず、繰り返し読み込むことができない 3.
パイプラインは半二重通信モードを採用しているため したがって、データは一方向にのみ流れることができます
。 4. パイプは、共通の祖先を持つプロセス間でのみ使用できます。
名前のないパイプを作成します。
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4
5 int main(void){
6 int fds[2];
7 int ret = -1;
8
9 //create a pipe
10 ret = pipe(fds);
11 if(-1 == ret){
12 perror("pipe");
13 return 1;
14 }
15
16 //fds[0] is for reading fds[1] is for writing
17 printf("fds[0]: %d fds[1]: %d\n",fds[0],fds[1]);
18
19 close(fds[0]);
20 close(fds[1]);
21
22 return 0;
23 }
出力結果:
fds[0]: 3 fds[1]: 4
0 1 2 は標準入力、標準出力、標準エラーによって占められるため、ここでの出力は 3 と 4 になります。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define SIZE 64
//父进程使用无名管道进行通信
//父进程写管道 子进程读管道
int main(void){
pid_t pid = -1;
int fds[2];
char buf[SIZE];
int ret = -1;
//创建无名管道
ret = pipe(fds);
if(-1 == ret){
perror("pipe");
return 1;
}
//创建进程
pid = fork();
if(-1 == pid){
perror("fork");
return 1;
}
//子进程
if(0 == pid){
//关闭写端
close(fds[1]);
memset(buf, 0, SIZE);
//读管道的内容
ret = read(fds[0], buf, SIZE);
if(ret < 0){
perror("read");
exit(-1);
}
printf("child process buf: %s\n",buf);
//关闭读端
close(fds[0]);
exit(0);
}
//父进程
//关闭读端
close(fds[0]);
ret = write(fds[1],"ABCDEGHIJK",10);
if(ret < 0){
perror("write");
return 1;
}
printf("parent process write len: %d\n",ret);
//关闭写端
close(fds[1]);
return 0;
}
出力結果:
親プロセスの書き込み長: 10
子プロセスのバッファ: ABCDEGHIJK
パイプラインの読み取りおよび書き込みの特性
要約:
読み取りパイプライン:
パイプラインにデータがあり、読み取りは実際に読み取られたバイト数を返します。
パイプラインにデータがない:
パイプラインのすべての書き込み端が閉じられ、読み取りは 0 を返します (ファイルの最後まで読み取るのと同等)。すべての書き込み終了が閉じられていない場合
、読み取りはブロックされません。待機します (近い将来にデータ配信が行われる可能性があり、この時点で CPU は解放されます)
書き込みパイプ:
パイプのすべての読み取り端が閉じられ、プロセスが異常終了します (プロセスは、SIGPIPE 信号をキャプチャすることによって終了することもできます)。パイプの読み取り端がすべて閉じられていない: パイプがいっぱいで
、
書き込みブロック
パイプがいっぱいではありません。write はデータを書き込み、実際に書き込まれたバイト数を返します。
ulimit -a のパイプ サイズはパイプライン バッファーのサイズです。
有名なパイプライン
パイプには名前がないため、アフィニティを使用したプロセス間通信にのみ使用できます。この欠点を克服するために、名前付きパイプ (FIFO)、または名前付きパイプ FIFO ファイルが提案されています。
FIFO とパイプにはいくつかの共通点がありますが、相違点は次のとおりです。
1. FIFO はファイル システム内の特殊なファイルとして存在しますが、FIFO の内容はメモリに保存されます
。 2. FIFO を使用するプロセスが終了しても、FIFO ファイルは引き続き保存されます。将来の使用に備えてファイル システムに保存されます。
3. FIFO には名前があり、無関係なプロセスは名前付きパイプを開いて通信できます。
mkfifo fifo は コマンドを通じて既知のパイプを作成し、その内容がメモリに配置されるためサイズが 0 であることがわかります。
注:
1. 読み取り専用のパイプを開くプロセスは、別のプロセスが書き込み専用のパイプを開くまでブロックされます。 2. 書き込み専用のパイプを開くプロセスは、
別のプロセスが読み取り専用のパイプを開くまでブロックされます。パイプを開ける