美文网首页
内存映射 mmap 是什么

内存映射 mmap 是什么

作者: ForestSen | 来源:发表于2020-07-09 08:35 被阅读0次

    介绍

    在内存映射的过程中,并没有实际的数据拷贝,文件没有被载入内存,只是逻辑上被放入了内存,具体到代码,就是建立并初始化了相关的数据结构(struct address_space),这个过程有系统调用mmap()实现,所以建立内存映射的效率很高。

    既然建立内存映射没有进行实际的数据拷贝,那么进程又怎么能最终直接通过内存操作访问到硬盘上的文件呢?那就要看内存映射之后的几个相关的过程了。

    mmap()会返回一个指针ptr,它指向进程逻辑地址空间中的一个地址,这样以后,进程无需再调用read或write对文件进行读写,而只需要通过ptr就能够操作文件。但是ptr所指向的是一个逻辑地址,要操作其中的数据,必须通过MMU将逻辑地址转换成物理地址,这个过程与内存映射无关。

    前面讲过,建立内存映射并没有实际拷贝数据,这时,MMU在地址映射表中是无法找到与ptr相对应的物理地址的,也就是MMU失败,将产生一个缺页中断,缺页中断的中断响应函数会在swap中寻找相对应的页面,如果找不到(也就是该文件从来没有被读入内存的情况),则会通过mmap()建立的映射关系,从硬盘上将文件读取到物理内存中,如图1中过程3所示。这个过程与内存映射无关。

    如果在拷贝数据时,发现物理内存不够用,则会通过虚拟内存机制(swap)将暂时不用的物理页面交换到硬盘上,这个过程也与内存映射无关。

    mmap内存映射的实现过程

    1. 进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域
    2. 调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系
    3. 进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝

    适合的场景

    • 您有一个很大的文件,其内容您想要随机访问一个或多个时间
    • 您有一个小文件,它的内容您想要立即读入内存并经常访问。这种技术最适合那些大小不超过几个虚拟内存页的文件。(页是地址空间的最小单位,虚拟页和物理页的大小是一样的,通常为4KB。)
    • 您需要在内存中缓存文件的特定部分。文件映射消除了缓存数据的需要,这使得系统磁盘缓存中的其他数据空间更大
      当随机访问一个非常大的文件时,通常最好只映射文件的一小部分。映射大文件的问题是文件会消耗活动内存。如果文件足够大,系统可能会被迫将其他部分的内存分页以加载文件。将多个文件映射到内存中会使这个问题更加复杂。

    不适合的场景

    • 您希望从开始到结束的顺序从头到尾读取一个文件
    • 这个文件有几百兆字节或者更大。将大文件映射到内存中会快速地填充内存,并可能导致分页,这将抵消首先映射文件的好处。对于大型顺序读取操作,禁用磁盘缓存并将文件读入一个小内存缓冲区
    • 该文件大于可用的连续虚拟内存地址空间。对于64位应用程序来说,这不是什么问题,但是对于32位应用程序来说,这是一个问题
    • 该文件位于可移动驱动器上
    • 该文件位于网络驱动器上

    示例代码

    //
    //  ViewController.m
    //  TestCode
    //
    //  Created by zhangdasen on 2020/5/24.
    //  Copyright © 2020 zhangdasen. All rights reserved.
    //
    
    #import "ViewController.h"
    #import <sys/mman.h>
    #import <sys/stat.h>
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"test.data"];
        NSLog(@"path: %@", path);
        NSString *str = @"test str2";
        [str writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
        
        ProcessFile(path.UTF8String);
        NSString *result = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
        NSLog(@"result:%@", result);
    }
    
    
    int MapFile(const char * inPathName, void ** outDataPtr, size_t * outDataLength, size_t appendSize)
    {
        int outError;
        int fileDescriptor;
        struct stat statInfo;
        
        // Return safe values on error.
        outError = 0;
        *outDataPtr = NULL;
        *outDataLength = 0;
        
        // Open the file.
        fileDescriptor = open( inPathName, O_RDWR, 0 );
        if( fileDescriptor < 0 )
        {
            outError = errno;
        }
        else
        {
            // We now know the file exists. Retrieve the file size.
            if( fstat( fileDescriptor, &statInfo ) != 0 )
            {
                outError = errno;
            }
            else
            {
                ftruncate(fileDescriptor, statInfo.st_size + appendSize);
                fsync(fileDescriptor);
                *outDataPtr = mmap(NULL,
                                   statInfo.st_size + appendSize,
                                   PROT_READ|PROT_WRITE,
                                   MAP_FILE|MAP_SHARED,
                                   fileDescriptor,
                                   0);
                if( *outDataPtr == MAP_FAILED )
                {
                    outError = errno;
                }
                else
                {
                    // On success, return the size of the mapped file.
                    *outDataLength = statInfo.st_size;
                }
            }
            
            // Now close the file. The kernel doesn’t use our file descriptor.
            close( fileDescriptor );
        }
        
        return outError;
    }
    
    
    void ProcessFile(const char * inPathName)
    {
        size_t dataLength;
        void * dataPtr;
        char *appendStr = " append_key2";
        int appendSize = (int)strlen(appendStr);
        if( MapFile(inPathName, &dataPtr, &dataLength, appendSize) == 0) {
            dataPtr = dataPtr + dataLength;
            memcpy(dataPtr, appendStr, appendSize);
            // Unmap files
            munmap(dataPtr, appendSize + dataLength);
        }
    }
    
    @end
    

    相关文章

      网友评论

          本文标题:内存映射 mmap 是什么

          本文链接:https://www.haomeiwen.com/subject/xhodkqtx.html