进程间通信之共享内存及其shm函数的使用
什么是共享内存
共享内存区是最快的IPC(Inter-Process Communication,进程间通信)形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据.
匿名管道和命名管道都需要通过系统调用接口来实现进程间通信,因为管道属于文件,属于内核中的一种特殊数据结构,由OSweihu,所以只能使用系统调用。而共享内存存在堆栈之间,可以直接读写。双方进程若要进行通信,直接进行内存级的读写即可。
从下图可以更加直观的看到在内核照中的运行方式:
- 共享内存的提供者是操作系统。
- 共享内存缺乏访问控制,用户(进程)双方都不会影响对方,甚至不知道对方的存在,容易造成接受的数据是不完整的。
- 共享内存 = 共享内存块 + 对应的共享内存的内核数据结构
共享内存的内核数据结构
如何实现共享内存
共享内存函数
shmget函数
功能:用来创建共享内存
原型:int shmget(key_t key, size_t size, int shmflg);
参数:
key
:这个共享内存段名字
size
:共享内存大小
shmflg
:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
参数
key
要保证在系统中的唯一性,key值相同,就是同一块共享内存,保证
key
的唯一性可以使用
ftok
函数实现:
ftok函数
所需要的头文件:
#include <sys/types.h>
#include <sys/ipc.h>
函数原型:
key_t ftok(const char *pathname, int proj_id);
参数:
pathname
:指定的文件,此文件必须存在且可存取
proj_id
:计划代号(project ID),可以在[0, 255]中随机取一个数字。
函数返回值
- 成功:返回key_t值(即IPC 键值)
- 出错:-1,错误原因存于error中ftok函数把从pathname导出的信息与id的低序8位组合成一个整数IPC键(也就是shmget函数中需要的key值)。
注意:如果在使用ftok的这段时间里,pathname指向的文件或者目录被删除而且又重新创建,ftok是能够成功返回的,但是由于inode可能不同了,返回值也就不同于所希望的值,由于key值相同,使用同一块共享内存,这时候就可能会导致key值不同,双方无法共享一块内存,所以这个pathname一定要是一个不会被修改的文件路径。
对于shmget的第二个参数size,代表这个共享内存的大小,最好是页(PAGE:4096byte)的整数倍,(OS和磁盘进行I/O操作的基本单位也是4096byte),如果设置为4097byte,虽然只是多了一个字节,但是系统底层可能会创建4096 * 2的空间,即使这样,也只能使用自己申请的大小(4097byte),不能使用4096*2的空间。
第三个参数shmflg:
- 该参数用于确定共享内存属性。
- 使用上为:标志位 | 内存权限
- 标志位参数有两种:IPC_CREAT、IPC_EXCL
- 使用TPC_CREAT | 0666(内存权限):创建共享内存若底层存在,就获取,并返回,若不存在,就新建,并返回。
- 使用TPC_CREAT | IPC_EXCL | 0666(内存权限):创建共享内存若底层存在,则出错返回。若不存在,则新建,并返回。所以返回的一定是一个全新的shm.
shmat函数
功能:将共享内存段连接到进程地址空间
原型:void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
shmid
: 共享内存标识
shmaddr
:指定连接的地址
shmflg
:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
使用这个函数建立物理内存和虚拟内存的映射。
将
shmaddr
设置为
nullptr
表示让内核自己确定位置。
将
shmflg
设置为
0
为默认方式。
使用shmat函数可以简单的这样:
char* shmaddr = (char*)shmat(shmid, nullptr, 0);
- shmaddr为NULL,核心自动选择一个地址
- shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
- shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr -(shmaddr % SHMLBA)
- shmflg=SHM_RDONLY,表示连接操作用来只读共享内存
shmdt函数
功能:将共享内存段与当前进程脱离
原型:int shmdt(const void *shmaddr);
参数:
shmaddr
: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段功能
shmctl函数
功能:用于控制共享内存
原型: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删除共享内存段
shmctl常用
IPC_RMID
,用于删除共享内存空间。
代码实现
//server.cpp// 由server再共享内存里面读取信息#include<stdio.h>#include<sys/types.h>#include<sys/ipc.h>#include<sys/shm.h>#include<error.h>#include<string.h>#include<unistd.h>#include<string>#include<cstdlib>constint SIZE =4094;intmain(){key_t key =ftok(".",113);if(key <0){perror("ftok");exit(1);}int shmid =shmget(key, SIZE, IPC_CREAT |0666);if(shmid <0){perror("shmget");exit(2);}char*addr =(char*)shmat(shmid,NULL,0);int i =0;while(i <20){
i++;printf("client# %s\n", addr);fflush(stdout);memset(addr,0,sizeof(addr));// 每次写入都清空,保证共享内存中没有上一次的数据残留。sleep(1);}shmdt(addr);shmctl(shmid, IPC_RMID,NULL);return0;}
// client.cpp// 由client再共享内存里面发送信息#include<stdio.h>#include<sys/types.h>#include<sys/ipc.h>#include<sys/shm.h>#include<error.h>#include<string.h>#include<unistd.h>#include<cstdlib>constint SIZE =4094;intmain(){key_t key =ftok(".",113);if(key <0){perror("ftok");exit(1);}int shmid =shmget(key, SIZE, IPC_CREAT |0666);if(shmid <0){perror("shmget");exit(2);}char*addr =(char*)shmat(shmid,NULL,0);int i =0;while(i <15){fgets(addr,100,stdin);fflush(stdin);sleep(1);
i++;}shmdt(addr);return0;}
// makeflie.PHONY:all
all:server client
server:server.cpp
g++-o $@ $^
client:client.cpp
g++-o $@ $^.PHONY:clean
clean:
rm -f server client
上面的代码是实现一个由客户端进行在终端上进行写入信息到共享内存,服务端从共享内存读出到终端打印到屏幕上的一个过程。
😄 创作不易,你的点赞和关注都是对我莫大的鼓励,再次感谢您的观看😄
- List item
版权归原作者 七月巫山晴 所有, 如有侵权,请联系我们删除。