进程间的通信之共享内存
一、system V 内存共享原理
利用内存共享进行进程间的通信的原理其实分为以下几个步骤:
- 在物理内存中创建一块共享内存。
- 将共享内存链接到要通信的进程的页表中,并通过页表进行进程地址空间的映射。
- 进程地址空间映射完毕以后返回首个虚拟地址,以便于进程之间进行通信。
根据共享内存的原理,一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据,这也就大大的提高了进程间通信的速度。
系统中可以用共享内存进行通信,但是系统中可能不是只有一对进程使用共享内存,在任何一个时刻,可能有多个共享内存在被用来进行通信,所以系统中一定会存在很多共享内存块,操作系统也要整体管理所有的共享内存。
所以共享内存,不是我们想的那样,只要在内存中开辟空间即可,系统也要为了管理共享内存,构建对应的描述共享内存的结构体对象! 在
Linux
中这个结构体是
struct shmid_ds
,这个结构体里面存放了共享内存的各种属性。
所以:**共享内存 = 共享内存的内核数据结构(
struct shmid_ds
) + 真正开辟的内存空间**
二、共享内存的使用
共享内存的使用涉及很多的系统调用,在这里我们边实现代码边进行讲解。
1、ftok函数
key_t
是一个
int
的
typedef
。
这个函数的作用就是根据你传递的两个参数,结合函数内部的算法生成一个
key_t
类型的值,这里我们暂时把这个值记为
key
,这个
key
值几乎不可能出现:参数的不同,结果生成相同的
key
值。
这个
key
值单独看起来是没有任何作用的,它要与其他的系统调用结合起来才会发挥作用,未来要通信的两个进程就可以在函数
ftok
输入相同的相同的参数,生成相同的
key
值,然后通过这个
key
值就能确定它们想要通信的共享内存是哪一个了。
- 参数:第一个是路径名,第二个是项目ID(只有最低8位可以使用),我们可以给这两个参数随意赋值,但是这个文件路径必须是现有的,可以访问的的文件,项目ID最低8位必须有效,而且必须非零。
- 返回值:返回一个
key_t
类型的值,如果调用失败,返回-1
,错误码被设置。
2、shmget函数
这个函数的作用是:在物理内存中申请一块共享内存,并返回一个和参数
key
相关联的这块共享内存的标识符。
- 参数 :1. 第一个参数是用
ftok
函数生成的key
值。2. 第二个参数是设置共享内存的大小(你实际得到的大小),但是这个共享内存的大小实际上操作系统是按照PAGE_SIZE
(4KB)对齐,来分配的。3. 第三个参数是标志位,这里主要介绍三个标志:IPC_CREAT
和IPC_EXCL
及mode_flags``````IPC_CREAT
:如果与key
有关共享内存不存在,就创建,如果已经存在就直接返回共享内存的标识符。IPC_EXCL
: 这个标志要与IPC_CREAT
一起使用,表示共享内存如果不存在就创建,如果存在就直接报错,通过这两个标志位的组合,我们能够保证我们拿到的共享内存一定是最新的,而不是以前其他进程可能使用过的。mode_flags
: 最低9位有效,表示申请的共享内存的权限。 - 返回值:调用成功,返回一个和参数
key
相关联的这块共享内存的标识符,调用失败就返回-1
,错误码被设置。
3、shmat函数
这个函数的作用就是将我们申请得到的共享内存与进程地址空间进行映射,映射完毕以后返回给我们一个映射好以后的首个虚拟地址,通过这个虚拟地址我们就可以进行进程间通信了。
- 参数:1. 来自于
shmget
函数得到的共享内存标识符。2. 一个虚拟地址,我们想要将共享内存的地址在进程地址空间的哪里开始映射,如果这里设置为NULL
操作系统会自动选择一个合适的地址进行映射。3. 标志位,当这里传入SHM_RDONLY
时,表示映射以后的进程对于此段空间只有读权限,没有写权限,当我们在这里传入0
时,表示读写权限都有。 - 返回值: 如果函数调用成功就返回映射好以后的首个虚拟地址,以便于进行读取和写入,如果调用失败就会返回一个
(void *) -1
的值
4、shmdt函数
当我们在进程地址空间中挂起了一个块共享内存之后当我们不需要使用时,也要将进程地址空间中的链接关系进行取消,shmdt函数的作用就是如此。
- 参数:
shmat
函数返回的地址。 - 返回值: 如果调用成功就返回
0
,调用失败就返回-1
5、shmctl函数
此函数的功能很强大,里面有许多的标志位,可以完成许多不同的工作,这个函数主要用来控制共享内存。
- 参数1. 来自于
shmget
函数得到的共享内存标识符。2. 是标志位,里面有许多标志,这里我们就介绍两个:IPC_RMID
,IPC_STAT``````IPC_RMID
: 设置这个标记表示要销毁共享内存,调用者必须是共享内存的创建者,或者是特权用户。IPC_STAT
: 将shmid
相关的内核数据结构中的信息复制到第三个参数buf
所指向的shmid_ds
结构中,调用者必须要有读权限。3. 一个struct shmid_ds
类型的指针,在标志位中设置了IPC_STAT
,指针所指向的变量里面就能拿到相关的内核信息,如果不关心内核信息可以设置为nullptr
。 - 返回值:一般来说,成功返回是
0
,错误返回结果是-1
。
6、代码使用
command .hpp
#ifndef__COMMAND_HPP__#define__COMMAND_HPP__#include<iostream>#include<string>#include<cstring>#include<cstdlib>#include<cerrno>#include<sys/types.h>#include<sys/stat.h>#include<sys/ipc.h>#include<sys/shm.h>#include<unistd.h>#defineNUM4096const std::string pathname ="./";constint proj_id =66;#endif
sysserve.cpp
#include"command.hpp"intmain(){// 1.生成相同的key值
key_t key =ftok(pathname.c_str(), proj_id);if(key ==-1){
std::cerr <<"错误码"<< errno <<" "<<strerror(errno)<< std::endl;exit(-1);}// 2.申请共享内存umask(0);int shmid =shmget(key, NUM, IPC_CREAT | IPC_EXCL |0666);if(shmid ==-1){
std::cerr <<"错误码"<< errno <<" "<<strerror(errno)<< std::endl;exit(-1);}// 停顿3s观察连接数变为 1sleep(2);// 3.将共享内存挂接到进程地址空间char*s =(char*)shmat(shmid,nullptr,0);// 4.进行进程间的通信sleep(6);// 启动另外一个要通信进程,看到连接数变为2// 5.取消挂接int err =shmdt(s);if(err ==-1){
std::cerr <<"错误码"<< errno <<" "<<strerror(errno)<< std::endl;}sleep(3);// 6.删除共享内存shmctl(shmid, IPC_RMID,nullptr);return0;}
sysclient.cpp
#include"command.hpp"intmain(){// 1.生成相同的key值
key_t key =ftok(pathname.c_str(), proj_id);if(key ==-1){
std::cerr <<"错误码"<< errno <<" "<<strerror(errno)<< std::endl;exit(-1);}// 2.得到共享内存标识符int shmid =shmget(key, NUM, IPC_CREAT);if(shmid ==-1){
std::cerr <<"错误码"<< errno <<" "<<strerror(errno)<< std::endl;exit(-1);}// 3.将共享内存挂接到进程地址空间char*s =(char*)shmat(shmid,nullptr,0);// 4.进行进程间的通信sleep(3);// 5.取消挂接int err =shmdt(s);if(err ==-1){
std::cerr <<"错误码"<< errno <<" "<<strerror(errno)<< std::endl;}return0;}
在
Linux
的命令行中我们可以使用
ipcs -m
命令查看共享内存的详细信息。
可以看到第一个是共享内存的
key
值,第二个是共享内存的标识符,第三个是拥有者,第四个是权限,第五个是共享内存的字节数,第六个是共享内存的连接数,第七个是共享内存的状态。
运行上面的代码,先启动
sysserve
再启动
sysclient
,我们可以看到连接数由
0->1 ->2 ->1 ->0 ->
被删除
三、一些细节的补充
- 共享内存的生命周期是随内核的,即进程退出以后如果没有删除共享内存,则共享内存不会消失!
- 共享内存没有任何保护机制(同步和互斥)
版权归原作者 看到我请叫我滚去学习Orz 所有, 如有侵权,请联系我们删除。