一、本章重点
1、什么是共享内存?
2、使用共享内存
3、共享内存的性质
4、基于管道+共享内存的cs通信
5、System V标准的三种进程通信
01 什么是共享内存?
1、一段用来进程通信的内核缓冲区
2、图解:
3、系统可以创建多个共享内存,如何确保两个进程看到同一个共享内存?
通过相同的key可以看到同一个共享内存,这个key是用来创建或获取共享内存的,用户可以传相同的key给两个进程,进程一用来创建一个全新的共享内存,进程二则用来获取进程一创建的共享内存,这样进程一和进程二就能看到相同的进程。
4、两个进程要使用共享内存需要先做好以下几件事
①创建/获取共享内存
②关联共享内存:即地址空间和物理内存建立映射关系
③去关联:去映射关系
④删除共享内存:因为共享内存的生命周期是随操作系统的,如果不主动删除,需要重启操作系统才会删除。
02 使用共享内存
1、shmget:用于创建共享内存
key:在系统层面上标定共享内存的唯一性,该值存放在共享内存的数据结构中,该值可以随意设置,但一般都是用ftok函数获取一个key值。
size:设置共享内存的大小,一般设置为4k的整数倍,因为操作系统在分配空间时是以4k对齐的。如果你设置为4097字节,它实际给了你8k的大小。
shmflg:设置你是要创建一个新的共享内存,还是获取一个旧的共享内存。
创建一个全新的共享内存:IPC_CREAT | IPC_EXEC | 0666
IPC_CREAT:创建一个共享内存,如果存在则返回旧的共享内存。
IPC_EXEC:不单独使用,IPC_CREAT | IPC_EXEC表示,创建一个共享内存,如果存在则出错返回,也就是说创建一个全新的共享内存。
0666:设置要创建的共享内存的权限。
返回值:出错返回-1.
2、ftok:获取key值
pathname:路径名,可以随意设置,我一般用pwd的输出值。
proj_id:项目id,随意设置。
ftok是通过pathname和proj_id的某种算法生成一个唯一的key值。
返回值:失败返回-1
3、shmctl:完成共享内存的控制,常用于删除共享内存
shmid:用户层标定共享内存唯一性的id。
cmd:设置为IPC_RMID,表示你要删除共享内存。
buf:与共享内存的结构体有关,删除设置为nullptr。
4、shmat:建立映射关系
shmid:用户层标定共享内存唯一性的id。
shmaddr:设置你要映射到地址空间的哪个区域,一般设置为nullptr,让操作系统帮你自动映射。
shmflg:设置你要对共享内存进行只读还是读写,一般设置为0,代表读写。
返回值:返回一段共享区的首地址,和malloc一样,malloc返回的是堆区的首地址。
5、shmdt:去掉映射关系
shmaddr:shmat的返回值,代表你要去关联的共享区。这个函数与free差不多。
6、写一个简单的基于共享内存的cs通信。
![]()
comm.hpp
#include<iostream> #include<sys/types.h> #include<sys/ipc.h> #include<sys/shm.h> #include<sys/stat.h> #include<cstdlib> #include<unistd.h> #define PATH "/home/ds/vscode" #define PROJID 0x1334 #define SIZE 4096 //建议为4k的整数倍
client.cc
#include"comm.hpp" using namespace std; int main() { key_t k = ftok(PATH,PROJID); if(k < 0) { cerr<<"ftok error"<<endl; } umask(0); int shmid = shmget(k,SIZE,IPC_CREAT);//获取共享内存 char* str = (char*)shmat(shmid,nullptr,0);//关联 //使用共享内存 int cnt = 0; while(cnt < 26) { sleep(1); str[cnt] = 'A' + cnt; cnt++; str[cnt] = '\0'; } shmdt(str);//去关联 return 0; }
server.cc
#include"comm.hpp" using namespace std; int main() { key_t k = ftok((const char*)PATH,PROJID); if(k < 0) { cerr<<"ftok error"<<endl; exit(1); } umask(0); int shmid = shmget(k,SIZE,IPC_CREAT | IPC_EXCL | 0666);//创建一个全新的共享内存 if(shmid < 0) { cerr<<"shmget error"<<endl; } char* str = (char*)shmat(shmid,nullptr,0);//关联 //使用共享内存 while(true) { sleep(1); cout<<"."<<str<<endl; } shmdt(str);//去关联 shmctl(shmid,IPC_RMID,nullptr);//删除共享内存 return 0; }
makefile
.PHONY:all all:client server client:client.cc g++ -o $@ $^ -std=c++11 server:server.cc g++ -o $@ $^ -std=c++11 .PHONY:clean clean: rm -f client server
7、查看创建的共享内存
key:在系统层面标定共享内存的唯一性
shmid:在用户层面标定共享内存的唯一性
owner:拥有者
perms:访问权限
bytes:共享内存的大小
nattch:关联数/挂接数
status:状态
8、删除创建的共享内存
可以用系统调用删除:shmctl
也可以用指令删除:ipcrm -m [shmid]
03 共享内存的性质
1、所有进程通信中,共享内存速度最快,因为没有访问控制,数据拷贝次数少。
2、没有访问控制(同步和互斥)
3、面向数据流
4、生命周期随内核
5、全双工
04基于管道+共享内存的cs通信
设计思路:管道用来访问控制,共享内存来传输数据。
client.cc
#include"comm.hpp" using namespace std; int main() { key_t k = ftok(PATH,PROJID); if(k < 0) { cerr<<"ftok error"<<endl; } umask(0); int shmid = shmget(k,SIZE,IPC_CREAT);//获取共享内存 char* str = (char*)shmat(shmid,nullptr,0);//关联 //使用共享内存 int fd = open(PATH_PIPE,O_WRONLY); while(true) { cout<<"please Enter# "; fflush(stdout); ssize_t s = read(0,str,sizeof(char)*4096); if(s < 0) { cerr<<"read error"<<endl; exit(2); } else if(s == 0) { cerr<<"写端关闭"<<endl; exit(0); } else { str[s-1] = '\0'; } write(fd,&s,sizeof(int));//告诉server已经写好了数据 } // int cnt = 0; // while(cnt < 26) // { // sleep(1); // str[cnt] = 'A' + cnt; // cnt++; // str[cnt] = '\0'; // } close(fd); shmdt(str);//去关联 return 0; }
server.cc
#include"comm.hpp" using namespace std; int main() { key_t k = ftok((const char*)PATH,PROJID); if(k < 0) { cerr<<"ftok error"<<endl; exit(1); } umask(0); int shmid = shmget(k,SIZE,IPC_CREAT | IPC_EXCL | 0666);//创建一个全新的共享内存 if(shmid < 0) { cerr<<"shmget error"<<endl; } char* str = (char*)shmat(shmid,nullptr,0);//关联 if(mkfifo(PATH_PIPE,0666)!=0) { cerr<<"mkfifo error"<<endl; } int fd = open(PATH_PIPE,O_RDONLY); //使用共享内存 while(true) { int opt = 0; ssize_t s = read(fd,&opt,sizeof(int));//等待client通知。 if(s==0) { cout<<"写端关闭"<<endl; } assert(s==4); cout<<str<<endl;; } shmdt(str);//去关联 shmctl(shmid,IPC_RMID,nullptr);//删除共享内存 close(fd); unlink(PATH_PIPE); return 0; }
comm.hpp
#include<iostream> #include<sys/types.h> #include<fcntl.h> #include<sys/ipc.h> #include<sys/shm.h> #include<sys/stat.h> #include<cstdlib> #include<unistd.h> #include<assert.h> #define PATH "/home/ds/vscode" #define PROJID 0x1334 #define SIZE 4096 //建议为4k的整数倍 #define PATH_PIPE ".fifo"
makefile
.PHONY:all all:client server client:client.cc g++ -o $@ $^ -std=c++11 server:server.cc g++ -o $@ $^ -std=c++11 .PHONY:clean clean: rm -f client server
05、System V标准的三种进程通信
1、共享内存
对应操作:shmget、shmctl、shmat、shmdt
2、消息队列
对应操作:msgget、msgctl、msgsnd、msgrcv
3、信号量
对应操作:semget、semctl、semop(P操作)、semop(V操作)
4、查看和删除ipc资源
ipcs
ipcs -m/-q/-s
ipcrm -m/-q/-s
三种资源的数据结构:
三者共有的数据结构:
其中创建共享内存shmget中的key就在shmid_ds的ipc_perm中
操作系统通过数组方式将ipc资源管理起来
使用时就强制类型转换:(struct shmid_ds*)array[0]。