要是想让两个进程之间通信,就是想尽一切办法,让两个进程能够看到同一份资源(内存)。
其中内存的提供是通过文件的方式就叫做 管道,如果绕过文件那么就到做 system V
进程间通信介绍
- 数据传输:一个进程需要将它的数据发送给另一个进程
- 资源共享:多个进程之间共享同样的资源。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
1. 管道
管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。
在Linux下可以认定为一切皆文件,那么管道也是一个文件。
1.1 匿名管道
匿名管道特点:
- 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
- 管道提供流式服务
- 一般而言,进程退出,管道释放,所以管道的生命周期随进程
- 一般而言,内核会对管道操作进行同步与互斥(互斥可以对数据进行保护)
- 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
#include <unistd.h>
功能:创建- - -匿名管道
原型:int pipe(int pipefd[2]);
数组传参会发生降维,这里就其实传的是一个指针
参数fd:文件描述符数组,其中pipefd[0]表示读端, pipefd[1]表示写端
返回值:成功返回0,失败返回错误代码
#include<stdio.h>
2 #include<unistd.h>
3 #include<string.h>
4
5 int main()
6 {
7 int pipefd[2] = {0};
8
9 pipe(pipefd);
10
11 pid_t id = fork();
12 if(id < 0){
13 perror("fork error\n");
14 }
15 else if(id == 0){
16 //child
17 close(pipefd[0]);
18 const char *msg = "I am child ......!\n";
19 while(1){
20 write(pipefd[1],msg,strlen(msg));
21 sleep(1);
22 }
23 }
else{
25 //parent
26 close(pipefd[1]);
27 char buffer[64];
28 while(1){
29 ssize_t s = read(pipefd[0],buffer,sizeof(buffer)-1);// 是因为文件里面的内容读出来,想要以字符串的形式显示
30 if(s > 0){
31 //说明读取成功
32 buffer[s] = '\0';
33 printf("father get message : %s\n",buffer);
34 }
35 }
36 }
37 return 0;
38 }
管道的四种情况:
①让子进程的写端每间隔5秒写一次,然后观察。发现父进程的读端也是和子进程保持同步,5秒钟才显示在屏幕一次。(管道是空的,因为读的快)
结论:如果写端不关闭文件描述符,且长时间不写入,那么读端可能需要长时间阻塞
②让子进程的写入端不停的写入,但是父进程的读端每隔1秒才读取一次.(管道是写满的,因为写的快)
结论:如果不关闭文件描述符的情况下,当我们实际在进行写入时,如果写入条件不满足,写入端就要被阻塞。
③如果写端关闭文件描述符,读端在读取完数据后,会读到文件的结尾。
④如果读端的文件描述符关闭,写端进程可能会被OS直接杀掉(所以不推荐这种方式) 此时你写入的数据已经没有人读了,操作系统认为你此时的写入是在浪费资源,所以把你杀死掉了,所以写端子进程就会被变成僵尸进程了。
那么这个写端子进程是如何被OS给杀掉的呢?信号
1.2 命名管道
- 匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
- 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
- 命名管道是一种特殊类型的文件
命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
$ mkfifo filename
命名管道也可以从程序里创建,相关函数有:
int mkfifo(const char *pathname,mode_t mode);
第一个参数你所创建的管道路径+名称,第二个参数是创建管道时的权限问题。如果成功了返回0,失败了返回-1
但是我所设置的权限是666,但是为什么不对呢?是因为umask可能会影响权限,所以在程序的开始可以使umask(0);
,就会是我设置的权限了。这里的修改umask的操作只会影响这一个进程,所以不必担心其余的进行权限问题。
两个毫不相关的进程就可以通过这个fifo文件进行通信了。
client.c
server.c
在完成通信之后,观察这个fifo管道文件,你会发现他的大小还是0,那是因为fifo只是一种符号性或者标志性的文件,其实在内存中所创建的,直接进行信息的交互,不需要刷新到硬盘上。
2. system V 共享内存
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。共享内存底层不提供任何同步与互斥机制。
在物理内存上可能存在着许许多多的共享内存块,那么多了,操作系统就需要将这些共享内存块进行管理起来。所以需要先描述在组织。struct shmid_ds
的结构体用来描述一个共享内存块,且在结构体中有一个变量为struct ipc_perm
在这个结构体中有一个变量是key,这个key值是在操作系统层面上标识IPC资源唯一性的
2.1 ftok
#include<sys/types.h>
#include<sys/ipc.h>
key_t ftok (const char* pathname, int proj_id);
从理论上讲,这个两个参数可以随便填写,如果成功了则会返回一个key值,失败了则会返回-1.简单说:ftok就是想通过算法的方式让这两个参数整合出一个唯一值。
2.2 共享内存函数
总结步骤:
- 创建共享内存
- 关联共享内存
- 取消关联共享内存
- 删除共享内存
①shmget函数
功能:用来创建共享内存
原型:int shmget(key_t key, size_t size, int shmflg);
参数: key:这个共享内存段名字 size(建议设置为page的整数倍):共享内存大小 shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的,只需要着重关注IPC_CREAT和IPC_EXCL,当他们两个合起来一起使用的时候(IPC_CREAT| IPC_EXCL)的意思就是如果你所创建的这个共享内存不存在就创建,如果已经存在了,那就出错返回,所以创建的共享内存一定是全新的。
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1(可以类比于文件描述符)然而这个返回值是给用户使用的。
IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核,ipcs -m
就可以查看我们刚才所创建的共享内存块,ipcrm -m shmid
(这里不用key值,因为此时在用户层)
perms是权限,nattch是有多少进程和这个共享内存块关联(挂接)
②shmat函数
功能:将共享内存段连接到进程地址空间
原型:void *shmat(int shmid, const void *shmaddr, int shmflg);
参数: shmid: 共享内存标识 shmaddr:指定连接的虚拟地址,shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存的起始地址;失败返回-1
③shmdt
功能:将共享内存段与当前进程脱离
原型:int shmdt(const void *shmaddr);
参数:shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
由于上面发现,每一次我都需要手动的使用ipcrm -m shmid
来删除太麻烦了,所以这里有一个函数,可以自己删除这个共享内存。
④shmctl函数
功能:用于控制共享内存
原型: int shmctl(int shmid, int cmd, struct shmid_ds*buf);
参数: shmid:由shmget返回的共享内存标识码 cmd:将要采取的动作(有三个可取值) buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
cmd的三个可取值:
systemm V代表的是一种标准,也就是说共享内存、消息队列、信号量等的基础实现方法是类似的。
system V消息队列
- 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法
- 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值特性方面
system V信号量
信号量主要用于同步(你快我也快,你慢我也慢)和互斥(保证数据的安全,在我访问期间,别人是不能有任何操作的)
- 由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥
- 系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。(这里可以理解为管道和共享内存的资源)
- 在进程中涉及到互斥资源的程序段叫临界区(也可以理解去访问管道和共享内存的代码)
- 这里有一个PV操作,也就是加锁和解锁的操作。