进程是一个独立的资源管理单元,不同进程间的资源是独立的,不能在一个进程中访问另一个进程的用户空间和内存空间。但是,进程不是孤立的,不同进程之间需要信息的交互和状态的传递,因此需要进程间数据的传递、同步和异步的机制。
当然,这些机制不能由哪一个进程进行直接管理,只能由操作系统来完成其管理和维护,Linux提供了大量的进程间通信机制,包括同一个主机下的不同进程和网络主机间的进程通信,如下图所示:
同主机间的信息交互:
匿名管道:
特点:多用于亲缘关系进程间通信,方向为单向;为阻塞读写;通信进程双方退出后自动消失
问题:多进程用同一管道通信容易造成交叉读写的问题
命名管道:
FIFO(First In First Out),方向为单向(双向需两个FIFO),以磁盘文件的方式存在;通信双方一方不存在则阻塞
消息队列:
可用于同主机任意多进程的通信,但其可存放的数据有限,应用于少量的数据传递
共享内存:
可实现同主机任意进程间大量数据的通信,但多进程对共享内存的访问存在着竞争
同主机进程间同步机制:信号量(Semaphore)
同主机进程间异步机制:信号(Signal)
网络主机间数据交互:Socket(套接字)
接下来我详细解释进程间通信管道:
1. 什么是管道
把一个进程连接到另一个进程的一个数据流称为一个“管道”,通常是用作把一个进程的输出通过管道连接到另一个进程的输入。管道本质上是内核的一块缓存。
例子
在shell中输入命令:ls -l | grep string,我们知道ls命令(其实也是一个进程)会把当前目录中的文件都列出来,但是它不会直接输出,而是把本来要输出到屏幕上的数据通过管道输出到grep这个进程中,作为grep这个进程的输入,然后这个进程对输入的信息进行筛选,把存在string的信息的字符串(以行为单位)打印在屏幕上。
2. 管道的实现原理
管道分为:匿名管道/命名管道
3. 匿名管道
3.1 概念
匿名管道是基于文件描述符的通信方式。实现两个进程间的通信时必须通过fork创建子进程,实现父子进程之间的通信。
3.2 读写规则
3.3 特性
3.4 原理
以父子进程为例:创建一个子进程,子进程复制了父进程的描述符表,因此子进程也有描述符表,并且他们指向的是同一个管道,由于父子进程都能访问这个管道,就可以通信。因为管道是半双工单向通信,因此在通信前要确定数据流向:即关闭父子进程各自一端不用的读写。如果一方是读数据就关闭写的描述符。
说明:
管道的读写端通过打开文件描述符来传递,因此要通信的两个进程必须从他们的公共祖先那里继承管道的文件描述符。上面的例子是父进程把文件描述符传给子进程之后父子进程之间通信。也可以父进程fork()两次,把文件描述符传给两个子进程,然后两个子进程之间通信。总之需要通过fork()传递文件描述符使两个进程都能访问同一个管道,他们才能通信。
创建匿名管道:pipe
#include
int pipe(int pipefd[2]);
父子进程通信实例
//这是一个匿名管道实现:功能:从父进程写入数据,子进程读取数据
#include
#include
#include
#include
int main()
{
int fd[2];
//管道需要创建在创建子进程前,这样才能复制
if(pipe(fd)<0)
{
perror("pipe errno");
return -1;
}
int pid=-1;
pid=fork();//创建子进程,对于父进程会返回子进程id,子进程会返回0,创建失败会返回0
if(pid<0)
{
perror("fork errno");
return -1;
}
else if(pid==0)
{
//子进程 读取数据-> fd[0]
close(fd[1]);//fd[1]是向管道写入数据,子进程不用写入数据,需要关闭管道写入端
char buff[1024]={0};
read(fd[0],buff,1024);//如果管道没数据会等待,然后读取数据,默认阻塞等待直至有数据
printf("chlid pid=%d, buff:%s\n",(int)getpid(), buff);
close(fd[0]);
}
else
{
//父进程 :写入数据->fd[1]
close(fd[0]); //fd[0]是读取数据,父进程不用读取数据,需要关闭管道读取端,由于父子进程相互独立,关闭一方描述符对另一方无影响
printf("parent pid=%d\n", (int)getpid()) ;
write(fd[1],"happy day",10);
close(fd[1]);
}
return 0;
}
四种特殊情况:
4. 命名管道FIFO
4.1 概念
命名管道:文件系统可见,是一个特殊类型(管道类型)文件,命名管道可以应用于同一主机上任意进程间通信。
在管道中,只有具有血缘关系的进程才能进行通信,对于后来的命名管道,就解决了这个问题,FIFO不同于匿名管道之处在于它提供了一个路径名与之关联,以FIFO的文件形式存储在文件系统中。有名管道是一个设备文件,因此即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能通过FIFO相互通信。值得注意的是,FIFO(first input first output)总是按照先进先出的原则工作,第一个被写入的数据首先从管道中读取。
4.2 特点
4.3 读写规则
4.4 命名管道的创建:
#include
#include
int mkfifo(const char *pathname, mode_t mode);
int mknod(const char* path,mode_t mod,dev_t dev);
需要注意:
服务器、客户端实例
server.c
#include
#include
#include
#include
#include
//
int main()
{
umask(0);//将权限清0
if(mkfifo("./mypipe",0666|S_IFIFO) < 0){//创建管道
perror("mkfifo");
return 1;
}
int fd = open("./mypipe",O_RDONLY);//打开管道
if(fd < 0){
perror("open");
return 2;
}
char buf[1024];
while(1){
buf[0] = 0;
printf("please waiting...\n");
ssize_t s = read(fd,buf,sizeof(buf)-1);
if(s > 0){
buf[s-1] = 0;//过滤\n
printf("Server:%s\n",buf);
}else if(s == 0){//当客户端退出时,read返回0
printf("client quit, Exit\n");
break;
}
}
close(fd);
return 0;
}
client.c
#include
#include
#include
#include
#include
int main()
{
int fd = open("./mypipe",O_WRONLY);//打开管道
if(fd < 0){
perror("open");
return 1;
}
char buf[1024];
while(1){
printf("Client:");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);//向管道文件中写数据
if(s > 0){
buf[s] = 0;//以字符串的形式写
write(fd,buf,strlen(buf));
}
}
close(fd);
return 0;
}
结果:
wezhu@bios-thinkstation-p720:~/linux_training/kernel/process-courses/pipe$ gcc server.c -o server
wezhu@bios-thinkstation-p720:~/linux_training/kernel/process-courses/pipe$ gcc client.c -o client
wezhu@bios-thinkstation-p720:~/linux_training/linux_training/kernel/process-courses/pipe$ ./server
please waiting...
Server:hello
please waiting...
Server:world
please waiting...
client quit, Exit
wezhu@bios-thinkstation-p720:~/linux_training/linux_training/kernel/process-courses/pipe$
wezhu@bios-thinkstation-p720:~/linux_training/linux_training/kernel/process-courses/pipe$ ./client
Client:hello
Client:world
Client:^C
wezhu@bios-thinkstation-p720:~/linux_training/linux_training/kernel/process-courses/pipe$
命名管道和匿名管道区别和联系:
1.区别:匿名管道用int pipe(int pipefd[2]); 创建并打开匿名管道返回描述符
命名管道用mkfifo或者 int mkfifo(const char *pathname, mode_t mode);创建,并没有打开,如果打开需要open;
匿名管道是具有亲缘关系进程间通信的媒介,而命名管道作为同一主机任意进程间通信的媒介;
匿名管道不可见文件系统,命名管道可见于文件系统,是一个特殊类型(管道类型)文件。
2.联系:匿名管道和命名管道都是内核的一块缓冲区,并且都是单向通信;另外当命名管道打开(open)后,所有特性和匿名管道一样(上文匿名管道读写规则与管道特性):两者自带同步(临界资源访问的时序性)与互斥(临界资源同一时间的唯一访问性),管道生命周期随进程退出而结束。
5. 总结