一、管道
管道是IPC通信中的一种最基本的通信机制,所有UNIX系统都提供此种通信机制。
管道有两种局限性:
- 是半双工的通信方式,同一时刻仅支持数据单向流动
- 只能在具有亲缘关系的两个进程间使用
二、管道函数
2.1 pipe函数
/*
功能:创建管道
参数:
pipefd[2],用于存储返回的两个文件描述符
返回值:成功返回 0 并由参数fd返回两个文件描述符:fd[0]为读描述符,fd[1]为写描述符
失败返回 -1
*/
#include <unistd.h>
int pipe(int pipefd[2]);
2.2 管道的读写:
父进程到子进程:父进程开启写端fd[1],子进程开启读端fd[0];
子进程到父进程:子进程开启读端fd[1],父进程开启读端fd[0];
注意:
- 管道是一种半双工的通信方式,所以在同一时刻只能有一种数据流向。
- 管道中的数据只能读一次,数据一旦被读端读取就从管道中删除。
- 管道的读写操作默认都是阻塞的。
- 当写入管道的数据大于PIPE_BUF,将不再保证写入的原子性,写满空闲缓存区后,写操作返回。
- 当多个进程(父进程可以有多个子进程)同时进行写操作,并且要求写的字节数超过PIPE_BUF,那么所写的数据就有可能会相互交叉。
2.3 读写状态分析
读端开启:
1.写管道未关闭:管道内有数据 > 不阻塞。读取数据,如果管道内数据被读完了,再继续读,阻塞。
管道内无数据 > 阻塞。直到写端写入数据,阻塞解除,开始读。
2.写管道已关闭:管道内有数据 > 不阻塞。读取数据,如果管道内数据被读完了,read返回0。
管道内无数据 > 不阻塞。read返回0。
写端开启:
1.读管道未关闭:管道未写满 > 写入数据。如果缓冲区已满,写操作返回,阻塞。
管道已写满 > 阻塞。直到读端读取数据后,阻塞解除,缓冲区有空闲区域,写端写入。
2.读管道已关闭:产生信号SIGPIPE,如果捕捉该信号并从其处理函数返回,则write返回-1,error设置为EPIPE。
2.4 编程示例:
通常我们会先调用pipe,接着调用fork,从而创建从父进程到子进程的IPC通道
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#define MSG_STR_C "child transferring information to parent"
#define MSG_STR_P "parent transferring information to child"
int main(int argc,char **argv)
{
pid_t pid;
int fd[2];
int rv=0;
char buf[1024];
memset(fd,0,sizeof(fd));
memset(buf,0,sizeof(buf));
pipe(fd); //创建管道,生成两个文件描述符fd[0],fd[1]
pid=fork(); //创建子进程
if(pid < 0)
{
printf("fork failure:%s\n",strerror(errno));
return 0;
}
if(pid == 0)
{
sleep(2);
printf("child start running\n");
//子进程打开写端,向缓存区内写入数据:MSG_STR_C
rv=write(fd[1],MSG_STR_C,strlen(MSG_STR_C));
if(rv <= 0)
{
printf("child failure:%s\n",strerror(errno));
goto cleanup;
}
sleep(1);
//父进程向缓存区写入数据,子进程则打开读端,读取缓存区内父进程写入的数据
rv=read(fd[0],buf,sizeof(buf));
if(rv <= 0)
{
printf("child failure:%s\n",strerror(errno));
goto cleanup;
}
printf("read %d char:%s\n",rv,buf);
}
if(pid > 0)
{
sleep(2);
printf("parent start running\n");
//子进程向缓存区写入数据,父进程则打开读端,读取缓存区内子进程写入的数据
rv=read(fd[0],buf,sizeof(buf));
if(rv <= 0)
{
printf("parent failure:%s\n",strerror(errno));
goto cleanup;
}
printf("read %d char:%s\n",rv,buf);
sleep(1);
//父进程打开写端,向缓存区内写入数据:MSG_STR_P
rv=write(fd[1],MSG_STR_P,strlen(MSG_STR_P));
if(rv <= 0)
{
printf("parent failure:%s\n",strerror(errno));
goto cleanup;
}
}
cleanup:
close(fd[0]);
close(fd[1]);
return 0;
}
注意:使用sleep函数并不能完全保证父子进程间读写端是否对应,可以使用信号signal或semaphore PV操作等来提升程序的可靠性。