🔥博客主页: 我要成为C++领域大神
🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】
❤️感谢大家点赞👍收藏⭐评论✍️本博客致力于知识分享,与更多的人进行学习交流
MMAP
实现的原理
内存映射文件(Memory-Mapped Files):将文件映射到进程的用户空间,使得进程之间可以直接通过内存访问文件内容,从而实现文件内容的共享,而不需要通过传统的读写方式来操作。适用于大型数据的共享和持久化。
使用mmap完成进程间的通信,也是确定通信方向,一个负责编辑修改映射内存,一个负责打印映射内存数据,不要让两端同时修改访问映射内存,会出现数据异常。
私有映射:拷贝映射,将映射文件的数据拷贝一份到映射内存,两份数据独立无关联
共享映射:同步映射,将映射文件的数据映射一份给进程,两份数据建立sync同步机制,对一份数据的修改会立即更新给另一份。
内存映射文件机制通过mmap
函数实现。
mmap()
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数
addr
:指定映射的起始地址。通常设为NULL
,表示让操作系统选择合适的地址。
length
:指定映射的长度,即要映射的文件或对象的大小。
prot
:指定映射区域的保护方式,可以是PROT_READ
(可读)、PROT_WRITE
(可写)、PROT_EXEC
(可执行)的组合,用来控制映射区域的访问权限。
flags
:用来指定映射对象的类型及其属性。常见的标志包括MAP_SHARED
(映射可以被其他进程共享)和MAP_PRIVATE
(映射私有,对映射的修改不会影响到原文件)。
fd
:打开文件时返回的文件描述符,用来标识要映射的文件。如果映射匿名内存区域,可以传入-1
。
offset
:指定文件映射的起始位置,通常设为0
。
映射成功返回ptr,映射内存地址;失败返回MAP_FAILED关键字
munmap
取消之前通过 mmap
创建的内存映射区域。当不再需要映射到进程地址空间的文件或者匿名内存时,调用 munmap
可以释放对应的内存空间,使操作系统可以重新管理这部分内存。
int munmap(void *addr, size_t length);
addr
:要解除映射的起始地址,即mmap
返回的映射区域的起始地址。
length
:要解除映射的长度,应该与mmap
创建映射时指定的长度相同。
下面是使用mmap
对映射文件进行操作的demo程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
int main()
{
int fd=open("Map_File",O_RDWR);//打开映射文件
int size;
size=lseek(fd,0,SEEK_END);//获取文件大小
int * ptr=NULL; //这里定义成什么类型的指针都可以。定义成int类型指针一次可以操作4个字节
if((ptr=(int*)mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0))==MAP_FAILED){
perror("mmap call failed");
exit(0);
}
close(fd); //不需要通过文件描述符操作映射文件,所以可以关闭
ptr[0]=0x34333231;
munmap(ptr,size);//回收映射内存
return 0;
}
运行结果:
mmap
是否可以读写映射,取决于文件的打开权限,如果打开文件时有足够的权限,那么可以进行读写映射,映射的权限小于等于文件的权限。
例如当我们修改demo程序中打开文件的权限为 只读 ,而映射的权限为 读写。
编译运行demo程序,会提示我们 权限不足。
使用mmap机制实现进程间的通信
步骤:
写端:
1、打开映射文件
2、拓展空文件
3、共享映射
4、向映射内存写入数据
读端:
1、打开映射文件
2、共享映射
3、打印内存数据
我们在创建映射文件时需要向文件内填入数据文件才有空间,而当我们想要通信时,共享映射文件应该为空。所以需要使用ftruncate
拓展映射内存大小为消息大小。
写端代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
typedef struct {
int id;
char data[1024];
}Msg_t;
int main()
{
int fd=open("Map_File",O_RDWR);//读写打开映射文件
//拓展空文件,使其有封装数据的空间大小,但是内容为空
ftruncate(fd,sizeof(Msg_t));
Msg_t* pMsg=NULL;
if((pMsg=(Msg_t*)mmap(NULL,sizeof(Msg_t),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0))==MAP_FAILED){
perror("mmap call failed");
exit(0);
}
close(fd); //不需要通过文件描述符操作映射文件,所以可以关闭
pMsg->id=1;
bzero(pMsg->data,sizeof(pMsg->data));
while(1){//持续向映射内存写入数据
++(pMsg->id);
sprintf(pMsg->data,"test message,msg id=%d\n",pMsg->id);
sleep(1);
}
munmap(pMsg,sizeof(Msg_t));//回收映射内存
return 0;
}
读端代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
typedef struct {
int id;
char data[1024];
}Msg_t;
int main()
{
int fd=open("Map_File",O_RDWR);//读写打开映射文件
Msg_t* pMsg=NULL;
if((pMsg=(Msg_t*)mmap(NULL,sizeof(Msg_t),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0))==MAP_FAILED){
perror("mmap call failed");
exit(0);
}
close(fd); //不需要通过文件描述符操作映射文件,所以可以关闭
while(1){//持续从映射内存读取数据
printf("msg id=%d message:%s",pMsg->id,pMsg->data);
sleep(1);
}
munmap(pMsg,sizeof(Msg_t));//回收映射内存
return 0;
}
运行结果:
映射文件的大小与映射内存大小的关系
在进行映射时,一般要看映射文件大小,如果映射内存与映射文件大小不符,在访问内存时产生总线错误,系统向进程发送SIGBUS,杀死进程。
当我们修改demo程序 中映射内存大小为1024时,使用指针对映射文件进行修改,发现只有映射5个字节能被修改(还有字符串结束标记'\0')。即使映射内存为1024,编译运行后,映射文件仍为5个字节。
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
int main()
{
int fd=open("Map_File",O_RDWR);//打开映射文件
int size;
size=lseek(fd,0,SEEK_END);//获取文件大小
char * ptr=NULL; //这里定义成什么类型的指针都可以。定义成int类型指针一次可以操作4个字节
if((ptr=(char*)mmap(NULL,1024,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0))==MAP_FAILED){
perror("mmap call failed");
exit(0);
}
close(fd); //不需要通过文件描述符操作映射文件,所以可以关闭
ptr[0]='Q';
ptr[1]='W';
ptr[2]='E';
ptr[3]='R';
ptr[4]='T';
ptr[5]='Y';
munmap(ptr,size);//回收映射内存
return 0;
}
mmap
是如何实现同步的
DMA缓存引擎预读硬盘中的数据,将数据拷贝到内核缓冲区中,内核缓冲区将数据映射到了用户空间中的映射内存。而映射内存是虚拟地址,当程序尝试通过映射内存修改数据时,会触发缺页中断,重新将该页加载到了物理内存。页被修改之后标记为脏页,重回硬盘。
mmap
相较于read
的优势
mmap
可以直接将文件内容映射到进程的虚拟内存空间,避免了使用read
时的内核空间到用户空间的数据拷贝,从而减少了拷贝开销。mmap
使用的内存页可以与内核页面缓存共享,这意味着如果多个进程映射同一个文件,它们可以共享相同的物理内存页,从而节省内存和提高性能。
在网络编程中mmap也同样适用
ZERO-COPY零拷贝
零拷贝技术在网络通信时可以减少拷贝开销
在数据传输过程中,尽可能减少或完全消除数据的拷贝操作
零拷贝的实现方式
使用mmap
和SendFile
函数来减少拷贝次数
SendFile
使得拷贝次数减少了两次。
sendfile
用于在两个文件描述符之间直接传输数据,而无需将数据从内核空间复制到用户空间。
sendfile
的适用范围
网络数据传输:在网络编程中,sendfile
可以用于将文件内容直接发送到套接字,这样可以避免将文件内容从内核缓冲区复制到用户空间的缓冲区中,从而减少系统调用和内存复制操作。
文件备份和同步:在文件系统操作中,sendfile
可以用于文件备份和同步操作,将文件内容直接复制到另一个文件,而无需经过用户空间,节省了额外的内存和CPU资源。
HTTP服务器:在Web服务器开发中,sendfile
可以用于将静态文件(如图片、视频)直接发送给客户端,提高了服务器的响应速度和吞吐量。
有效减少了拷贝开销。