进程是一个独立的资源分配单元,不同进程之间资源是独立的,每个进程各自有不同的用户地址空间,进程的全局变量在另一个进程中看不到,不能在一个进程中直接访问另一个进程的资源,所以进程之间交换数据要通过内核,在内核中开辟一块缓冲区,进程1把数据写到内核的缓冲区,进程2再从内核的缓冲区把数据读走,称为进程间通信(IPC)。
管道(匿名管道PIPE)
管道是一种基本的IPC机制,它是调用pipe函数在内核中开辟一片缓冲区,该片缓冲区就是管道。它有一个读端和一个写端,然后int pipe(fd[2])通过fd参数给用户程序给用户程序两个文件描述符。fd[0]指向管道的读端,fd[1]指向管道的写端。管道就像打开文件一样,write(fd[1])往管道里写,read(fd[0])从管道里读。
特点
1.普通管道只能进行单向通信。因为开辟的管道只有一个读端和一个写端。
2.该管道只能用于具有血缘关系的进程(例如父子进程),因为是匿名管道,进行通信的两个进程具有公共祖先,才会具有共同的文件描述符信息,才会指向同一个管道。
3.该管道的生命周期随进程。随着进程的退出随即消失
4.管道以字节流的形式读写数据进行通信。
管道的管理
1.匿名管道的创建
如果执行成功,pipefd的文件描述符pipefd[0]指向管道的读端,用来执行读操作,pipefd[1]指向管道的写端,用来执行写操作。如果执行失败,则返回-1。
2.从管道里读数据
fd为打开的文件描述符,buf是读出数据的存储位置,count为读取的字节数。
3.往管道里写数据
fd为打开的文件描述符,buf指向向管道写入的缓冲区,count为读取的字节数。
父子间进程通信
1.创建父进程,父进程创建管道,得到两个文件描述符分别指向管道
2.创建子进程,子进程也有两个文件描述符指向管道的两端
3.利用管道进行通信,父进程close(fd[0]),write(fd[1])往管道里写数据。子进程close(fd[1]),read(fd[0])从管道中读取数据。
代码实现
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
int main()
{
pid_t id;
int fd[2];
int ret=pipe(fd);
if(ret<0)
{
printf("pipe error!\n");
}
if(id=fork()<0)
{
printf("fork error!\n");
}
else if(id>0)//father
{
close(fd[0]);
int i=0;
char* mesg=NULL;
while(i<10)
{
mesg="hello";
write(fd[1],mesg,strlen(mesg)+1);
sleep(1);
i++;
}
close(fd[1]);
}
else//child
{
close(fd[1]);
char mesg[256];
int j=0;
while(j<10)
{
ssize_t s=read(fd[0],mesg,sizeof(mesg));
mesg[s-1]='\0';
printf("%s\n",mesg);
j++;
}
close(fd[0]);
}
return 0;
}
使用管道通信的四种特殊情况
1.所有指向通道的写端的文件描述符都关闭了,仍有进程从管道中读取数据,当管道到没有数据时,再次read就会返回0,就像读到文件结束一样。
2.如果指向管道写端的文件描述符没有关闭,但是写进程并没有向管道中写数据,
当剩下的数据被读完后,读进程再次read时会发生阻塞。直到管道有了数据读取后才返回。
3.如果所有指向管道读端的文件描述符都关闭了,这时有进程向管道中write,该进程会受SIGPIPE,通常会导致该进程终止。
4.如果指向管道读端的文件描述符没有关闭,但写进程并没有从进程中读取数据,当写进程把管道写满时,再次write就会发生阻塞,直到管道有了空位置才写数据bing返回。
命名管道(FIFO)
匿名管道没有名字,只能用于具有亲缘关系的进程之间,它是临时的,在完成通信后自动消失。 命名管道 它是存在一个特殊的文件,以FIFO的文件形式存储于文件系统中,只要访问该文件的路径,两个不同的进程之间就可以通过FIFO读写数据进行通信。
注:命名管道和普通文件一样具有文件路径,文件权限,存在于磁盘中,命名管道不能直接存储信息,它存储的信息再两个进程通信结束后就会自动丢失,但命名管道的文件本身还存在。
管道的管理
1.命名管道的创建
pathname为创建的命名管道名,mode为该文件的权限。
2.读写命名管道
用法和匿名管道一样。不一样的的是每次都写之前都需要open打开管道。
注:调用open()打开命名管道的进程可能会发生阻塞。如果同时有读写方式打开,则一定不会导致阻塞。如果以只读方式(O_RDONLY)打开,调用open()的进程直到写方打开管道,同样以写方式(O_WRONLY)打开,也会阻塞到有读方式打开。
代码实现
//write.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#define _PATH_ "temp"
int main()
{
int ret=mkfifo(_PATH_,0666|S_IFIFO);
if(ret==-1)
{
printf("mkfifo error\n");
return 1;
}
int fd=open(_PATH_,O_WRONLY);
if(fd<0)
{
printf("open error\n");
return 2;
}
char buf[100];
memset(buf,0,sizeof(buf));
int i=0;
while(i<10)
{
scanf("%s",buf);
write(fd,buf,sizeof(buf)+1);
i++;
}
close(fd);
return 0;
}
//read.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#define _PATH_ "temp"
int main()
{
int fd=open(_PATH_,O_RDONLY);
if(fd<0)
{
printf("open file error\n");
return 1;
}
char buf[100];
memset(buf,'\0',sizeof(buf));
int i=0;
while(i<10)
{
ssize_t s=read(fd,buf,sizeof(buf));
printf("%s\n",buf);
fflush(stdout);
i++;
}
close(fd);
return 0;
}
最后在这里将匿名管道和命名管道对比下
1.匿名管道只能实现具有亲缘关系的进程的通信。命名管道以磁盘文件形式存在,可以实现任意进程间的通信。
2.命名管道在读写前需要open()打开。匿名管道创建后通过文件描述符直接读写。
3.匿名管道的生命周期随进程,命名管道一旦创建,在进程结束后文件本身任然存在。