程序员求职经验分享与学习资料整理平台

网站首页 > 文章精选 正文

内存映射文件(内存映射文件跨主机传输)

balukai 2025-03-14 14:55:47 文章精选 8 ℃

API

  • Linux: mmap
  • Windows: CreateFileMapping
  • Java: FileChannel.map

原理解读

分页机制

  • 32位系统下,每个进程都能看到独立的4GB虚拟内存空间, 每个进程都有一套对应的页目录和页表映射物理内存,页目录和页表并不会映射全部的4GB物理内存,只会映射已经分配的物理内存. 这就是所谓的进程间的内存隔离和内存资源分配单位. 而进程切换就是切换对应的页目录和页表以及对应的寄存器.
  • 而内核空间(高2GB)是所有进程都需要公用的, 所以页目录和页面也有公用部分, 这就达到了多进程共享虚拟内存空间的效果.

内存映射文件

  1. 分配物理页(物理内存以页为单位,windows下是4kb)
  2. 把已分配的物理页映射到公用的页目录和页表下
  3. (可选) 把磁盘文件的数据读取到对应已映射好的共享虚拟内存, 或置换虚拟内存的数据到磁盘文件

基于以上特性的实际应用

进程间通信

以Windows为例

//写进程
void demo() {
        HANDLE hFile = CreateFile(TEXT(R"(D:\tmp\test.txt)"), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
        HANDLE hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 1024, TEXT("testFileMapping"));//1K的内存文件
        //HANDLE hFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 1024, TEXT("testFileMapping"));//无文件
        if (hFileMapping == NULL) {
                throw Exception(0,"创建文件映射失败");
        }
        LPVOID p =  MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, 1024);
        if (p == NULL) {
                throw Exception(0, "创建文件映射失败");
        }
        memcpy(p, "hello", 5);
        
        UnmapViewOfFile(p);
        CloseHandle(hFileMapping);
        CloseHandle(hFile);
}

//读进程
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd) {
    HANDLE hFileMapping = OpenFileMapping(FILE_MAP_ALL_ACCESS, TRUE, TEXT("testFileMapping"));//1K的内存文件
    if (hFileMapping == NULL) {
        return 0;
    }
    LPVOID p = MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, 1024);
    if (p == NULL) {
        return 0;
    }
    char* buff[1024] = {0};
    memcpy(buff, p, 5);
    MessageBoxA(NULL, (const char*)buff, "title", MB_OK);

    UnmapViewOfFile(p);
    CloseHandle(hFileMapping);
}

读取大文件

内存映射文件,使程序可以像操作内存一样直接访问文件内容,而不需要显式地调用read或write方法。这种操作方式不仅可以简化代码,还能显著提升文件I/O的性能,特别是在处理大文件或频繁访问文件内容的场景中. 以Java为例子

public static void mmap() throws IOException {
    RandomAccessFile file = new RandomAccessFile("example.txt","rw");
    FileChannel channel = file.getChannel();
    //直接把文件映射到内容中,从而直接修改/读取文件
    MappedByteBuffer buffer =channel .map(FileChannel.MapMode.READ_WRITE, 0, file.length());
    //读
    char c = buffer.getChar(0);
    //写
    buffer.putChar(0,'a');
    channel.close();
    file.close();
}

零拷贝

以如上场景为例, 从本地磁盘上读取一个文件,然后通过socket连接,发送给客户端
操作系统首先需要将磁盘中的文件读取到内核态的页缓存,然后加载到用户态的应用程序中,这样服务端应用程序才能拿到文件的内容。而服务端应用程序往客户端发送文件内容时,也需要先将文件写入内核态的Socket缓冲区,然后才能通过Socket往客户端发送消息。整个过程中有四次文件拷贝。

  • 所谓零拷贝,主要任务就是要避免这个过程中的CPU拷贝,让CPU从这些繁重毛时的拷贝任务中解脱出来。这其中,硬件与页缓存之间的交互过程,已经可以通过DMA(直接存储器存储)进行,不需要CPU参与,所以,零零拷贝的重点就在于减少内核态与用户态之间的文件拷贝
  • mmap: 内存映射文件,避免用户态与内核态之间的数据拷贝
  • sendfile: 它允许内核态直接将文件数据直接发送到套接字,无需在用户空间和内核空间之间复制数据
最近发表
标签列表