在了解本节之前我们需要了解什么是进程间的通信以及常见的进程间通信的方式。
目录
1.定义:进程间的通信——进程之间的交流
2.进程间为什么要进行沟通交流?
因为进程之间的独立性,在实际工作中往往会出现在一个系统中有好几个进程协同工作,那么这些进程就需要交流沟通,完成协作,同样因为进程间的独立向使得进程间的交流沟通变得困难,复杂,于是就产生了进程间的各种通信方式,来解决如何进行进程间通信的问题。【总而言之:进程间通信的本质原因---->进程间的独立性】,让操作系统提供进程间的通信方式本质上就是为进程提供一个功能的通信媒介,因为提供媒介的方式不同,因此进程间的通信方式就有多种。
3.常见的进程间的通信方式
- system V 标准的进程间的通信方式:
管道
消息队列
共享内存
信号量
- POSIX IPC
消息队列
共享内存
信号量
互斥量
条件变量
读写锁
此文以及后续的几篇以system V IPC展开讨论
4.进程间通信的目的
(1)数据传输:一个进程需要将自己的数据发送给另一个进程
(2)资源共享:多个进程之间共享同样的资源
(3)通知事件:一个进程需要向另一个或另一组进程发送消息,通知它(它们)发生了某种事件(如进程终止要通知父进程)
(4)进程控制:有些进程希望完全控制另外一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
5.管道
5.1管道的定义
管道的本质是内核中的一块缓冲区,我们把从一个进程连接到另一个进程的数据流称为一个“管道”
5.2管道的分类
5.2.1匿名管道
创建的缓冲区是没有名字的,是不可见于文件系统的,仅用于具有亲缘关系的进程间的通信
(1) 匿名管道的创建:pipe(int fd[2])
(2) 匿名的管道创建成功后会返回两个文件描述符供我们对管道进行操作,这个操作也就是我们的I/O操作
fd[0]: 用于从管道读数据 fd[1]: 用于向管道写入数据
//这是一个匿名管道的实现,功能:父进程写如数据,子进程读取数据 //匿名管道仅能用于具有亲缘关系的进程间的通信 //int pipe(int pipefd[2]); //pipefd :用于接收匿名管道创建成功后返回的两个描述符 //pipefd[0]:用于从管道读取数据 //pipefd[1]:用于向管道写入数据 //pipe 函数调用成功,就会返回一对描述符 //本质上是创建了一个匿名管道,匿名管道其实是内核中的一段 //内存,再加上Linux下一切皆文件的思想,就通过这一对描述符来操作管道 //对应的内存 // //成功返回0,失败返回-1 // #include <stdio.h> #include <unistd.h> #include <string.h> #include <errno.h> int main() { int fd[2]; //管道需要在创建子进程之前创建好,这样才能复制 if(pipe<0){ perror("pipe error"); return -1; } int pid =-1; pid = fork (); if (pid < 0){ return -1; }else if(pid ==0){ //child read close(fd[1]); char buff[1024]={0}; sleep(3); read(fd[0],"buff",1024); printf("child: %s",buff); close(fd[0]); }else{ //parent write //父进程写入数据,因此需要将管道的读取端关闭 close(fd[0]); write(fd[1],"hello",5); write(fd[1],"world",5); close(fd[1]); } return 0; }
(3) 匿名管道的原理:
创建一个子进程,子进程复制了父进程的描述符表,因此也有两个描述符,并且它们指向的是同一个管道,这时候父子进程都能对这个管道进行访问,因此它们之间就可以进行通信了,而管道就是父子进程之间通信的媒介。
(4) 匿名管道的特性:
a.只能用于具有亲缘关系间的进程间的通信
b.管道是半双工单向通信
c.管道的生命周期随进程(打开管道的所有进程退出,管道释放)
d.管道是面向字节流传输数据的(面向字节流:收发数据灵活,数据无规则发送,数据无边界)
e.自带同步与互斥
同步:对临界资源访问的时序可控性【临界资源】
互斥:对临界资源同一时间的时序可控性【临界区】
(5)匿名管道的读写规则:
a.管道无数据读取:
如果描述符是默认的阻塞属性,读取将会挂等待,直到管道有数据
如果描述符被设置为非阻塞属性,读取操作将不具备条件,直接报错返回
b.管道数据满了写入:
如果描述符是默认的阻塞属性,写入操作将会挂起等待,直到数据被读取
如果描述符被设置为非阻塞属性,写入操作经不具备条件,直接报错返回
c.如果写入端全部关闭,这时候如果读取数据,读取完管道中的数据,然后返回0;如果读取端全部关闭,这时候如果写如数据,则会触发异常,操作系统会给进程发送SIGPIPE信号,进程收到这个信号会退出
5.2.2命名管道
命名管道:可见于文件系统,是一个特殊类型(管道)的文件
命名管道可以应用于同一种主机上的任意间的通信
命名管道的创建:
1)命令创建:mkfifo pipe_filename
2)代码创建:int mkfifo(const char* pathname, mode_t mode);
//这是一个命名管道的操作代码,从命名管道中读取数据 //1.创建一个命名管道: // int mkfifo(const char* pathname , mode_t mode); // pathname :管道文件的路径名 // mode : 管道文件的权限 // 成功 :0 失败:-1 // 2.打开管道open // 3.从管道读取数据 read // 4.不玩了,则关闭管道文件 close // #include<stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> int main() { umask(0); //1.创建命名管道 //为了防止因为管道文件已经存在,每次创建失败 if(mkfifo("./test.fifo",0664)<0){ if(errno == EEXIST){ }else{ perror("mkfifo error"); } return -1; } //2.打开管道文件 //·打开特性: 如果以只读打开命名管道,那么open函数将阻塞等待 //直到有其他进程以写的方式打开这个命名管道 // // //·打开特性: 如果以只写打开命名管道,那么open函数将阻塞等待 //直到有其他进程以读的方式打开这个命名管道 // // //如果命名管道以读写方式打开,将不会阻塞 int fd = open ("./test.fifo‘, O_RDONLY "); if(fd < 0){ perror("open fifo error"); return -1; } printf("open fifo file sucess!! read start!!"); while(1){ char buff[1024] = {0}; int ret=read(fd,buff,1023); if(ret >0){ printf("client say:[%s]\n",buff); }else if(ret == 0){ //管道特性:如果所有的写段都关闭,那么读取时返回0 printf("all write point close!!\n"); sleep(1); } } close(fd); return 0; }
一个命名管道打开之后,则所有特性和匿名管道完全相同
命名管道的读写规则:
a.打开特性:如果以只读打开命名管道,那么open函数将阻塞等待直到有其他进程以写的方式打开这个命名管道;
b.打开特性:如果以只写打开命名管道,那么open函数将阻塞等待直到有其他进程以读的方式打开命名管道,如果命名管道以读写方式打开,将不会阻塞。