在学习进程的时候,我们了解到了进程的独立性:进程之间是相互独立的,每个进程有自己的虚拟地址空间,并且虚拟地址空间通过页表的映射,映射到属于自己的物理内存上。并且各个进程之间互相不影响,执行自己的代码。
但是正因为进程的独立性,所以导致进程间进行数据通信将变得非常麻烦。操作系统为了使进程间能够通信,会提供一个介质能够让多个进程都能访问。也就是在内存上开辟一块公共资源,让进程在这块公共资源上面交流联系。就像我们人一样,每个人都是一个独立的个体,但是到了一个公共场所,人与人就会不由的交流联系。
进程间通信:
一、目的:
进程之间的通信能够让进程之间进行数据的交换,并且这些进程共用一块公共资源,共同享有这些资源,进程之间进行信息的通知,如fork之后,子进程若退出则需要向父进程发送退出信息,让父进程来维护子进程的退出信息。同时,进程间如果能够通信交流,那么可以达到对进程的控制。
二、分类:
进程间通信分为管道通信、System V进程间通信、POSIX进程间通信。
管道通信又分为匿名管道与命名管道通信。
System V进程间通信有:消息队列、共享内存、信号量,其中共享内存通信是速度最快的进程间通信方式。
POSIX进程间通信方式有:消息队列、共享内存、信号量、互斥量、条件变量、读写锁。
今天我们先来理解进程间通信中的管道通信:
管道通信
一、管道:在理解管道通信之前,我们要先理解管道这个概念:
所谓“管道”,是指用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件,就像现实中的水管,水就像数据。(连接进程,相当于在进程间连接一个通路,用来传递信息)
“Linux下⼀切皆⽂件”,所以我们可以把管道看做是文件,是服务于管道通信的特殊文件,而管道通信是一种通信方式。
二、管道通信的基本概念:
1.原理:操作系统为进程提供一个双方都能访问的缓冲区。
2.功能:传输数据资源。
3.特性:管道通信是一个半双工通信方式(单向通信),它不能在创建时就确定确定数据流向(操作系统无法确定谁读谁写),而是在使用的时候确定,因此操作系统会提供两个描述符供使用,一个读一个写,这样的确定方向就是将对应的一段关闭掉即可,这样方向的控制权就交给了用户。
4.分类:管道通信分为两类:匿名管道、命名管道。
5.管道通信是消息传递的一种特殊方式,管道机制必须提供以下三方面的协调能力:互斥、同步和确定对方的存在。
匿名管道:
一、概念:
仅仅适用于具有亲缘关系(如父子进程)的进程间通信,因为匿名管道无法被其他进程找到,也就无法通信,所以只能通过子进程复制父进程的方法,让子进程能够访问到相同的管道来实现通信。(管道的操作:io操作------文件描述符)
二、创建
接口:pipe(int fd[2]),其中fd[1]用于读,fd[2]用于写。创建匿名管道必须在创建子进程之前,否则子进程将无法复制。
三、读写特性:
-
如果管道没有数据,那么read一直等待,直到有数据;
-
如果管道数据满了,那么write一直等待,直到有数据被读取出去。
-
如果所有管道写入端关闭,read读完所有数据之后返回0;
-
如果所有管道读写端关闭,write将触发异常,操作系统此时会发送SIGPIPE信号,通知我们读取端都被关闭了。 (这个信号会导致write端进程退出)。
四、管道自带同步与互斥特性:
- 同步:保证一个操作访问的时序性。
- 互斥:保证操作的同一时间唯一访问。
五、实现:
例:这里利用fork产生一个子进程, 然后让父进程在之前创建的管道内进行写入内容,写入一个hello world,然后子进程去读这个hello world到buf里面。这里注意,由于管道是单向的,所以父进程在写入内容之前,需将管道的读入,也就是fd[0]关闭,在写完之后再将fd[1]关闭,防止外来因素影响,而子进程同理,在读之前将fd[1]关闭。
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
pid_t pid;
int pipefd[2] = { 0 };
if (pipe(pipefd) < 0)
{
perror("pipe error");
return -1;
}
pid = fork();
if (pid < 0)
{
perror("fork error");
return -1;
}
else if (pid == 0) //子进程 读
{
close(pipefd[1]);//关闭写端
char buff[1024] = { 0 };
read(pipefd[0], buff, 1024);
printf("child:%s\n", buff);
close(pipefd[0]);
}
else //父进程 写
{
close(pipefd[0]);//关闭读端
char *ptr = "hello world";
write(pipefd[1],ptr,strlen(ptr);
close(pipefd[1]);
}
return 0;
}
运行代码:
这个例子中利用fork来共享管道的原理:
命名管道:
未完待续...