目录
进程间通信介绍
进程通信(Interpeocess communication)简称IPC;所谓进程间通信就是两个相互独立的进程之间完成交互工作,我们在之前学习到的所有的程序都是进程,他们之间相互独立,那么他们之间是怎么完成交互工作的呢,就比如你和你的微信好友是怎么发信息聊天的呢?
进程间通信的目的
- 数据传输:一个进程将自己的数据发送给另外一个进程
- 资源共享:多个进程之间享受同样的资源
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
进程间通信发展与分类
- 管道
- System V进程间通信
- POSIX进程间通
管道
- 匿名管道
- 命名管道
System V进程间通信
- System V 消息队列
- System V 共享内存
- System V 信号量
POSIX IPC
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
管道
管道概念
管道是unix中最原始的进程之间通信的方式
我们把一个进程连接到另一个进程的数据流称为管道
通过这个|我们可以查看code.c这个文件包含了多少行代码。但是cat和wc是两个进程,这就完成两个进程之间的互动,cat进程将查看代码数量命令通过管道交给wc进程
匿名管道:
通过pipe函数创建一个匿名管道:
int pipe(int id[2]);
参数解读:
id:文件描述符数组,id【0】表示读端,id【1】表示写端,创建成功返回0,失败返回错误代码
用户可以指定关闭或者打开管道的读端或者写端,比如让父进程读取,子进程写入~
注意:匿名管道只用于父子进程之间交换资源~
父子进程控制管道原理
父子进程被创建之后进程PCB当中都有了一个指针数组,struct file_struct,这个是用来描述组织文件的。而且父子进程都拥有这个指针数组,而且子进程继承了父进程的数据的。所以子进程就会和父进程拥有同样的数据并且指向相同的空间。在创建好管道要完成父子间通信的时候,父子进程同时指向这个文件,父进程想要对文件进行写入操作,会先将数据写入到文件缓冲区当中,而不是直接写入然后就打印到屏幕上了。结论:对于程序的操作当中,所发送的命令并不是立即执行,而是存入到相对应的缓冲区当中,在合适的时候再完成相应命令~
#include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include<string.h>
5 int main()
6 {
7 int pipefd[2]={0};
8 //pipef为一个输出型参数 我们可以通过这个参数读取到打开的两个fd
9 if(pipe(pipefd)!=0)
10 {
11 perror("pipe error\n");
12 return 1;
13 }
14 printf("pipefd[0]:%d\n",pipefd[0]);
15 printf("pipefd[1]:%d\n",pipefd[1]);
16 //创建父子进程完成他们之间的通信工作
17 if(fork()==0)
18 {
19 close(pipefd[0]);
20 const char*msg="hello world";
W> 21 int count=0;
22 while(1)
23 {
24 write(pipefd[1],msg,strlen(msg));
25 //write(pipefd[1],"a",1);
26 //printf("child:%d\n",count);
27 //count++;
28 sleep(1);
29 }
30 exit(0);
31 }
32 close(pipefd[1]);
33 while(1)
34 {
35 sleep(1);
}
30 exit(0);
31 }
32 close(pipefd[1]);
33 while(1)
34 {
35 sleep(1);
36 char c[1024*2]={0};
37 read(pipefd[0],c,sizeof(c));
38 printf("father take:%s\n",c);
39 // char buffer[64]={0};
40 // ssize_t s=read(pipefd[0],buffer,sizeof(buffer)-1);
41 // if(!s)
42 // {
43 // break;
44 // }
45 // else if(s>0)
46 // {
47 // buffer[s]=0;
48 // printf("child say to father:%s\n",buffer);
49 // }
50 // else
51 // {
52 // break;
53 // }
54 }
55
56 return 0;
57 }
我们关闭了子进程的读操作,父进程端的写入操作操作看看结果是什么
本来是子进程写入到缓冲区的hello world可以被父进程读取到,这就是父子间通信了~
匿名管道之间读写规则
我们想一下如果一端不进行写入,或者一段不进行读取,这两个进程之间还能进行正常的交互么?
管道特点
- 管道是一个单向通信的文件,只限于父子进程之间
- 提供流式服务
- 自带同步与互斥机制
- 管道生命周期跟随进程,进程结束管道文件关闭
管道会发生的情况
- 读端不读或者读的很慢,写段要等待读端
- 读端关闭,写段收到另一端关闭信号后终止
- 写端不写或者很慢,读端需要一直等待写端
- 写段关闭,读端会读完管道内容,读到0时结束
命名管道
一张图理解命名管道
boy.c
#include"comm.h"
2 int main()
3 {
4 umask(0);
5 //命名管道的创建
6 if(mkfifo(MY_fifo,0666)<0)
7 {
8 perror("mkfifo");
9 return 1;
10 }
11 //管道创建完成,需要完成进程之间的通信,只需要对管道进行文件操作即可
12 int fd=open(MY_fifo,O_RDONLY);
13 if(fd<0)
14 {
15 perror("open");
16 return 2;
17 }
18
19 //进行相对应的读写操作
20 while(1)
21 {
22 char buffer[64]={0};
23 ssize_t s=read(fd,buffer,sizeof(buffer)-1);
24 if(s>0)//读取成功
25 {
26 buffer[s]=0;
E> 27 if(strcmp(buffer,"show")==0)
28 {
29 if(fork()==0)
30 {
31 execl("/usr/bin/ls","ls","-l",NULL);
32 exit(1);
33 }
E> 34 waitpid(-1,NULL,0);
35 }
36 else if(strcmp(buffer,"run")==0)
37 {
38 if(fork()==0)
39 {
40 execl("/usr/bin/sl","sl",NULL);
41 }
E> 42 waitpid(-1,NULL,0);
43 }
44 else
45 {
46 printf("gril# %s\n",buffer);
47 }
48 }
49 else if(!s)
50 {
51 printf("gril quit ...\n");
52 break;
53 }
54 else
55 {
56 perror("read");
57 break;
58 }
59 }
60
61
62 close(fd);
63 return 0;
64 }
gril.c
1 #include"comm.h"
2 #include<string.h>
3 int main()
4 {
5 //进程之间完成通信工作,另外一个进程就不需要在创建管道文件了,只需要对以创建好的管道文件
进行读写操作即可
6 //获取管道文件描述符
7 int fd=open(MY_fifo,O_WRONLY);
8 if(fd<0)
9 {
10 perror("open");
11 return 1;
12 }
13 //成功打开之后就可以进行操作读写操作
14 while(1)
15 {
16 printf("请输入:");
17 fflush(stdout);
18 char buffer[64]={0};
19 //先将数据从标准输入拿到这个进程内部
20 //就是你从键盘键入的内容,需要先读到这进程当中,这个进程再做处理,通过管道发送给别的进
程
21 ssize_t s=read(0,buffer,sizeof(buffer)-1);
22 if(s>0)
23 {
24 buffer[s-1]=0;
25 printf("%s\n",buffer);
26 write(fd,buffer,strlen(buffer));
27 }
28 }
29 close(fd);
30 return 0;
31 }
System v 共享内存
共享内存是什么:
共享内存指 (shared memory)在多处理器的计算机系统中,可以被不同中央处理器(CPU)访问的大容量内存。由于多个CPU需要快速访问存储器,这样就要对存储器进行缓存(Cache)。任何一个缓存的数据被更新后,由于其他处理器也可能要存取,共享内存就需要立即更新,否则不同的处理器可能用到不同的数据。共享内存是 Unix下的多进程之间的通信方法 ,这种方法通常用于一个程序的多进程间通信,实际上多个程序间也可以通过共享内存来传递信息。
共享内存数据结构
我们知道在操作系统中是存在大量的进程的,如果两两进程进程进行通信,就需要多个共享内存。既然共享内存在系统中存在多份,就一定要将这些不同的共享内存管理起来,即先描述,再组织;为了保证两个或多个进程能够看到它们的同一份共享内存,那么共享内存一定要有能够唯一标识性的ID,方便让不同的进程识别它们的同一份共享内存;这个所谓的ID一定是在共享内存的数据结构中;
共享内存释放
我们在make clean之后,又make生成之后,发现显示shget: file exist。这样我们只是把这个进程结束掉了,但是,这两个进程之间创建的共享内存还是存在的,直到服务器被关闭了,这个共享内存也就没有了。共享内存不进行释放这样对用户是不友好的。所以这里我们需要手动释放共享内存;
- 命令释放:ipcrm -m + 共享内存id
- 函数释放:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
共享内存函数解读
shmget函数
功能:用来创建共享内存原型int shmget(key_t key, size_t size, int shmflg);参数key: 这个共享内存段名字size: 共享内存大小shmflg: 由九个权限标志构成,它们的用法和创建文件时使用的 mode 模式标志是一样的返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回 -1
shmat函数
功能:将共享内存段连接到进程地址空间原型void *shmat(int shmid, const void *shmaddr, int shmflg);参数shmid: 共享内存标识shmaddr: 指定连接的地址shmflg: 它的两个可能取值是 SHM_RND 和 SHM_RDONLY返回值:成功返回一个指针,指向共享内存第一个节;失败返回 -1
notice:
shmdt函数
功能:将共享内存段与当前进程脱离原型int shmdt(const void *shmaddr);参数shmaddr: 由 shmat 所返回的指针返回值:成功返回 0 ;失败返回 -1注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmctl函数
功能:用于控制共享内存原型int shmctl(int shmid, int cmd, struct shmid_ds *buf);参数shmid: 由 shmget 返回的共享内存标识码cmd: 将要采取的动作(有三个可取值)buf: 指向一个保存着共享内存的模式状态和访问权限的数据结构返回值:成功返回 0 ;失败返回 -1
server.c
1 #include"comm.h"
2 #include<unistd.h>
3 int main()
4 {
5 //创建一个共享内存的key值
6 key_t key=ftok(PATH_NAME,PROJ_ID);
7 if(key<0)
8 {
9 perror("ftok");
10 return 1;
11 }
12 //共享内存的创建,参数解读,key是刚才生成的值,size共享内存的大小,IPC_CREAT|IPC_EXCL确
保生成的shm是全新的
13 int shmid=shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);
14 if(shmid<0)
15 {
16 perror("shmget");
17 return 2;
18 }
19 printf("key: %u,shmid: %d\n",key,shmid);
20 //实现共享内存和进程之间的链接
21 char*mem=(char*)shmat(shmid,NULL,0);
22 printf("attaches shm success\n");
23 sleep(15);
24
25 //断开共享内存和进程链接,不等于删除
26 shmdt(mem);
27 printf("detaches shm success \n");
28 sleep(5);
29
30 //共享内存的删除,这里有专门的删除函数,不用在命令行使用ipcrm删除了
31 shmctl(shmid,IPC_RMID,NULL);
32 printf("key: %u,shmid: %d->shm delete success\n",key,shmid);
33 sleep(10);
34 return 0;
35 }
client.c
1 #include"comm.h"
2 #include<unistd.h>
3 int main()
4 {
5 key_t key=ftok(PATH_NAME,PROJ_ID);
6 if(key<0)
7 {
8 perror("ftok");
9 return 1;
10 }
11 printf("%u\n",key);
12 //用户端获取共享内存,使用创建的方法,如果存在就直接使用了
13 int shmid=shmget(key, SIZE,IPC_CREAT);
14 if(shmid<0)
15 {
16 perror("shget");
17 return 1;
18 }
19 //获取成功之后就可以attach了
20 char*mem=(char*)shmat(shmid,NULL,0);
21 sleep(15);
22 printf("client process attach success\n");
23
24 //client端断开
25 shmdt(mem);
26 printf("detaches shm success\n");
27 sleep(5);
28 return 0;
29 }