目次
4.1 読み取りと書き込みの両方のエンドが存在し、読み取りのみで書き込みはありません
4.2 読み取りと書き込みの両方のエンドが存在し、書き込みのみで読み取りはありません
4.3 同一プロセス内では読み取り終了のみで書き込み終了はありません。
4.4 同じプロセス内には書き込みセグメントのみが存在し、読み取りセグメントはありません。
4.5 1 つのプロセスは読み取り専用で、もう 1 つのプロセスは書き込み専用です。
0. 名前付きパイプ
名前付きパイプ (FIFO) とパイプは基本的に同じですが、いくつかの大きな違いがあります。
その特徴は次のとおりです。
- 半二重では、データは一度に一方向にのみ流れることができます。
- FIFO に書き込まれるデータは、先入れ先出し規則に従います。
- FIFO によって送信されるデータはフォーマットされていないため、FIFO のリーダーとライターは、メッセージとして何バイトカウントするかなど、データのフォーマットについて事前に合意する必要があります。
- FIFO はファイル システム内の特殊ファイルとして存在し、ファイル システム内で参照できるため、名前付きパイプは無関係なプロセス間通信を実装できますが、FIFO の内容はメモリに保存されます。
- パイプはメモリ内のバッファに対応します。異なるシステムは必ずしも同じサイズであるとは限りません。
- FIFO からのデータの読み取りは 1 回限りの操作であり、一度読み取られたデータは FIFO から破棄され、さらにデータを書き込むためのスペースが確保されます。
- FIFO を使用するプロセスが終了すると、FIFO ファイルは後で使用できるようにファイル システムに引き続き保存されます。
- FIFO には名前があり、無関係なプロセスは名前付きパイプを開くことで通信できます。
1.名パイプの作成
方法 1: シェル コマンド mkfifo を使用して名前付きパイプを作成する
mkfifo ファイル名
方法 2: 関数 mkfifo を使用する
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char *パス名, mode_t モード);
機能: 名前付きパイプを作成し、ローカル ファイル システムに表示されるファイル パス名を生成します。
パラメータ:
パス名: 有名なパイプ作成によって生成されたファイル。コード パスにすることができます。
モード: パイプライン ファイルの権限。通常は 0664 などの 8 進数で設定されます。
戻り値:
成功: 0
失敗: -1
コード例:
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
if (mkfifo("fifo_file", 0664) == -1)
{
// printf("errno:%d\n",errno);
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
return 0;
}
2. 有名なパイプでの基本的な読み取りおよび書き込み操作
有名なパイプはローカルにパイプ ファイルを作成するため、システムによって呼び出される IO 関数は基本的に有名なパイプ上で動作できますが、lseek を使用してパイプ ファイルのオフセットを変更することはできません。
注: 有名なパイプによって作成されたローカル ファイルは、表現的な目的のみを提供します。実際の有名なパイプは、プロセス間通信を実装するか、カーネル空間のメモリを開くため、ローカルに生成されたファイルは単なる識別子であり、他の効果はありません。ローカル パイプ ファイルの操作の本質は、カーネル空間での操作です。
コード例:
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define FIFONAME "fifo_file"
int main()
{
if (mkfifo(FIFONAME, 0664) == -1)
{
// printf("errno:%d\n",errno);
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
int fd;
//打开
fd = open(FIFONAME, O_RDWR);
if (fd == -1)
{
perror("fail to open");
exit(1);
}
//写入数据
if (write(fd, "hello world", strlen("hello world")) == -1)
{
perror("fail to write");
exit(1);
}
//再次写数据
write(fd, "nihao bejing", strlen("hello world"));
char buf[32] = "";
//读数据
if (read(fd, buf, sizeof(buf)) == -1)
{
perror("fail to read");
exit(1);
}
printf("buf = [%s]\n", buf);
/* 此时管道中无数据,再读则阻塞
if (read(fd, buf, sizeof(buf)) == -1)
{
perror("fail to read");
exit(1);
}
*/
//关闭
close(fd);
return 0;
}
3. 名前付きパイプはプロセス間通信を実装します
名前付きパイプはパイプ ファイルをローカルに作成するため、関連しないすべてのプロセス間で通信を行うこともできます。
次のプログラム 1 と 2 は、名前付きパイプを介して通信する 2 つの異なるプロセスです。
手順1:
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define FIFO_01 "fifo_01"
#define FIFO_02 "fifo_02"
int main()
{
if (mkfifo(FIFO_01, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
if (mkfifo(FIFO_02, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
int fd_w, fd_r;
if ((fd_w = open(FIFO_01, O_WRONLY)) == -1)
{
perror("fail to oepn");
exit(1);
}
if ((fd_r = open(FIFO_02, O_RDONLY)) == -1)
{
perror("fail to open");
exit(1);
}
// printf("%d %d\n",fd_w,fd_r);
char buf[128] = "";
ssize_t bytes;
pid_t pid;
pid = fork();
if (pid < 0)
{
perror("fail to fokr");
exit(1);
}
else if (pid > 0)
{
while (1)
{
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
if ((bytes = write(fd_w, buf, sizeof(buf))) == -1)
{
perror("fail to write");
exit(1);
}
}
}
else
{
while (1)
{
if ((bytes = read(fd_r, buf, sizeof(buf))) == -1)
{
perror("fail to read");
exit(1);
}
if (bytes)
{
buf[bytes] = '\0';
printf("from fifo_02: %s\n", buf);
memset(buf, 0, sizeof(buf));
}
}
}
return 0;
}
手順2:
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define FIFO_01 "fifo_01"
#define FIFO_02 "fifo_02"
int main()
{
if (mkfifo(FIFO_01, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
if (mkfifo(FIFO_02, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
int fd_w, fd_r;
if ((fd_r = open(FIFO_01, O_RDONLY)) == -1)
{
perror("fail to open");
exit(1);
}
if ((fd_w = open(FIFO_02, O_WRONLY)) == -1)
{
perror("fail to oepn");
exit(1);
}
// printf("%d %d\n",fd_w,fd_r);
char buf[128] = "";
ssize_t bytes;
pid_t pid;
pid = fork();
if (pid < 0)
{
perror("fail to fork");
exit(1);
}
else if (pid > 0)
{
while (1)
{
if ((bytes = read(fd_r, buf, sizeof(buf))) == -1)
{
perror("fail to read");
exit(1);
}
if (bytes)
{
buf[bytes] = '\0';
printf("from fifo_01: %s\n", buf);
memset(buf, 0, sizeof(buf));
}
}
}
else
{
while (1)
{
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
if ((bytes = write(fd_w, buf, sizeof(buf))) == -1)
{
perror("fail to write");
exit(1);
}
}
}
return 0;
}
実行中のスクリーンショット:
4. 有名パイプの読み書きルール(ブロッキング)
4.1 読み取りと書き込みの両方のエンドが存在し、読み取りのみで書き込みはありません
//読み取りと書き込みの両方のエンドが存在し、読み取りのみで書き込みは行われません
//元のパイプにデータがある場合は通常どおり読み取ります
//パイプにデータがない場合、読み取り関数はブロックして待機します
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define FIFONAME1 "myfifo"
int main()
{
if (mkfifo(FIFONAME1, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
//读写端都存在,只读不写
//如果原本管道中有数据,则正常读取
//如果管道中没有数据,则read函数会阻塞等待
int fd;
if ((fd = open(FIFONAME1, O_RDWR)) == -1)
{
perror("fail to open");
exit(1);
}
write(fd, "hello world", 11);
char buf[128] = "";
read(fd, buf, sizeof(buf));
printf("buf = %s\n", buf);
read(fd, buf, sizeof(buf));
printf("buf = %s\n", buf);
return 0;
}
結果:
4.2 読み取りと書き込みの両方のエンドが存在し、書き込みのみで読み取りはありません
//読み取りと書き込みの両方のエンドが存在し、書き込みのみで読み取りはできません
//有名なパイプのバッファーがいっぱいになると、書き込み関数はブロッキングを送信します
//有名なパイプのデフォルトのバッファーは 64K バイトです
コード例:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>
#include<stdlib.h>
#define FIFONAME "myfifo"
int main()
{
if (mkfifo(FIFONAME, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
//读写端都存在,只写不读
//当有名管道的缓冲区写满后,write函数会发送阻塞
//默认有名管道的缓冲区为64K字节
int fd;
if ((fd = open(FIFONAME, O_RDWR) == -1)
{
perror("fail to open");
exit(1);
}
int num = 0;
while (1)
{
write(fd, "", 1024);
num++;
printf("num = %d\n", num);
}
return 0;
}
結果:
4.3 同一プロセス内では読み取り終了のみで書き込み終了はありません。
//プロセス内には読み出し側のみで書き込み側は存在しない
//open関数の位置でブロックされる。
コード例:
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define FIFONAME1 "myfifo"
int main()
{
if (mkfifo(FIFONAME1, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
//在一个进程中,只有读端,没有写端
//会在open函数的位置阻塞
printf("********************************\n");
int fd;
if ((fd = open(FIFONAME1, O_RDONLY)) == -1)
{
perror("fail to open");
exit(1);
}
printf("___________________________\n");
char buf[128] = "";
ssize_t bytes;
if ((bytes = read(fd, buf, sizeof(buf))) == -1)
{
perror("fail to read");
exit(1);
}
printf("buf = %s\n", buf);
printf("bytes = %ld\n", bytes);
return 0;
}
結果:
4.4 同じプロセス内には書き込みセグメントのみが存在し、読み取りセグメントはありません。
//プロセス内には書き込み側のみで読み取り側はありません。
//open関数の位置でブロックされます。
コード例:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>
#include<stdlib.h>
#define FIFONAME "myfifo"
int main()
{
if (mkfifo(FIFONAME, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
//在一个进程中只有写端,没有读端
//会在open函数的位置阻塞
int fd;
if ((fd = open(FIFONAME, O_WRONLY)) == -1)
{
perror("fail to open");
exit(1);
}
write(fd, "hello world", 11);
printf("****************************\n");
return 0;
}
結果:
4.5 1 つのプロセスは読み取り専用で、もう 1 つのプロセスは書き込み専用です。
上記の 4.3 と 4.4 のコードを一緒に実行して、有名なパイプの読み取り端と書き込み端の両方が存在することを確認します。
法:
名前付きパイプの読み取り端と書き込み端が存在することが保証されている限り、プロセスの数に関係なく、オープン状態でブロックされることはありません。
一方のプロセスが読み取り専用で、もう一方のプロセスが書き込み専用の場合、両方のプロセスが実行された後、関係が書き込み側であれば、読み取り側の読み取りは 0 を返します。
プロセスが読み取り専用で、プロセスが書き込み専用の場合、両方の実行後に読み取り側が閉じられると、書き込み側は直ちに SIGPIPE シグナルを生成し、デフォルトの処理方法はプロセスを終了します。
5. 有名パイプの読み書きルール(ノンブロッキング)
O_NONBLOCK を指定します (つまり、オープン ビットまたは O_NONBLOCK)
- 最初は読み取り専用モードでオープンします。書き込み用に FIFO をオープンするプロセスがない場合、読み取り専用オープンは成功し、オープンはブロックされません。
- 最初は書き込み専用モードでオープンします。読み取り用に FIFO をオープンするプロセスがない場合、書き込み専用オープンはエラーとともに -1 を返します。
- 名前付きパイプ内のデータを読み取るときに読み取りと書き込みがブロックされない
- 通信プロセス中、読み取りプロセスが終了した後、書き込みプロセスが名前付きパイプ内のデータを読み取りたい場合、書き込みプロセスも終了します (SIGPIPE シグナルを受信します)。
コード例:
#include<string.h>
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define FIFONAME "myfifo"
int main()
{
int fd;
if (mkfifo(FIFONAME, 0664) == -1)
{
if (errno != EEXIST)
{
perror("fail to mkfifoJ");
exit(1);
}
}
#if 0
//如果open标志位设置为非阻塞,并且以只读的方式打开管道文件
//open函数和read函数都不会阻塞
fd = open(FIFONAME, O_RDONLY | O_NONBLOCK);
if (fd < 0)
{
perror("fail to open");
exit(1);
}
while (1)
{
char recv[100] = "";
memset(recv, 0, sizeof(recv));
read(fd, recv, sizeof(recv));
printf("read from myfifo buf = [%s]\n", recv);
sleep(1);
}
#endif
#if 0
//如果open标志位设置为非阻塞,并且以只写方式打开管道文件
//open函数会直接报错
char send[100] = "Hello I love you";
fd = open(FIFONAME, O_WRONLY | O_NONBLOCK);
if (fd < 0)
{
perror("fail to open");
exit(1);
}
write(fd, send, strlen(send));
printf("write to myfifo buf = %s\n", send);
char recv[100];
read(fd, recv, sizeof(recv));
printf("read from myfifo buf = [%s]\n", recv);
#endif
#if 1
//如果open标志位设置为非阻塞,并且以读写方式打开管道文件
//这样和阻塞是一样的效果。
char send[100] = "Hello I love you";
fd = open(FIFONAME, O_RDWR | O_NONBLOCK);
if (fd < 0)
{
perror("fail to open");
exit(1);
}
write(fd, send, strlen(send));
printf("write to myfifo buf = %s\n", send);
char recv[100];
read(fd, recv, sizeof(recv));
printf("read from myfifo buf = [%s]\n", recv);
#endif
return 0;
}
実行のスクリーンショット:
要約:
名前付きパイプ (FIFO) は、異なるプロセス間でデータを転送する簡単かつ効果的な方法を提供します。FIFO を正しく使用し、アクセス許可の設定やバッファ管理などの一般的な問題を回避する方法を学びます。複雑ではありますが、これらのポイントをマスターすれば、FIFO をうまく活用してプロセス間の通信を容易にすることができます。