Last Updated: 2023-11-05 08:21:23 Sunday
-- TOC --
系统调用pipe接口,用于创建一个匿名管道(即不在文件系统中存在,没有显示出来的pathname),用于父进程和子进程之间的通信。匿名管道是单向的!
// pipe - create pipe
// man 2 pipe
// man 7 pipe
#include <unistd.h>
int pipe(int pipefd[2]);
pipe() creates a pipe, a
unidirectional data channel
that can be used for interprocess communication. The array pipefd is used to return two file descriptors referring to the ends of the pipe.pipefd[0]
refers to theread end
of the pipe.pipefd[1]
refers to thewrite end
of the pipe. Data written to the write end of the pipe is buffered by the kernel until it is read from the read end of the pipe.
pipe接口的测试代码:
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#define _PEXIT \
do {\
char ebuf[64] = {0};\
sprintf(ebuf, "%s: %d", __FILE__, __LINE__);\
perror(ebuf);\
exit(errno);\
}while(0)
int main(void) {
pid_t c1;
int pipes[2];
char *msg = "hello pipe";
/* create a pipe */
if (pipe(pipes))
_PEXIT;
/* fork */
if ((c1=fork()) == -1)
_PEXIT;
else if (c1 == 0) { // child
char rbuf[32] = {0};
close(pipes[1]); // close write end
if (read(pipes[0],rbuf,32-1) == -1)
_PEXIT;
close(pipes[0]); // close read end
printf("in child read: %s\n", rbuf);
return 0;
}
// parent
close(pipes[0]); // close read end
if (write(pipes[1],msg,strlen(msg)) == -1)
_PEXIT;
close(pipes[1]); // close write end
return 0;
}
通过fork创建子进程,子进程能够继承父进程的所有file descriptor:
The child inherits copies of the parent's set of open file descriptors. Each file descriptor in the child refers to the same open file description (see open(2)) as the corresponding file descriptor in the parent. This means that the two file descriptors share open file status flags, file offset, and signal-driven I/O attributes (see the description of F_SETOWN and F_SETSIG in fcntl(2)).
因此,上面的代码在创建子进程后,子进程就直接有了pipe对应的两个file descriptor。但是,一定要及时关闭不需要的fd,因为只有所有指向同一个pipe的write fd都close后,read才会收到最后的EOF。只有所有指向同一个pipe的read fd都close后,write才会返回错误,或收到SIGPIPE。
这段测试代码只用了fork,但没有使用exec系列接口,即父子进程是同一个进程image,只是执行不同的if branch,这样的是OK的!
匿名管道pipe虽然被广泛使用,但也有其局限性:
单向传输数据
。(这一点匿名管道和命名管道是相同的,可以创建两个单向管道来实现数据的双向传输)pipe,很多技术中都有管道的概念,是否基本逻辑都一样呢...
下面的测试代码,创建2个pipe,实现双向通信:
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#define _PEXIT \
do {\
char ebuf[64] = {0};\
sprintf(ebuf, "%s: %d", __FILE__, __LINE__);\
perror(ebuf);\
exit(errno);\
}while(0)
int main(void) {
pid_t c1;
int pipes[2][2];
/* create 2 pipe */
if (pipe(pipes[0])) // parent write, child read
_PEXIT;
if (pipe(pipes[1])) // child write, parent read
_PEXIT;
/* fork */
if ((c1=fork()) == -1)
_PEXIT;
else if (c1 == 0) { // child
char rbuf[32] = {0};
/* child read */
close(pipes[0][1]); // close write end
if (read(pipes[0][0],rbuf,32-1) == -1)
_PEXIT;
close(pipes[0][0]); // close read end
printf("in child read: %s\n", rbuf);
/* child write */
char *msg = "hi papa....";
close(pipes[1][0]);
if (write(pipes[1][1],msg,strlen(msg)) == -1)
_PEXIT;
close(pipes[1][1]);
return 0;
}
/* parent write */
close(pipes[0][0]); // close read end
char *msg = "hello child...";
if (write(pipes[0][1],msg,strlen(msg)) == -1)
_PEXIT;
close(pipes[0][1]); // close write end
/* parent read */
char rbuf[32] = {0};
close(pipes[1][1]);
if (read(pipes[1][0],rbuf,32-1) == -1)
_PEXIT;
close(pipes[1][0]); // close read end
printf("parent read: %s\n", rbuf);
wait(NULL);
return 0;
}
运行效果:
$ gcc -Wall -Wextra test_pipe.c -o test_pipe
$ ./test_pipe
in child read: hello child...
parent read: hi papa....
Linux下对fd的读写,默认都是blocking模式,pipe也一样。read时如果pipe空,block。write时如果pipe满,block。可以通过fcntl设置为non-blocking模式。
关于pipe更多详细的工作原理和资源限制,man 7 pipe
。
close管道的write end,会触发EOF,read end还能读,直到读到EOF。但是要注意,父子进程的write end都要close才有效果...
pipe机制是Linux系统的一个基础设施(从UNIX学来的),它使得
小而美
的设计哲学成为可能,即一大堆精致的小工具通过管道相互配合实现复杂多变的业务需求。
本文链接:https://cs.pynote.net/sf/linux/prog/202202051/
-- EOF --
-- MORE --