共享内存
- 共享内存
- 共享内存原理
- 创建共享内存
- 关联共享内存
- 去关联共享内存
- 控制共享内存
- 使用共享内存
- 代码
共享内存
进程间通信的前提是:先让不同的进程,看到同一份资源
之前,管道进程通信是采用看到同一个文件,那么共享内存就是看到同一份内存
共享内存原理
使用共享内存进行通信
1、先在物理内存中创建共享内存
2、将每一个进程通过页表和共享内存产生关联
3、使用共享内存进行通信
4、使用结束,将进程和共享内存去关联
5、删除共享内存
共享内存,首先需要在物理内存中先创建一份共享内存,通过页表把共享内存映射到各自进程的虚拟进程地址空间的共享区(在栈区和堆区之间(使用动态库时,动态库也映射到这部分))来实现各自进程关联共享内存。
ps
:有创建共享内存,关联共享内存,就有删除共享内存和去关联共享内存
创建共享内存
int shmget(key_t key, size_t size, int shmflg);
key:表示共享内存唯一性的值,两个进程使用同一个
key
值就可以看到同一个共享内存size:共享内存大小(建议
4kb
的整数倍)分配空间单位是
4kb
,如果申请4097个字节,它会分配给8kb
,但是只有4097个字节可供使用
shmflg:IPC_CREAT
,IPC_EXCL
PC_CREAT
:创建共享内存,如果存在,就获取它,如果不存在,就创建共享内存
IPC_EXCL
:不能单独使用,必须和IPC_CREAT
组合使用,如果共享内存不存在,就创建共享内存,如果存在,返回失败(这是为了保证函数调用成功一定是一个全新的共享内存)返回值:如果创建成功,会返回共享内存标识符(
shmid
),失败会返回-1在这里我们介绍了
shmget
函数的参数,那么我们怎么知道这个共享内存是否存在呢?首先,我们需要知道可能存在多个共享内存,那么就需要对共享内存进行管理,先描述,再组织,那么在内核中,内核会给我们维护共享内存的数据结构。
下表,共享内存的数据结构(
ipc_ids
)在
ipc_ids
中有一个结构体ipc_id_ary
,在ipc_id_ary
中有两个字段,size,p
,p字段是一个指向kern_ipc_perm
数据结构的指针数组下表,
kern_ipc_perm
在
kern_ipc_perm
中有一个key
(IPC
关键字),通过key
来标识共享内存,我们让两个进程使用相同key(key
值由用户提供),就可以看到同一个共享内存,进行通信。那么也可以通过key
来判断共享内存是否存在这个
key
是由用户提供的,就是int shmget(key_t key, size_t size, int shmflg);
的第一个参数(
key_t key
),这个key值我们通常使用ftok()
函数生成key_t ftok(const char *pathname, int proj_id);
pathname:一个文件路径
proj_id
:一个8位数字,不能为0返回值为一个密钥(根据文件的
inode
和proj_id
生成)
关联共享内存
void *shmat(int shmid, const void *shmaddr, int shmflg);
将共享内存段连接到进程地址空间
shmid
: 共享内存标识符(表示关联到那个共享内存)
shmaddr
:指定连接的地址(表示关联到该进程虚拟进程地址空间的哪个位置,一般设为nullptr
)
shmflg
:它的两个可能取值是SHM_RND
和SHM_RDONLY
(表示对于该共享内存的读写权限,一般设为0,默认读写方式)返回值:成功返回一个指针,指向共享内存关联到进程地址空间的地址;失败返回-1
去关联共享内存
int shmdt(const void *shmaddr);
将共享内存段与当前进程脱离
shmaddr
: 由shmat
所返回的指针(共享内存关联到进程地址空间的地址)返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
用于控制共享内存
shmid
:由shmget
返回的共享内存标识符
cmd
:将要采取的动作(有三个可取值)(IPC_RMID
表示立即删除)
buf
:指向一个保存着共享内存的模式状态和访问权限的数据结构返回值:成功返回0;失败返回-1
注意:
当我们运行完毕创建全新的共享内存的代码后(进程退出),再次运行代码,会报错,因为这个共享内存是已存在的。
int main()
{
key_t key=creat_key();
cout<<"key:"<<key<<endl;
//创建共享内存
int shmid=shmget(key,4096,IPC_CREAT|IPC_EXCL);
if(shmid<0)
{
log()<<"error"<<strerror(errno)<<endl;
exit(2);
}
log()<<"shared memory: sucessed"<<endl;
return 0;
}
system V下的共享内存,生命周期是随内核的,如果不显示删除,就必须重启os
解决.
如何显示删除
命令行
ipcrm -m shmid
系统接口
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
如何查看
IPC
资源ipcs -m
perms
:表示该共享内存的读写权限(当前perms为0,那就无法对共享内存进行读写,所以创建共享内存时需要*|读写权限*)
nattch
:表示挂接到该共享内存的进程数
使用共享内存
我们把共享内存和进程产生关联,是把共享内存通过页表映射到进程地址空间的栈区和堆区之间的共享区,也就是映射到用户空间,既然是用户空间,那么我们就可以直接进行使用,而不需要调用系统接口。
同时因为共享内存被关联到进程的用户空间,可以被进程直接使用,这使得共享内存是进程间通信最快的方式,同时导致共享内存没有任何访问控制机制,不会发生阻塞等待,共享内存可以直接通信,但不安全
代码
两个进程使用共享内存进行通信
comm.hpp
#pragma once #include<iostream> #include<map> #include<sys/ipc.h> #include<sys/shm.h> #include<cstring> #include<sys/types.h> #include<unistd.h> #include<time.h> #define IPC_PATH "/home/byld/test_linux" //创建key值,两个进程使用相同的key值访问相同的共享内存 key_t creat_key() { return ftok(IPC_PATH,0X12); } std::ostream& log() { std::cout<<"时间戳"<<time(nullptr)<<"|"; return std::cout; }
client_IPC.cpp
#include"comm.hpp" using namespace std; int main() { key_t key=creat_key(); //获取共享内存 int shmid=shmget(key,4096,IPC_CREAT); //关联到进程 char* str=(char*)shmat(shmid,nullptr,0); if(str==(char*)-1) { log()<<"attach failed"<<endl; exit(3); } log()<<"attach successed"<<endl; //使用 //写入 while(1) { cout<<"客户端:输入#:"<<endl; ssize_t s = read(0,str,4096); if(s>0) { str[s]='\0'; } } //去关联 shmdt(str); log()<<"detach sucess"<<endl; return 0; }
server_IPC.cpp
#include"comm.hpp" using namespace std; int main() { key_t key=creat_key(); cout<<"key:"<<key<<endl; //创建共享内存 int shmid=shmget(key,4096,IPC_CREAT|IPC_EXCL|0666); if(shmid<0) { log()<<"error"<<strerror(errno)<<endl; exit(2); } log()<<"shared memory sucessed shmid:"<<shmid<<endl; //进程关联共享内存 char* str=(char*)shmat(shmid,nullptr,0); if(str==(char*)-1) { log()<<"attach failed"<<endl; exit(3); } log()<<"attach successed shmid:"<<shmid<<endl; //使用 //读出 while(1) { cout<<"服务器端读出#:"; cout<<str<<endl; sleep(1); } //进程去关联共享内存 shmdt(str); log()<<"detach sucess"<<endl; //删除共享内存 shmctl(shmid,IPC_RMID,nullptr); return 0; }