目录
Linux的通信方式之共享内存,总共有两种映射的方法。第一种就是内存映射,通过mmap将文件或者其他对象映射到内存中。还有一种就是共享内存,把物理内存映射到虚拟内存中,进程间便可以通过这块共享的内存进行通信。
【内存映射】
1.内存映射的概述
内存映射I/O (Memory-mapped lO)使一个磁盘文件与存储空间中的一个缓冲区相映射
当将一个磁盘文件通过内存映射映射到内存(进程的地址空间)之后,文件磁盘地址和进程的虚拟地址空间会存在对映关系,于是进程就可以听过指针的方式来对这一段内存进行读写了。
于是当从这段内存中取数据,就相当于读文件中的相应字节。于此类似,将数据存入缓冲区,则相应的字节就自动写入文件
看着似乎和管道很相似,都是通过一个媒介往媒介中间写入或者读取,看着像,但是本质还是有区别。无名管道本质就是一块内存,通过文件描述符往里面读写。有名管道就是一个抽象出来的特殊文件,使用文件IO进行读写。内存映射是把磁盘中的文件映射到内存当中去,也就是说,文件本来就已经是存在得了。
还有两点不同的是,管道是半双工的,只能在同一时刻有一个流向,而内存映射是全双工的,也就是同一时刻可读可写,另外,管道读取读取到数据后,立刻从管道中删除,但是内存映射由于是把磁盘文件映射出来,所以数据还是存在的。
关于这个内存映射的一个疑问:
磁盘文件是存放在硬盘的,那么为什么要叫做内存映射呢。因为这个磁盘文件本质是物理内存抽象在磁盘生成的零大小的文件,其实本质还是内存而不是磁盘文件。
2.内存映射API
2.1建立文件和内存的映射
#include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
功能
建立磁盘文件和内存之间的映射
参数
addr:地址,填NULL(因为这个是用来手动分配文件映射在内存的地址的,NULL自动分配)
len:申请的映射区长度
prot:权限
PROT_READ:可读
PROT_WRITE:可写
flags:标志位
MAP_SHARED:共享的,对映射区的修改会修改源文件
MAP_PRIVATE:私有的,对映射区修改不会修改源文件
fd:文件描述符,需要打开一个文件(也就是被映射的磁盘文件)
offset:指定一个偏移位置,从该位置开始映射(一般为0)
返回值
成功:返回内存中映射区的首地址(有了这个地址才可以进行操作)
失败:-1
2.磁盘文件大小的扩展
问什么要扩展文件的大小呢?
假如一开始我们想要选择一个磁盘文件来进行映射,参数使用了create选项,这个参数的作用是,如果文件存在,清空文件,如果文件不存在,创建一个空的文件。不管是哪一个情况都会导致文件是空的,如果文件是空的,映射到内存中的空间也为0,那么没有任何意义。
#include <unistd.h>
int truncate(const char *path, off_t length);
功能
给指定的文件扩展指定的长度
参数
path:扩展的文件路径
length:扩展的长度
3.断开内存映射
#include <sys/mman.h>
int munmap(void *addr, size_t len);
功能
断开当前进程中的内存映射
参数
addr:映射区首地址(mmap返回值)
len:映射区的长度(扩展文件大小的时候设置的值)
4.建立内存映射进行读写
其实建立内存映射后,两个进程的本质区别就是:一个进程是用来从映射的内存中读取数据,一个是从映射的内存中写入数据。
使用条件编译和宏合二为一
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
int main(int argc, char const *argv[])
{
//建立内存映射
//打开文件
int fd = open("temp.txt",O_RDWR|O_CREAT,0666);
//填充文件
truncate("temp.txt",16);
//建立内存映射
char *add = (char *)mmap(NULL, 16, PROT_READ|PROT_WRITE, MAP_SHARED,fd, 0);
#ifdef WRITE
strcpy(add,"hello mem");
#endif
#ifdef READ
printf("read文件读取到的内容为%s\n",add);
#endif
//断开映射
munmap(add,16);
return 0;
}
【共享内存】
内存映射依赖的是磁盘文件,映射到内存当中,通过操作内存来操作文件。不同进程也可以通过读写文件来进行通信
共享内存映射的就不是磁盘文件了,而是直接映射物理内存
1.共享内存的概述
共享内存依靠的是物理内存,通过物理内存映射到虚拟内存当中
共享内存具有以下特点
1.共享内存是进程间通信最快的一种方式,当一个进程向内存中写入数据之后,其他共享这块内存的进程可以立即看到这些内容
2.使用共享内存需要注意不同进程对这块内存访问的互斥关系,比如一个进程在往这里面写,那么其他进程此时就不应该有其他操作。(可以考虑互斥锁的使用)
2.共享内存的API
2.1获取唯一标识符IPC键值
这一块是和消息队列中IPC键值的获取是一样的
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
功能
获取项目相关唯一Key值
参数
pathname:路径名
proj_id:项目ID(非0整数)
返回值
成功返回键值,失败-1
pathname这个参数可以随便设置的,因为Key值是通过所设置文件信息以及proj_id所合成Key值的,而proj_id这个数也是可以随便设置的。但是由于它只有8bit,也就是0-255.
2.2通过IPC键值创建或打开共享内存区
这一块和消息队列是很相似的(记住使用IPC键值的地方只有消息队列、共享内存以及信号量),都是获取唯一IPC键值,然后再通过这个键值创建或者打开需要操作的进程间通信方法。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
功能
创建或者打开一块共享内存区
参数
key:唯一IPC键值
size:共享存储段的字节
shmflg:标识函数的行为以及共享内存的权限
shmflg参数
IPC_CREAT:共享内存区不存在创建
IPC_EXCL:如果已经存在返回失败
位或权限位:位或权限位可以设置共享内部的权限,格式和open的mode_t一样
返回值
成功:共享内存标识符
失败:-1
2.1如何通过命令查看共享内存
ipcs -m 查看共享内存
ipcrm -m 标识符 删除共享内存区
2.3创建虚拟内存和存在的共享内存区的映射
在使用shmget生成共享内存区后,需要建立每个进程和共享内存区的映射关系。就像消息队列中使用IPC键值生成消息队列后,也需要对消息队列进行读写操作。
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能
将共享内存映射到进程中的内存
参数
shmid:共享内存标识符
shmaddr:共享内存在进程内存中映射的地址,使用NULL(推荐)系统自动分配
shmflg:共享内存段的访问权限和映射条件
0:具有可读可写的权限
SHM_RDONLY:只读
SHM_RND:(shmaddr非空时才有效):没有指定SHM_RND则此段连接到shmaddr所指定的地址上(shmaddr必需页对齐)。指定了SHM_RND则此段连接到shmaddr- shmaddr%SHMLBA所表示的地址
2.4解除当前进程映射
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
功能
断开当前进程的共享内存
参数
shmaddr:共享内存映射的地址
返回值
成功:0
失败:-1
2.5共享内存的控制
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能
共享内存空间控制
参数
shmid:共享内存标识符
cmd:函数功能控制
IPC_RMID:删除。
IPC_SET:设置shmid_ds参数。IPC_STAT:保存shmid_ds参数。
SHM_LOCK:锁定共享内存段(超级用户)。SHM_uNLOCK:解锁共享内存段。
buf:shmid_ds数据类型结构体,用来存放修改共享内存的结构体
3.创建并从共享内存中读写数据
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main(int argc, char const *argv[])
{
//获取唯一Key值
key_t key = ftok("/",2023);
//生成物理内存,获得物理内存唯一标识符
int shm_id = shmget(key, 16, IPC_CREAT | 0666);
//虚拟内存映射物理内存
char *add = (char *)shmat(shm_id, NULL, 0);
//操作虚拟内存操作物理内存
#ifdef WRITE
strcpy(add,"Hello!!!!!!!!!!!\n");
#endif // WRITE
#ifdef READ
printf("读到的数据为%s\n",add);
#endif // READ
//断开映射
shmdt(add);
return 0;
}