【操作系统】MMAP内存映射|零拷贝

 🔥博客主页: 我要成为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零拷贝

零拷贝技术在网络通信时可以减少拷贝开销

在数据传输过程中,尽可能减少或完全消除数据的拷贝操作

零拷贝的实现方式

使用mmapSendFile函数来减少拷贝次数

SendFile使得拷贝次数减少了两次。

sendfile 用于在两个文件描述符之间直接传输数据,而无需将数据从内核空间复制到用户空间。

sendfile适用范围

网络数据传输:在网络编程中,sendfile 可以用于将文件内容直接发送到套接字,这样可以避免将文件内容从内核缓冲区复制到用户空间的缓冲区中,从而减少系统调用和内存复制操作。

文件备份和同步:在文件系统操作中,sendfile 可以用于文件备份和同步操作,将文件内容直接复制到另一个文件,而无需经过用户空间,节省了额外的内存和CPU资源。

HTTP服务器:在Web服务器开发中,sendfile 可以用于将静态文件(如图片、视频)直接发送给客户端,提高了服务器的响应速度和吞吐量。

有效减少了拷贝开销。

  • 32
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值