これは、Linuxでのプロセス間通信(IPC)に関するシリーズの2番目の記事です。共有ストレージを介したIPCに焦点を当てた最初の記事:共有ファイルと共有メモリセグメント。この記事では、通信のためにプロセスを接続するチャネルであるパイプについて説明します。チャネルには書き込み終了があります バイトの書き込み用、および読み取り終了 これらのバイトをFIFO(先入れ先出し)の順序で読み取るため。通常の使用では、1つのプロセスがチャネルに書き込み、別のプロセスがこの同じチャネルから読み取ります。バイト自体は、数字、従業員の記録、デジタル映画など、何でも表すことができます。
パイプには、名前付きと名前なしの2つの種類があり、コマンドラインからインタラクティブに使用することも、プログラム内で使用することもできます。例は近日公開されます。この記事では、時代遅れになっているメモリキューについても説明しますが、当然のことながらそうなっています。
最初の記事のコード例では、共有ストレージを使用するIPCでの競合状態(ファイルベースまたはメモリベース)の脅威を認識しています。チャネルベースのIPCの安全な同時実行性については、当然のことながら疑問が生じます。これについては、この記事で説明します。パイプとメモリキューのコード例では、POSIX承認のスタンプが付いたAPIを使用しており、POSIX標準の主要な目標はスレッドセーフです。
mq_openのマニュアルページを検討してください。 メモリキューAPIに属する関数。これらのページには、この小さなテーブルの属性に関するセクションが含まれています:
インターフェース | 属性 | 値 |
mq_open() | スレッドセーフ | MT-Safe |
値MT-Safe ( MT マルチスレッドの場合)は、 mq_openを意味します 関数はスレッドセーフです。これは、プロセスセーフを意味します。プロセスは、そのスレッドの1つが実行されるという意味で正確に実行され、同じのスレッド間で競合状態が発生しない場合 プロセスでは、このような状態は異なるプロセスのスレッド間で発生することはありません。 MTセーフ 属性は、 mq_openの呼び出しで競合状態が発生しないことを保証します 。一般に、チャネルベースのIPCは同時安全ですが、以下の例では注意が必要です。
名前のないパイプがどのように機能するかを示す、考案されたコマンドラインの例から始めましょう。最新のすべてのシステムでは、垂直バーは | コマンドラインで名前のないパイプを表します。 %と仮定します はコマンドラインプロンプトであり、次のコマンドを検討してください。
% sleep 5 | echo "Hello, world!" ## writer to the left of |, reader to the right
スリープ およびecho ユーティリティは個別のプロセスとして実行され、名前のないパイプによって通信が可能になります。ただし、この例は、通信が発生しないという点で考案されています。あいさつHello、world! 画面に表示されます。次に、約5秒後、コマンドラインプロンプトに戻り、両方がスリープであることを示します。 およびecho プロセスが終了しました。何が起こっているのですか?
コマンドラインからの垂直バー構文で、左側のプロセス( sleep )はライターであり、右側のプロセス( echo )はリーダーです。デフォルトでは、リーダーはチャネルから読み取るバイトができるまでブロックし、ライターは(バイトを書き込んだ後)ストリーム終了マーカーを送信して終了します。 (ライターが途中で終了した場合でも、ストリーム終了マーカーがリーダーに送信されます。)名前のないパイプは、ライターとリーダーの両方が終了するまで存続します。
[Linuxでのプロセス間通信の完全ガイドをダウンロード]
考案された例では、 sleep プロセスはチャネルにバイトを書き込みませんが、約5秒後に終了します。これにより、ストリーム終了マーカーがチャネルに送信されます。その間、エコー このプロセスはチャネルからバイトを読み取らないため、プロセスはすぐにグリーティングを標準出力(画面)に書き込み、待機しません。 スリープ およびecho プロセスが終了すると、名前のないパイプ(通信にはまったく使用されません)がなくなり、コマンドラインプロンプトに戻ります。
これは、2つの名前のないパイプを使用したより便利な例です。ファイルtest.dat 次のようになります:
this
is
the
way
the
world
ends
コマンド:
% cat test.dat | sort | uniq
catからの出力をパイプします (連結)プロセスをソートに ソートされた出力を生成するプロセスを実行してから、ソートされた出力を uniqにパイプします。 重複レコードを削除するプロセス(この場合、 theの2つのオカレンス 1つに減らす):
ends
is
the
this
way
world
これで、名前のないパイプを介して通信する2つのプロセスを持つプログラムのシーンが設定されました。
#include <sys/wait.h> /* wait */
#include <stdio.h>
#include <stdlib.h> /* exit functions */
#include <unistd.h> /* read, write, pipe, _exit */
#include <string.h>
#define ReadEnd 0
#define WriteEnd 1
void report_and_exit(const char* msg) {
perror(msg);
exit(-1); /** failure **/
}
int main() {
int pipeFDs[2]; /* two file descriptors */
char buf; /* 1-byte buffer */
const char* msg = "Nature's first green is gold\n"; /* bytes to write */
if (pipe(pipeFDs) < 0) report_and_exit("pipeFD");
pid_t cpid = fork(); /* fork a child process */
if (cpid < 0) report_and_exit("fork"); /* check for failure */
if (0 == cpid) { /*** child ***/ /* child process */
close(pipeFDs[WriteEnd]); /* child reads, doesn't write */
while (read(pipeFDs[ReadEnd], &buf, 1) > 0) /* read until end of byte stream */
write(STDOUT_FILENO, &buf, sizeof(buf)); /* echo to the standard output */
close(pipeFDs[ReadEnd]); /* close the ReadEnd: all done */
_exit(0); /* exit and notify parent at once */
}
else { /*** parent ***/
close(pipeFDs[ReadEnd]); /* parent writes, doesn't read */
write(pipeFDs[WriteEnd], msg, strlen(msg)); /* write the bytes to the pipe */
close(pipeFDs[WriteEnd]); /* done writing: generate eof */
wait(NULL); /* wait for child to exit */
exit(0); /* exit normally */
}
return 0;
}
pipeUN 上記のプログラムは、システム関数 fork を使用します プロセスを作成します。プログラムにはソースファイルが1つしかありませんが、(成功した)実行中にマルチプロセッシングが発生します。ライブラリがどのように機能するかについての簡単なレビューの詳細は次のとおりですフォーク 作品:
- フォーク 親で呼び出される関数 プロセス、 -1を返します 失敗した場合は親に。 pipeUN たとえば、呼び出しは次のとおりです。
pid_t cpid = fork(); /* called in parent */
戻り値は、この例では、変数 cpidに格納されます。 整数型のpid_t 。 (すべてのプロセスには独自のプロセスID があります 、プロセスを識別する非負の整数。)新しいプロセスのフォークは、完全なプロセステーブルなど、いくつかの理由で失敗する可能性があります。 、プロセスを追跡するためにシステムが維持する構造。ゾンビプロセスは、まもなく明らかになりますが、これらが収集されていない場合、プロセステーブルがいっぱいになる可能性があります。
- フォークの場合 呼び出しが成功すると、新しい子プロセスが生成(作成)され、1つの値が親に返されますが、別の値が子に返されます。親プロセスと子プロセスの両方が同じを実行します フォークの呼び出しに続くコード 。 (子は、親でこれまでに宣言されたすべての変数のコピーを継承します。)特に、フォークの呼び出しが成功しました。 戻り値:
- 子プロセスへのゼロ
- 親に対する子のプロセスID
- if / else または同等の構成は通常、フォークが成功した後に使用されます 親向けのコードを子向けのコードから分離するための呼び出し。この例では、構成は次のとおりです。
if (0 == cpid) { /*** child ***/
...
}
else { /*** parent ***/
...
}
子のフォークが成功した場合、 pipeUN プログラムは次のように進行します。整数配列があります:
int pipeFDs[2]; /* two file descriptors */
2つのファイル記述子を保持します。1つはパイプへの書き込み用で、もう1つはパイプからの読み取り用です。 (配列要素 pipeFDs [0] は読み取り側のファイル記述子であり、配列要素は pipeFDs [1] は書き込み終了のファイル記述子です。)システムへの正常な呼び出しパイプ strong> フォークを呼び出す直前に作成された関数 、配列に2つのファイル記述子を入力します:
if (pipe(pipeFDs) < 0) report_and_exit("pipeFD");
その他のLinuxリソース
- Linuxコマンドのチートシート
- 高度なLinuxコマンドのチートシート
- 無料のオンラインコース:RHELの技術概要
- Linuxネットワーキングのチートシート
- SELinuxチートシート
- Linuxの一般的なコマンドのチートシート
- Linuxコンテナとは何ですか?
- 最新のLinux記事
親と子は両方のファイル記述子のコピーを持っていますが、関心の分離 パターンは、各プロセスが記述子の1つだけを必要とすることを意味します。この例では、役割を逆にすることもできますが、親が書き込みを行い、子が読み取りを行います。子の最初のステートメントif -したがって、句コードはパイプの書き込み終了を閉じます:
close(pipeFDs[WriteEnd]); /* called in child code */
親の最初のステートメントelse -句コードはパイプの読み取り端を閉じます:
close(pipeFDs[ReadEnd]); /* called in parent code */
次に、親は名前のないパイプにいくつかのバイト(ASCIIコード)を書き込み、子はこれらを読み取り、標準出力にエコーします。
プログラムのもう1つの側面は、明確にする必要があります。待機の呼び出しです。 親コードの関数。スポーンされると、子プロセスは、短い pipeUN のように、その親からほとんど独立しています。 プログラムが示しています。子は、親とは関係のない任意のコードを実行できます。ただし、子が終了した場合、システムはシグナルを介して親に通知します。
親が子の前に終了した場合はどうなりますか?この場合、予防策を講じない限り、子供はゾンビになり、そのままになります。 プロセステーブルのエントリを使用して処理します。注意事項には大きく分けて2種類あります。予防策の1つは、親が子の退職に関心がないことを親にシステムに通知させることです。
signal(SIGCHLD, SIG_IGN); /* in parent: ignore notification */
2番目のアプローチは、親に待機を実行させることです。 子供の終了時に、それによって親が子供より長生きすることを保証します。この2番目のアプローチは、 pipeUNで使用されます。 プログラム。親コードには次の呼び出しがあります:
wait(NULL); /* called in parent */
この待機の呼び出し 子の終了が発生するまで待つことを意味します 、および pipeUN プログラムでは、子プロセスは1つだけです。 ( NULL 引数を整数変数のアドレスに置き換えて、子の終了ステータスを保持することができます。)より柔軟な waitpidがあります。 いくつかの中から特定の子プロセスを指定するなど、きめ細かい制御のための機能。
pipeUN プログラムは別の予防策を講じます。親が待機を終了すると、親は通常の出口への呼び出しで終了します 働き。対照的に、子は _exitへの呼び出しで終了します バリアント。終了の通知を迅速に追跡します。事実上、子は、子が終了したことをできるだけ早く親に通知するようにシステムに指示しています。
2つのプロセスが同じ名前のないパイプに書き込む場合、バイトをインターリーブできますか?たとえば、プロセスP1が次のように書き込んだ場合:
foo bar
パイプとプロセスP2に同時に書き込みます:
baz baz
同じパイプに対して、パイプの内容は次のように任意のものである可能性があります。
baz foo baz bar
POSIX標準は、書き込みが PIPE_BUF を超えない限り、書き込みがインターリーブされないことを保証します。 バイト。 Linuxシステムでは、 PIPE_BUF サイズは4,096バイトです。パイプに関する私の好みは、単一のライターと単一のリーダーを用意して、問題を回避することです。
名前のないパイプにはバッキングファイルがありません。システムは、ライターからリーダーにバイトを転送するためのメモリ内バッファーを維持します。ライターとリーダーが終了すると、バッファーが再利用されるため、名前のないパイプはなくなります。対照的に、名前付きパイプにはバッキングファイルと個別のAPIがあります。
名前付きパイプの要点を理解するために、別のコマンドラインの例を見てみましょう。手順は次のとおりです。
- 2つの端子を開きます。作業ディレクトリは両方で同じである必要があります。
- いずれかの端末で、次の2つのコマンドを入力します(プロンプトは再び%です 、そして私のコメントは ##で始まります ):
% mkfifo tester ## creates a backing file named tester
% cat tester ## type the pipe's contents to stdout名前付きパイプにはまだ何も書き込まれていないため、最初はターミナルに何も表示されないはずです。
- 2番目のターミナルで、次のコマンドを入力します。
% cat > tester ## redirect keyboard input to the pipe
hello, world! ## then hit Return key
bye, bye ## ditto
<Control-C> ## terminate session with a Control-Cこの端末に入力されたものはすべて、他の端末にエコーされます。一度Ctrl+ C が入力されると、通常のコマンドラインプロンプトが両方の端子に戻ります。パイプは閉じられています。
- 名前付きパイプを実装するファイルを削除してクリーンアップします:
% unlink tester
ユーティリティの名前としてmkfifo つまり、名前付きパイプは、最初のバイトインが最初のバイトアウトであるため、FIFOとも呼ばれます。 mkfifoという名前のライブラリ関数があります これは、プログラムで名前付きパイプを作成し、次の例で使用されます。この例では、2つのプロセスで構成されています。1つは名前付きパイプに書き込み、もう1つはこのパイプから読み取ります。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#define MaxLoops 12000 /* outer loop */
#define ChunkSize 16 /* how many written at a time */
#define IntsPerChunk 4 /* four 4-byte ints per chunk */
#define MaxZs 250 /* max microseconds to sleep */
int main() {
const char* pipeName = "./fifoChannel";
mkfifo(pipeName, 0666); /* read/write for user/group/others */
int fd = open(pipeName, O_CREAT | O_WRONLY); /* open as write-only */
if (fd < 0) return -1; /* can't go on */
int i;
for (i = 0; i < MaxLoops; i++) { /* write MaxWrites times */
int j;
for (j = 0; j < ChunkSize; j++) { /* each time, write ChunkSize bytes */
int k;
int chunk[IntsPerChunk];
for (k = 0; k < IntsPerChunk; k++)
chunk[k] = rand();
write(fd, chunk, sizeof(chunk));
}
usleep((rand() % MaxZs) + 1); /* pause a bit for realism */
}
close(fd); /* close pipe: generates an end-of-stream marker */
unlink(pipeName); /* unlink from the implementing file */
printf("%i ints sent to the pipe.\n", MaxLoops * ChunkSize * IntsPerChunk);
return 0;
}
fifoWriter 上記のプログラムは次のように要約できます。
- プログラムは、書き込み用の名前付きパイプを作成します。
mkfifo(pipeName, 0666); /* read/write perms for user/group/others */
int fd = open(pipeName, O_CREAT | O_WRONLY);ここで、 pipeName mkfifoに渡されるバッキングファイルの名前です 最初の引数として。次に、名前付きパイプは、 openへの今ではおなじみの呼び出しで開かれます ファイル記述子を返す関数。
- ちょっとしたリアリズムのために、 fifoWriter 一度にすべてのデータを書き込むのではなく、チャンクを書き込み、ランダムな数のマイクロ秒をスリープします。合計で、768,000の4バイト整数値が名前付きパイプに書き込まれます。
- 名前付きパイプを閉じた後、 fifoWriter また、ファイルのリンクを解除します。
close(fd); /* close pipe: generates end-of-stream marker */
unlink(pipeName); /* unlink from the implementing file */パイプに接続されているすべてのプロセスがリンク解除操作を実行すると、システムはバッキングファイルを再利用します。この例では、そのようなプロセスは2つだけです。 fifoWriter およびfifoReader 、どちらもリンク解除を実行します 操作。
2つのプログラムは、同じ作業ディレクトリを持つ異なる端末で実行する必要があります。ただし、 fifoWriter fifoReaderの前に開始する必要があります 、前者がパイプを作成するように。 fifoReader 次に、すでに作成されている名前付きパイプにアクセスします。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
unsigned is_prime(unsigned n) { /* not pretty, but efficient */
if (n <= 3) return n > 1;
if (0 == (n % 2) || 0 == (n % 3)) return 0;
unsigned i;
for (i = 5; (i * i) <= n; i += 6)
if (0 == (n % i) || 0 == (n % (i + 2))) return 0;
return 1; /* found a prime! */
}
int main() {
const char* file = "./fifoChannel";
int fd = open(file, O_RDONLY);
if (fd < 0) return -1; /* no point in continuing */
unsigned count = 0, total = 0, primes_count = 0;
while (1) {
int next;
int i;
ssize_t count = read(fd, &next, sizeof(int));
if (0 == count) break; /* end of stream */
else if (count == sizeof(int)) { /* read a 4-byte int value */
total++;
if (is_prime(next)) primes_count++;
}
}
close(fd); /* close pipe from read end */
unlink(file); /* unlink from the underlying file */
printf("Received ints: %u, primes: %u\n", total, primes_count);
return 0;
}
fifoReader 上記のプログラムは次のように要約できます。
- fifoWriter 名前付きパイプfifoReaderを作成します 標準の呼び出しオープンのみが必要です バッキングファイルを介してパイプにアクセスするには:
const char* file = "./fifoChannel";
int fd = open(file, O_RDONLY);ファイルは読み取り専用で開きます。
- 次に、プログラムは潜在的に無限ループに入り、反復ごとに4バイトのチャンクを読み取ろうとします。 読む 呼び出し:
ssize_t count = read(fd, &next, sizeof(int));
ストリームの終了を示すために0を返します。この場合、 fifoReader ループから抜け出し、名前付きパイプを閉じ、終了する前にバッキングファイルのリンクを解除します。
- 4バイト整数を読み取った後、 fifoReader 数が素数であるかどうかをチェックします。これは、本番グレードのリーダーが受信したバイトに対して実行する可能性のあるビジネスロジックを表します。サンプル実行では、受け取った768,000個の整数の中に37,682個の素数がありました。
サンプルを繰り返し実行すると、 fifoReader fifoWriterが使用するすべてのバイトを正常に読み取りました 書きました。これは驚くべきことではありません。 2つのプロセスは同じホスト上で実行され、ネットワークの問題を排除します。名前付きパイプは、信頼性が高く効率的なIPCメカニズムであるため、広く使用されています。
2つのプログラムからの出力は次のとおりです。それぞれが別々の端末から起動されますが、同じ作業ディレクトリがあります。
% ./fifoWriter
768000 ints sent to the pipe.
###
% ./fifoReader
Received ints: 768000, primes: 37682
パイプには厳密なFIFO動作があります。つまり、書き込まれる最初のバイトが読み取られる最初のバイト、書き込まれる2番目のバイトが読み取られる2番目のバイトというようになります。メッセージキューは同じように動作できますが、バイトチャンクをFIFOの順序から取得できるほど十分に柔軟性があります。
名前が示すように、メッセージキューは一連のメッセージであり、それぞれに2つの部分があります。
- バイトの配列であるペイロード( char C)
- 正の整数値として指定されたタイプ。タイプは、柔軟な検索のためにメッセージを分類します
次のメッセージキューの描写を検討してください。各メッセージには整数型のラベルが付いています。
+-+ +-+ +-+ +-+
sender--->|3|--->|2|--->|2|--->|1|--->receiver
+-+ +-+ +-+ +-+
表示されている4つのメッセージのうち、1というラベルの付いたメッセージは前面にあります。つまり、受信者に最も近いメッセージです。次に、ラベル2の2つのメッセージがあり、最後に、後ろに3のラベルが付いたメッセージがあります。厳密なFIFO動作が機能している場合、メッセージは1-2-2-3の順序で受信されます。ただし、メッセージキューでは他の取得順序を許可します。たとえば、メッセージは受信者が3-2-1-2の順序で取得できます。
mqueue 例は、送信者の2つのプログラムで構成されています メッセージキューとレシーバーに書き込みます このキューから読み取ります。どちらのプログラムにも、ヘッダーファイル queue.h が含まれています 以下に示します:
#define ProjectId 123
#define PathName "queue.h" /* any existing, accessible file would do */
#define MsgLen 4
#define MsgCount 6
typedef struct {
long type; /* must be of type long */
char payload[MsgLen + 1]; /* bytes in the message */
} queuedMessage;
ヘッダーファイルは、 queuedMessageという名前の構造タイプを定義します 、ペイロード (バイト配列)およびタイプ (整数)フィールド。このファイルは、シンボリック定数( #define )も定義します。 ステートメント)、最初の2つはキーを生成するために使用され、キーはメッセージキューIDを取得するために使用されます。 ProjectId 任意の正の整数値、および PathName 既存のアクセス可能なファイルである必要があります。この場合、ファイル queue.h 。両方の送信者のセットアップステートメント およびレシーバー プログラムは次のとおりです。
key_t key = ftok(PathName, ProjectId); /* generate key */
int qid = msgget(key, 0666 | IPC_CREAT); /* use key to get queue id */
ID qid 事実上、メッセージキューのファイル記述子に相当します。
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
#include "queue.h"
void report_and_exit(const char* msg) {
perror(msg);
exit(-1); /* EXIT_FAILURE */
}
int main() {
key_t key = ftok(PathName, ProjectId);
if (key < 0) report_and_exit("couldn't get key...");
int qid = msgget(key, 0666 | IPC_CREAT);
if (qid < 0) report_and_exit("couldn't get queue id...");
char* payloads[] = {"msg1", "msg2", "msg3", "msg4", "msg5", "msg6"};
int types[] = {1, 1, 2, 2, 3, 3}; /* each must be > 0 */
int i;
for (i = 0; i < MsgCount; i++) {
/* build the message */
queuedMessage msg;
msg.type = types[i];
strcpy(msg.payload, payloads[i]);
/* send the message */
msgsnd(qid, &msg, sizeof(msg), IPC_NOWAIT); /* don't block */
printf("%s sent as type %i\n", msg.payload, (int) msg.type);
}
return 0;
}
送信者 上記のプログラムは6つのメッセージを送信します。それぞれ2つは指定されたタイプです。最初のメッセージはタイプ1、次の2つはタイプ2、最後の2つはタイプ3です。送信ステートメント:
msgsnd(qid, &msg, sizeof(msg), IPC_NOWAIT);
非ブロッキングになるように構成されています(フラグ IPC_NOWAIT )メッセージがとても小さいからです。唯一の危険は、この例ではありそうもないキューがいっぱいになると、送信が失敗することです。 レシーバー 以下のプログラムもIPC_NOWAITを使用してメッセージを受信します フラグ。
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include "queue.h"
void report_and_exit(const char* msg) {
perror(msg);
exit(-1); /* EXIT_FAILURE */
}
int main() {
key_t key= ftok(PathName, ProjectId); /* key to identify the queue */
if (key < 0) report_and_exit("key not gotten...");
int qid = msgget(key, 0666 | IPC_CREAT); /* access if created already */
if (qid < 0) report_and_exit("no access to queue...");
int types[] = {3, 1, 2, 1, 3, 2}; /* different than in sender */
int i;
for (i = 0; i < MsgCount; i++) {
queuedMessage msg; /* defined in queue.h */
if (msgrcv(qid, &msg, sizeof(msg), types[i], MSG_NOERROR | IPC_NOWAIT) < 0)
puts("msgrcv trouble...");
printf("%s received as type %i\n", msg.payload, (int) msg.type);
}
/** remove the queue **/
if (msgctl(qid, IPC_RMID, NULL) < 0) /* NULL = 'no flags' */
report_and_exit("trouble removing queue...");
return 0;
}
レシーバー プログラムはメッセージキューを作成しませんが、APIは同じように提案します。 レシーバー内 、呼び出し:
int qid = msgget(key, 0666 | IPC_CREAT);
IPC_CREAT が原因で、誤解を招く可能性があります フラグですが、このフラグは実際には必要に応じて作成し、それ以外の場合はアクセスすることを意味します 。 送信者 プログラムはmsgsndを呼び出します メッセージを送信しますが、受信者 msgrcvを呼び出します それらを取得します。この例では、送信者 メッセージは1-1-2-2-3-3の順序で送信されますが、受信者 次に、それらを3-1-21-1-3-2の順序で取得し、メッセージキューが厳密なFIFO動作にバインドされていないことを示します。
% ./sender
msg1 sent as type 1
msg2 sent as type 1
msg3 sent as type 2
msg4 sent as type 2
msg5 sent as type 3
msg6 sent as type 3
% ./receiver
msg5 received as type 3
msg1 received as type 1
msg3 received as type 2
msg2 received as type 1
msg6 received as type 3
msg4 received as type 2
上記の出力は、送信者が およびレシーバー 同じ端末から起動できます。この出力には、送信者の後もメッセージキューが存続することも示されています。 プロセスはキューを作成し、それに書き込み、終了します。キューはレシーバーの後でのみ消えます プロセスは、 msgctlの呼び出しで明示的に削除します :
if (msgctl(qid, IPC_RMID, NULL) < 0) /* remove queue */
パイプとメッセージキューAPIは基本的に単方向です :1つのプロセスが書き込み、別のプロセスが読み取ります。双方向の名前付きパイプの実装がありますが、私の2セントは、このIPCメカニズムが最も単純なときに最適であるということです。前述のように、メッセージキューの人気は低下していますが、正当な理由はありません。これらのキューは、IPCツールボックスのさらに別のツールです。パート3では、ソケットと信号を介したIPCのコード例を使用して、IPCツールボックスのこのクイックツアーを完了します。