进程间通信本质是让不同的进程,看到同一份资源内存(文件内核缓冲等)
资源由谁(os中的那些模块)提供,就有了不同的进程通信
如何实现进程间通信
1.进程之间是具有独立性的,所以难以直接通信
2.进程间通信,一般一定要借助第三方(OS)资源
3.通信的本质就是”数据的拷贝“
进程A->数据"拷贝"给OS的一段内存区域->OS数据"拷贝"给进程B
这样通过OS这个“媒介”就实现了进程A与进程B的通信
1.数据传输:一个进程需要将它的数据发送给另一个进程
2.资源共享:多个进程之间共享同样的资源
3.通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)
4.进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
①管道
②System V进程间通信(主机内通信)
③POSIX进程间通信(可以跨网络通信)
管道的实质是一个内核缓冲区
匿名管道没有文件名,没有磁盘索引节点,进程没有权限进行访问,只能以进程继承的形式让另一个进程访问
所以匿名管道主要是具有血缘关系的进程的通信方式
创建匿名管道
#include cunistd.h>
int pipe(int fd[2])
参数:
fd:输出型参数,文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
返回值:
成功返回0,失败返回错误代码
简单的父子进程通信:
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 创建命名管道
程序里创建命名管道
int mkfifo(const char *pathname,mode_t mode);
参数:
pathname:文件路径名mode:文件属性
返回值:
成功返回0,失败返回-1
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 }
管道VS System V:
管道通信本质是基于文件的,OS直接沿用文件实现
System V通信是OS特地设计的,包括共享内存,消息队列,信号量,前两者的目的是传送数据,后者是保证进程的互斥与同步
共享内存的建立整个过程
1.申请共享内存
2.共享内存挂接到地址空间(建立映射关系)
3.去关联共享内存(修改页表,取消映射关系)
4.释放共享内存(归还内存资源)
int shmget(key_t key, size_t size, int shmflg);
参数:
key:用于标识共享内存的唯一性
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-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
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:
成功返回0;失败返回-1
对于第二个参数cmd
命令 | 解释 |
---|---|
IPC_STAT | 把shmid_ds结构中的数据设置为当前共享内存的关联值 |
IPC_SET | 在进程有足够权限的情况下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值 |
IPC_RMID | 删除共享内存段 |
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
shmid: 共享内存标识
shmaddr:指定连接的地址 ,NULL由OS决定连接到哪
shmflg:属性,它的两个可能取值是SHM_RND和SHM_RDONLY;默认0,支持读写
返回值:
成功返回一个指针,该指针指向对应共享内存的映射到进程地址空间的虚拟地址的共享区的起始地址;失败返回-1
int shmdt(const void *shmaddr);
参数:
shmaddr: 由shmat所返回的指针
返回值:
成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
进程通过共享内存通信
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.不提供任何的互斥与同步
在一个进程写的同时另一个进程也能读
消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法
每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值
相关接口
三者对比
信号量本质是一个计数器,用来描述临界资源中资源数量
1.由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥
2.系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源
3.在进程中涉及到互斥资源的程序段叫临界区
4.原子性:一个操作要么操作完整执行了要么没有,只有两态,那么该操作具有原子性(执行中不能被中断)
二元信号量sem=1实现互斥,多元信号量sem>1
sem>0表示还有sem个资源可用,sem=0表示当前无资源可用,sem<0表示还有-sem个资源在阻塞等待资源
信号量++对应V操作,表示资源释放
信号量–对应P操作,表示占用资源
为什么不直接定义全局变量的计数器呢,非要用PV操作的接口?
因为进程之间具有独立性,会发生写时拷贝,并且计数器++,–并不是原子的,而PV操作是