进程间通信的前提:让不同的进程看到同一个资源!!
IPC(Inter-Process Communication,进程间通信)。
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。共享内存可以减少数据拷贝的次数!
目录
1. 共享内存的原理
1.1 共享内存示意图
1.2 共享内存数据结构
系统中可以用shm进行通信->是不是只能有一对进程使用共享内存呢?不是->
所以,在任何一个时刻,可能有多个共享内存在被用来进行通信->
所以系统中一定会存在很多shm同时存在->OS要不要整体管理所有的共享内存呢?要的!->
OS如何管理多个shm内存呢?先描述,在组织->
所以共享内存,不是我们想的那样,只要在内存中开辟空间即可,系统也要为了管理shm,构建对应的共享内存的结构体对象!->
共享内存=共享内存的内核数据结构(struct shmid_ds)+ 真正开辟的内存空间。
1.3 共享内存的原理分析
- 首先要在物理内存上开辟一块共享内存的空间。
- 再将该共享空间关联进程上,就是进程的虚拟地址空间的共享区需要的得到该共享内存的地址。
- 双方都得到之后,就可以对共享内存进行读写,从而进行进程通信!
-
取消进程关联——提高代码的健壮性
-
释放共享内存
2 共享内存的操作
2.1 命令行
共享内存的生命周期是随OS系统的,进程创建了共享内存后,没有手动进行释放共享内存,进程退出的时候,共享内存还是存在的,只有当系统重启的时候才会被释放。
在使用共享内存,结束程序退出后。如果你没在程序中用shmctl()删除共享内存的话,一定要在令行下用ipcrm命令删除这块共享内存。你要是不管的话,它就一直在那儿放着了。
ipcs命令
取得ipc信息:
ipcs [-m|-q|-s]
-m 输出有关共享内存(shared memory)的信息
-q 输出有关信息队列(message queue)的信息
-s 输出有关“遮断器”(semaphore)的信息
ipcrm命令
删除ipc
ipcrm -m|-q|-s shm_id
2.2 共享内存函数
shmget函数
功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
shmat函数(shared memory attach)
功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
【说明】
- shmaddr为NULL,核心自动选择一个地址
- shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
- shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr -(shmaddr % SHMLBA)
- shmflg=SHM_RDONLY,表示连接操作用来只读共享内存
shmdt函数(shared memory detach)
功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmctl函数(shared memory control)
功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
3 使用共享内存进行通信
3.1 使用步骤
第一步 :
调用 ftok ( )函数获取一个接近唯一的 key 值供shmget函数使用(ftok是一个算法而已)
第二步:
调用 shmget ( ) 函数创建或获取一个已有的共享内存,并返回该内存标识符
第三步:
调用 shmat ( ) 函数将指定标识符的共享内存映射到该进程的页表中,并返回指针供进程使用 ,换句话说就是将指定的共享内存与进程关联起来
第四步:
调用 shmdt ( ) 函数 解除对应共享内存与该进程间的关联-+
第五步:
调用shmctl ( ) 函数 将指定表示符的共享内存删除
3.2 获取key值
为什么要获取key的值?
- 由于获取共享内存时,shmget()会根据key值转换成对应的IPC标识符(共享内存标识符shmid)。
- 也就意味着,shmget()函数只要我们传相同的key,就能得到相同共享内存标识符,所以key和共享内存标识符是一一对应的!
- 共享内存标识符又和共享内存块一一对应,所以当不同进程使用相同的标识符将对应共享内存块映射到进程时,就会让不同进程访问同一个空间,达到数据交互的目的。
- 但是当想创建一个新的共享内存块供进程使用时,就需要一个接近唯一的key值,这时就可以使用ftok()函数帮我们获取随机数,让我们无需指定key值并无需思考key值是否重复。
key VS shmid
key的值只是用来创建和获取共享内存空间,本质是在内核中使用key值的;类比于文件iNode编号
shmid是共享空间描述符,对shm未来所有操作,都需要通过shmid来进行;类比于文件fd
3.3 创建 | 获取共享内存
shmget()系统调用会创建一个新的共享内存段或获取一个已经存在的共享内存段标识符
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
注意:
shmget()新创建的内存段中的内容会被初始化为0 , 只要 shmget ()中的key 参数相同就会获取到同 一个共享内存段标识符
作用 :
创建一个新的共享内存段或获取一个已经存在的共享内存段标识符
返回值:
成功返回获取到的内存段的描述符,失败返回-1,可以得到shmid
参数解释:
key : 想创建新的共享内存段应使用 ftok() 获取 key 值,想获取已有的共享内存段就使用开辟内存时所传递的key值
size: 是一个正整数,表示所申请共享内存段的字节数。如果shmget () 是用来获取一个已有的的共享内存段标识符,则size参数不起作用,但必须小于或等于已有共享内存段的实际大小。
shmfly:可以是 IPC_CREAT 或者是 IPC_CREAT | IPC_EXCL
IPC_CREAT : 如果该key值的共享内存段已存在,则直接获取其内存段标识符并返回,但如果不存在与该key值相同的内存段则创建一个新的共享内存段,并返回新内存段的标识符。
IPC_CREAT | IPC_EXCL :注意 IPC_EXCL 不能单独使用,需或上 IPC_CREAT 使用。表示但如果不存在与该key值相同的内存段则创建一个新的共享内存段,并返回新内存段的标识符。如果该key值的共享内存段已存在,则直接返回错误
注意:
IPC_CREAT参数不挑内存,有了直接拿标识符,没有才创建
IPC_CREAT | IPC_EXCL参数很挑,一定要自己新创建的共享内存段的标识符,也就意味着这个参数能保证程序获取的内存段是最新的,不可能有其他人使用
3.4 使用共享内存段
上述 shmget() 系统调用只是获取到了共享共存段的标识符,但要使标识符对应的内存段还需将内存段映射进进程的虚拟地址空间中
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
作用:将标识符对应的内存段映射进进程的虚拟地址空间中(也可以理解为关联共享内存段)
返回值:成功返回共享内存段的地址,失败返回-1
参数解释:
shmid : 要关联的目标共享内存段的描述符
shmaddr : 建议设置为NULL,这样共享内存会自动被映射到一个合适的虚拟地址空间。(不建议传递非NULL,传递的是其他指针时,系统会根据这个指针及其地址边界,内存对齐等分配地址。)
shmflg : 如果参数为 0 ,则进程对共享内存段有读写权限
如果指定为:SHM_RDONLY 则只要只读权限
3.5 解除共享内存段
调用 shmdt()来分离共享内存段,执行了该步骤后进程将无法再引用这块共享内存。
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
参数解释:(和free类似)
shmaddr :为使用 shmat()获取到的指针
作用:取消 shmaddr 指向的共享内存与进程的映射(取消关联)
3.6 共享内存的销毁
4 代码实例——共享内存
lesson16/shm · 杰编程/LinuxCode - 码云 - 开源中国 (gitee.com)
5 知识补充
共享内存的大小是以PAGE页(4KB)为单位的,4096个字节数,进程申请的字节数/4096向上取整就是OS给该进程申请的实际空间大小 ,但是用户只能使用自己申请的空间大小,不能使用后面的空间。