linux进程通信———无名管道
引言:打算认认真真实践一下进程间通信的几种方式,本篇笔记介绍了管道局限性、创建方式、代码实例、内核实现。建议感兴趣的同学,以书籍为主,建立系统知识,书本远远比博客深邃。
一、无名管道简介
1.1、局限性:
管道是最初的unix IPC(interprocess communication)形式,其本身存在两个局限性:首先管道没有名字,他们只能在具有共同祖先的进程间使用,一般用于父子进程;其次历史上管道是半双工的,即数据只能在一个方向流动。
1.2、创建:
所有式样的Unix都提供管道,它由pipe()函数创建,提供一个单向的数据流。pipe2()与pipe()区别在于增加了flags标志位,可以通过标志位O_NONBLOCK、O_CLOEXEC设置所创建管道的状态。
该函数返回两个文件描述符:fd[0]和fd[1],前者打开来读,后者打开来写。图2展示了单个进程中管道的样子:
尽管管道是单个进程创建的,但是其很少在单个进程内使用,管道的典型用途是为父子进程提供进程间通信的手段。首先,其由一个进程(它将成为父进程)创建一个管道后调用fork派生一个自身的副本,如图3所示:
接着父进程关闭这个管道的读出端,子进程关闭同一管道的写入端,这就在父子进程间提供了一个单向的数据流,如图4所示:
到目前为止所示的所有管道都是半双工即单向的,只提供一个方向的数据流,可以创建两个管道,父子进程各关闭相应的读写,其实际步骤如下:
1)、创建管道1(fd1[0]和fd1[1])和管道2(fd2[0]和fd2[1]);
2)、fork创建子进程
3)、父进程关闭管道1的读出端(fd1[0]),父进程关闭管道2的写入端(fd2[1]);
4)、子进程关闭管道1的写入端(fd1[1]),子进程关闭管道2的读出端(fd2[0]);
二、代码实例
2.1、父子进程单向传送数据:
/*******************************************************
*内容:IPC管道代码测试
*时间:2018年3月30日
*逻辑:父进程读取argv接收的字符串,将其通过管道传递给子进程,打印
*问题:在计算输入字符串长度时,开始用了sizeof(argv[1]),字符串传递不完整
想起sizeof()运算符仅返回类型长度,argv[1]是指针,改用strlen
*********************************************************/
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#define MAXLINE 512
int main(int argc,char *argv[])
{
pid_t pid;
int length;
int flag = -1;
int pipfd[2];
char readbuf[MAXLINE];
//第一步、用户输入合法性检测
if (argc != 2)
{
fprintf(stderr, "usage : %s\n", argv[0]);
exit(1);
}
//第二步、创建管道
flag = pipe(pipfd);
if(flag < 0)
{
perror("pipe error");
}
//第三步、fork创建子进程,并构造单向流通管道
pid = fork();
if(pid < 0)
{
perror("fork error");
}
else if(0 == pid)
{
//执行子进程的动作,关闭写入端、读取数据、写至标准输出
close(pipfd[1]);
memset(readbuf,0,sizeof(readbuf));
length = read(pipfd[0], readbuf, MAXLINE);
//printf("length = %d.\n", length);
//write(STDOUT_FILENO, readbuf, length);
printf("%s\n", readbuf);
}
else
{
//执行父进程的动作,关闭读出端、读argv字符串、写数据
close(pipfd[0]);
write(pipfd[1], argv[1], strlen(argv[1]));
}
return 0;
}
执行结果:
2.2、父子进程双向传送数据:
/*******************************************************
*内容:利用管道,父子进程间双向传递数据
*时间:2018年3月30日
*逻辑:创建两个管道,分别关闭对应读写端口
* 父进程与标准输入输出绑定,子进程仅仅回射
*问题:read()为阻塞函数,添加sleep()便于查看
*********************************************************/
int main(int argc,char *argv[])
{
pid_t pid;
int length;
int pip_in[2], pip_out[2];
char child_recvbuf[MAXLINE];
char father_recvbuf[MAXLINE];
//第一步、用户输入合法性检测
if (argc != 2)
{
fprintf(stderr, "usage : %s\n", argv[0]);
exit(1);
}
//第二步、创建管道
if(pipe(pip_in) < 0)
{
perror("pipe_in error");
}
if(pipe(pip_out) < 0)
{
perror("pipe_out error");
}
//第三步、fork创建子进程,并构造双向流通管道
pid = fork();
if(pid < 0)
{
perror("fork error");
}
else if(0 == pid)
{
//sleep(2);
//执行子进程的动作,关闭流入管道写入端pip_in[1]、由pip_in[0]读数据
close(pip_in[1]);
memset(child_recvbuf,0,sizeof(child_recvbuf));
length = read(pip_in[0], child_recvbuf, MAXLINE);
printf("child receive message: %s\n", child_recvbuf);
//关闭流出管道读入端pip_out[0],回射数据
close(pip_out[0]);
write(pip_out[1], child_recvbuf, strlen(child_recvbuf));
printf("child send message: %s\n", child_recvbuf);
}
else
{
//执行父进程的动作,操作读写端口
close(pip_in[0]);
write(pip_in[1], argv[1], strlen(argv[1]));
printf("father send message: %s\n", argv[1]);
//sleep(3);
//关闭流出管道写入端pip_out[1]
close(pip_out[1]);
memset(father_recvbuf,0,sizeof(father_recvbuf));
length = read(pip_out[0], father_recvbuf, MAXLINE);
printf("father receive message: %s\n", father_recvbuf);
}
return 0;
}
执行结果:
三、pipe管道的内核实现
linux下的进程的用户态地址空间都是相互独立的,因此两个进程在用户态是没法直接通信的,因为找不到彼此的存在;而内核是进程间共享的,因此进程间想通信只能通过内核作为中间人,来传达信息。
在一个进程中,向管道中写入数据时,其实就是写入这个缓存中;然后在另一个进程读取管道时,其实就是从这个缓存读取,实现进程的通信.关于pipe管道的实现,可以参考这篇文章,写的很详细:从内核代码聊聊pipe的实现。
参考资料:
1、unix 网络编程卷II
2、Linux进程间通信(一)(经典IPC:管道、FIFO)
3、从内核代码聊聊pipe的实现
纠错与建议
邮箱:[email protected]