进程用户空间是相互独立的,一般而言是不能相互访问的。但很多情况下进程间需要互相通信,来完成系统的某项功能。进程通过内核与其它进程之间的互相通信来协调它们的行为。
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。
管道分为有名管道和无名管道;
- 无名管道:是一种半双工的通信方式,数据只能单向流动(即一方只能写,另一方只能读),而且只能在具有亲缘关系的进程间使用,进程的亲缘关系一般指的是父子关系。
- 有名管道:也是一种半双工的通信方式,但是它允许无亲缘关系进程间的通信。
无名管道也成为匿名管道,通过调用pipe()函数创建:
#include
//返回:成功返回0,出错返回-1
int pipe (int fd[2]);
fd参数返回两个文件描述符,fd[0]指向管道的读端,fd[1]指向管道的写端。fd[1]的输出是fd[0]的输入。下图描述了无名管道的通信实现过程:
代码实现:
#include
#include
#include
#include
int main()
{
int fd[2];
int ret = pipe(fd); //创建管道
if(ret == -1) {
perror("pipe error\n");
return -1;
}
//创建子进程,fork调用一次,返回两次,子进程返回0,父进程返回子进程ID
pid_t id = fork();
if(id == 0) {
//子进程执行动作
close(fd[0]); //关闭读
char* child="I am child!";
for(int i = 0; i < 5; ++i) {
write(fd[1], child, strlen(child) + 1);
sleep(2);
}
}
else if(id > 0) {
//父进程执行动作
close(fd[1]); //关闭写
char msg[100];
for(int j = 0; j < 5; ++j) {
memset(msg,'\0',sizeof(msg));
ssize_t s = read(fd[0], msg, sizeof(msg));
if(s > 0) {
msg[s-1] = '\0';
}
printf("%s\n",msg);
}
}
else {
perror("fork error\n");
return -1;
}
return 0;
}
管道读取数据的四种情况分析:
读取数据情况 | 不同情况分析 |
---|---|
读端(fd[0])不读且未关闭,写端(fd[1])一直写 | 管道数据满时,再次调用write()往管道写数据时,将会被堵塞直到数据被读出才返回 |
写端(fd[1])不写且未关闭,读端(fd[0])一直读 | 读取完管道剩余数据后,再次调用read()读取管道数据时,将会被堵塞直到有数据可读才返回 |
读端(fd[0])一直读且未关闭,写端(fd[1])写部分数据后不写了且关闭 | 读取完管道剩余数据后,再次调用read()读取管道数据时,将会返回0(像读到文件末尾一样),此时不会堵塞。 |
写端(fd[1])一直写且未关闭,读端(fd[0])读了部分数据不读了且关闭 | 读端(fd[0])关闭后,再次调用write()往管道写数据时,此时会收到SIGPIPE信号,通常会导致进程异常终止 |
(如果一个管道读端一直在读数据,而管道写端的引⽤计数⼤于0决定管道是否会堵塞,引用计数大于0,只读不写会导致管道堵塞。如果一个管道写端一直在写数据,而管道读端的引用计数大于0决定是否会堵塞,引用计数大于0,只写不读会导致管道堵塞) |
无名管道特点:
有名管道,即FIFO,遵循先进先出原则,通过调用mkfifo()函数创建:
#include
#include
/*
*参数:
- filename 有名管道文件名(包括路径);
- mode 权限(读写0666)
*成功返回 0 ,失败返回-1 并设置 errno 号
*/
int mkfifo(const char *filename, mode_t mode)
对有名管道的操作是通过文件IO 中的open read write 等操作的,
进程open打开有名管道以只读方式打开,返回的文件描述符就代表管道的读端,反之为写端。
- O_RDONLY:以只读打开,读端
- O_WRONLY:以只写打开,写端
- O_RDWR:读写方式打开
另外由于普通文件在读写时不会出现阻塞问题,而在管道的读写中却有阻塞的可能, 这里的非阻塞标志可以设定为O_NONBLOCK。
读进程 | 写进程 |
---|---|
阻塞打开管道且管道内没有数据,则一直阻塞到有数据 | 阻塞打开管道,则写操作将一直阻塞到数据可以被写入 |
非阻塞打开管道,则不论管道内是否有数据,进程都会立即执行,没有数据则返回0 | 非阻塞打开管道而不能写入全部数据,则读操作进行部分写入或者调用失败 |
写进程代码实现: |
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, const char *argv[])
{
char buf[50] = {
0};
//创建有名管道
if(mkfifo("./fifo",0777) != 0 ) {
if(errno == EEXIST) {
//已存在,或可在mkfifo前用access函数判断文件是否存在
printf("File exists\n");
}
else {
perror("mkfifo fail ");
exit(1);
}
}
int fd_fifo, fd_file;
fd_fifo = open("./fifo",O_WRONLY);//只写方式打开管道,默认阻塞
if(fd_fifo < 0) {
perror("open fifo fail: ");
exit(1);
}
fd_file = open(argv[1],O_RDONLY);//只读方式打开源文件,进行复制到管道中
if(fd_file < 0) {
perror("open source fail ");
exit(1);
}
//循环读取文件内容
ssize_t size;
while(1) {
memset(buf, 0, sizeof(buf));
size = read(fd_file,buf,50); //文件中读取数据,返回读取到多少数据
if(size <= 0) {
break;
}
write(fd_fifo,buf,size);
}
close(fd_file);//关闭读的源文件
close(fd_fifo);
return 0;
}
读进程代码实现:
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, const char *argv[])
{
char buf[50] = {
0};
//创建有名管道,或可在mkfifo前用access函数判断文件是否存在
if(mkfifo("./fifo",0777) != 0 ) {
if(errno == EEXIST) {
//有名管道存在的情况
printf("File exists\n");
}
else {
perror("mkfifo fail ");
exit(1);
}
}
int fd_fifo,fd_file;
fd_fifo = open("./fifo",O_RDONLY);//读方式打开,默认阻塞
if(fd_fifo < 0) {
perror("open fifo fail: ");
exit(1);
}
//把从有名管道中读取的数据,写到文件中,只读,没有创建,清空打开
fd_file = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,0666);
if(fd_file < 0) {
perror("fd_w open fail ");
exit(1);
}
//fifo 中循环读取数据,然后写到文件中
ssize_t size;
while(1) {
memset(buf, 0, sizeof(buf));
size = read(fd_fifo,buf,50); //读有名管道内容,返回读取多少个数据
if(size <= 0) {
break;
}
write(fd_file