进程间通信
概念
进程间通信本质是让不同的进程,看到同一份资源内存(文件内核缓冲等)
资源由谁(os中的那些模块)提供,就有了不同的进程通信
进程通信的目的
1.数据传输:一个进程需要将它的数据发送给另一个进程
2.资源共享:多个进程之间共享同样的资源
3.通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)
4.进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
进程间通信的发展
①管道
②System V进程间通信(主机内通信)
③POSIX进程间通信(可以跨网络通信)
管道
管道的实质是一个内核缓冲区
①匿名管道
匿名管道没有文件名,没有磁盘索引节点,进程没有权限进行访问,只能以进程继承的形式让另一个进程访问
所以匿名管道主要是具有血缘关系的进程的通信方式
简单的父子进程通信:
1 #include<stdio.h>
2 #include<sys/stat.h>
3 #include<string.h>
4 #include<wait.h>
5 #include<stdlib.h>
6 #include<sys/types.h>
7 #include<unistd.h>
8 int main()
9 {
10 int fd[2];
11 if(pipe(fd)<0)
12 {
13 perror("pipi");
14 return 1;
15 }
16 pid_t id=fork();
17 if(id==0)
18 {
19 //子进程
20 close(fd[0]);//关闭读端
21 const char* msg="hello world";
22 int count=10;
23 while(count--)
24 {
25 write(fd[1],msg,strlen(msg));
26 sleep(1);
27 }
28 close(fd[1]);
29 exit(0);
30 }
31 close(fd[1]);
32 char buffer[20];
33 while(1)
34 {
35 ssize_t reallysize=read(fd[0],buffer,sizeof(buffer));
36 if(reallysize>0)
37 {
38 buffer[reallysize]='\0';
39 printf("father receive:%s\n",buffer);
40 }
41 else if(reallysize==0)
42 {
43 //读取完
44 printf("read over\n");
45 break;
46 }
47 else
48 {
49 printf("read error\n");
50 break;
51 }
52 }
53 waitpid(id,NULL,0);
54 return 0;
55 }
管道大小linux2.6.11后为65535字节
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
为什么不能定义全局变量buffer进行通信?
因为进程之间具有独立性,如果修改,会发生写时拷贝
而子进程修改数据怎么就可以呢?
根本原因是父子进程通过管道通信,是父进程将数据拷贝给操作系统,操作系统在拷贝给子进程,并不属于父进程,所以不会写时拷贝,通信不是用的父子进程的数据直接通信
看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件思想”
②命名管道
命令行创建命名管道:mkfifo 创建命名管道
简单的命令行通信:
程序里创建命名管道
comm.h
1 #include<stdio.h>
2 #pragma once
3 #include<unistd.h>
4 #include<sys/types.h>
5 #include<sys/stat.h>
6 #include<sys/fcntl.h>
7 #define FILE_NAME "myfifo"
8 #include<string.h>
client.c
1 #include"comm.h"
2 int main()
3 {
4 int fd=open(FILE_NAME,O_WRONLY);
5 if(fd<0)
6 {
7 printf("open error\n");
8 return 1;
9 }
10 char s[100];
11 while(1)
12 {
13 s[0]=0;
14 printf("请输入:");
15 fflush(stdout);
16 ssize_t n=read(0,s,sizeof(s)); //从显示器上读,写到字符串里
17 if(n>0)
18 {
19 s[n-1]=0;
20 write(fd,s,strlen(s));//字符串向管道中写入
21 }
22 }
23 close(fd);
24 return 0;
25
26 }
server.c
1 #include"comm.h"
2 int main()
3 {
4 if(mkfifo(FILE_NAME,0644)<0)
5 {
6 perror("mkfifo");
7 return 1;
8 }
9 int fd=open(FILE_NAME,O_RDONLY);
10 if(fd<0)
11 {
12 perror("open");
13 return 2;
14 }
15 char s[100];
16 printf("创建成功\n");
17 while(1)
18 {
19 s[0]=0;//清空字符串
20 ssize_t n=read(fd,s,sizeof(s)-1);
21 if(n>0)
22 {
23 s[n]=0;
24 printf("server receive:%s\n",s);
25 }
26 else if(n==0)
27 {
28 printf("read over\n");
29 break;
30 }
31 else
32 {
33 printf("read error\n");
34 }
35 }
36 return 0;
37 }
最后并没有把内容写到磁盘上,所以是内存文件
命令行上的|是匿名管道还是命名管道呢?
System V进程间通信
管道VS System V:
管道通信本质是基于文件的,OS直接沿用文件实现
System V通信是OS特地设计的,包括共享内存,消息队列,信号量,前两者的目的是传送数据,后者是保证进程的互斥与同步
①共享内存
共享内存的建立整个过程
1.申请共享内存
2.共享内存挂接到地址空间(建立映射关系)
3.去关联共享内存(修改页表,取消映射关系)
4.释放共享内存(归还内存资源)
1.创建共享内存
OS中共享内存可能存在多个共享内存,为了管理和维护这些共享内存,操作系统底层提供了内核数据结构
key标识共享内存的唯一性,用key参数来保证通信的进程看到同一份资源
获取key
size要进行页对齐,申请按页申请,但只能使用指定的大小,一页4096字节,所以尽量设置为页的整数倍
参数解释
代码:
1 #include"comm.h"
2 int main()
3 {
4 key_t k=ftok(PATHNAME,PROJ_ID);
5 if(k<0)
6 {
7 printf("ftok error\n");
8 return 1;
9 }
10 printf("%x\n",k);
11 int shm=shmget(k,size,IPC_CREAT|IPC_EXCL);
12 if(shm<0)
13 {
14 perror("shmget");
15 return 2;
16 }
17 sleep(10);
18 return 0;
19 }
命令行查看共享内存:ipcs -m
命令行删除共享内存:ipcrm -m shmid
2.控制共享内存
对于第二个参数cmd
命令 | 解释 |
---|---|
IPC_STAT | 把shmid_ds结构中的数据设置为当前共享内存的关联值 |
IPC_SET | 在进程有足够权限的情况下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值 |
IPC_RMID | 删除共享内存段 |
3.关联共享内存
4.取消关联
进程通过共享内存通信
comm.h
1 #pragma once
2 #include<stdio.h>
3 #include<sys/types.h>
4 #include<sys/ipc.h>
5 #include<unistd.h>
6 #include<sys/shm.h>
7 #define PATHNAME "/home/tzc/2022/2/14/comm.h"
8 #define PROJ_ID 0x4356
9 #define size 4096
server.c
1 #include"comm.h"
2 int main()
3 {
4 key_t k=ftok(PATHNAME,PROJ_ID);
5 if(k<0)
6 {
7 printf("ftok error\n");
8 return 1;
9 }
10 printf("%x\n",k);
11 int shm=shmget(k,size,IPC_CREAT|IPC_EXCL|0666);//默认权限
12 if(shm<0)
13 {
14 perror("shmget");
15 return 2;
16 }
17 printf("%d\n",shm);
18 char* mem=(char*)shmat(shm,NULL,0);
19 while(1)
20 {
21
22 //读取共享内存
23 printf("server receive:%s\n",mem);
24 sleep(1);
25 }
26
27 sleep(2);
28 shmdt(mem);
29 shmctl(shm,IPC_RMID,NULL);//删除共享内存
30 return 0;
31 }
client.c
1 #include"comm.h"
2 int main()
3 {
4 key_t k=ftok(PATHNAME,PROJ_ID);
5 if(k<0)
6 {
7 printf("ftok error\n");
8 return 1;
9 }
10 printf("%x\n",k);
11 int shm=shmget(k,size,IPC_CREAT);//获取shm
12 if(shm<0)
13 {
14 perror("shmget");
15 return 2;
16 }
17 char* mem=shmat(shm,NULL,0);//关联共享内存
18 int i=0;
19 while(1)
20 {
21 mem[i]='a'+i;
22 i++;
23 mem[i]='\0';
24 sleep(1);
25 }
26
27 shmdt(mem);//去关联
28 return 0;
29
30 }
深入理解共享内存
共享内存的特点
1.共享内存是速度最快的,因为拷贝次数少
2.不提供任何的互斥与同步
在一个进程写的同时另一个进程也能读
共享内存相关数据结构
共享内存结构体
②消息队列
消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法
每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值
相关接口
三者对比
③信号量
信号量本质是一个计数器,用来描述临界资源中资源数量
二元信号量sem=1实现互斥,多元信号量sem>1
sem>0表示还有sem个资源可用,sem=0表示当前无资源可用,sem<0表示还有-sem个资源在阻塞等待资源
信号量++对应V操作,表示资源释放
信号量–对应P操作,表示占用资源