内存映射文件_内存映射文件原理探索 - CSDN
精华内容
参与话题
  • 内存映射文件(专门读写大文件)

    万次阅读 2019-03-04 18:28:46
     文件操作是应用程序最为基本的功能之一,Win32 API和MFC均提供有支持文件处理的函数和类,常用的有Win32 API的CreateFile()、WriteFile()、ReadFile()和MFC提供的CFile类等。一般来说,以上这些函数可以满足大多数...

    引言

      文件操作是应用程序最为基本的功能之一,Win32 API和MFC均提供有支持文件处理的函数和类,常用的有Win32 API的CreateFile()、WriteFile()、ReadFile()和MFC提供的CFile类等。一般来说,以上这些函数可以满足大多数场合的要求,但是对于某些特殊应用领域所需要的动辄几十GB、几百GB、乃至几TB的海量存储,再以通常的文件处理方法进行处理显然是行不通的。目前,对于上述这种大文件的操作一般是以内存映射文件的方式来加以处理的,本文下面将针对这种Windows核心编程技术展开讨论。

      内存映射文件概述

      内存文件映射也是Windows的一种内存管理方法,提供了一个统一的内存管理特征,使应用程序可以通过内存指针对磁盘上的文件进行访问,其过程就如同对加载了文件的内存的访问。通过文件映射这种使磁盘文件的全部或部分内容与进程虚拟地址空间的某个区域建立映射关联的能力,可以直接对被映射的文件进行访问,而不必执行文件I/O操作也无需对文件内容进行缓冲处理。内存文件映射的这种特性是非常适合于用来管理大尺寸文件的。

      在使用内存映射文件进行I/O处理时,系统对数据的传输按页面来进行。至于内部的所有内存页面则是由虚拟内存管理器来负责管理,由其来决定内存页面何时被分页到磁盘,哪些页面应该被释放以便为其它进程提供空闲空间,以及每个进程可以拥有超出实际分配物理内存之外的多少个页面空间等等。由于虚拟内存管理器是以一种统一的方式来处理所有磁盘I/O的(以页面为单位对内存数据进行读写),因此这种优化使其有能力以足够快的速度来处理内存操作。

      使用内存映射文件时所进行的任何实际I/O交互都是在内存中进行并以标准的内存地址形式来访问。磁盘的周期性分页也是由操作系统在后台隐蔽实现的,对应用程序而言是完全透明的。内存映射文件的这种特性在进行大文件的磁盘事务操作时将获得很高的效益。

      需要说明的是,在系统的正常的分页操作过程中,内存映射文件并非一成不变的,它将被定期更新。如果系统要使用的页面目前正被某个内存映射文件所占用,系统将释放此页面,如果页面数据尚未保存,系统将在释放页面之前自动完成页面数据到磁盘的写入。

      对于使用页虚拟存储管理的Windows操作系统,内存映射文件是其内部已有的内存管理组件的一个扩充。由可执行代码页面和数据页面组成的应用程序可根据需要由操作系统来将这些页面换进或换出内存。如果内存中的某个页面不再需要,操作系统将撤消此页面原拥用者对它的控制权,并释放该页面以供其它进程使用。只有在该页面再次成为需求页面时,才会从磁盘上的可执行文件重新读入内存。同样地,当一个进程初始化启动时,内存的页面将用来存储该应用程序的静态、动态数据,一旦对它们的操作被提交,这些页面也将被备份至系统的页面文件,这与可执行文件被用来备份执行代码页面的过程是很类似的。图1展示了代码页面和数据页面在磁盘存储器上的备份过程:


    图1 进程的代码页、数据页在磁盘存储器上的备份

      显然,如果可以采取同一种方式来处理代码和数据页面,无疑将会提高程序的执行效率,而内存映射文件的使用恰恰可以满足此需求。

    对大文件的管理

      内存映射文件对象在关闭对象之前并没有必要撤销内存映射文件的所有视图。在对象被释放之前,所有的脏页面将自动写入磁盘。通过 CloseHandle()关闭内存映射文件对象,只是释放该对象,如果内存映射文件代表的是磁盘文件,那么还需要调用标准文件I/O函数来将其关闭。在处理大文件处理时,内存映射文件将表示出卓越的优势,只需要消耗极少的物理资源,对系统的影响微乎其微。下面先给出内存映射文件的一般编程流程框图:

    图2 使用内存映射文件的一般流程

      而在某些特殊行业,经常要面对十几GB乃至几十GB容量的巨型文件,而一个32位进程所拥有的虚拟地址空间只有232 = 4GB,显然不能一次将文件映像全部映射进来。对于这种情况只能依次将大文件的各个部分映射到进程中的一个较小的地址空间。这需要对上面的一般流程进行适当的更改:

      1)映射文件开头的映像。

      2)对该映像进行访问。

      3)取消此映像

      4)映射一个从文件中的一个更深的位移开始的新映像。

      5)重复步骤2,直到访问完全部的文件数据。

      下面给出一段根据此描述而写出的对大于4GB的文件的处理代码:
     

     

     

     


      在本例中,首先通过GetFileSize()得到被处理文件长度(64位)的高32位和低32位值。然后在映射过程中设定每次映射的块大小为 1000倍的分配粒度,如果文件长度小于1000倍的分配粒度时则将块大小设置为文件的实际长度。在处理过程中由映射、访问、撤消映射构成了一个循环处理。其中,每处理完一个文件块后都通过关闭文件映射对象来对每个文件块进行整理。CreateFileMapping()、 MapViewOfFile()等函数是专门用来进行内存文件映射处理用的。

    下面分别对这些关键函数进行说明:

      1)CreateFile():CreateFile()函数是一个用途非常广泛的函数,在这里的用法并没有什么特殊的地方,但有几点需要注意:一是访问模式参数dwDesiredAccess。该参数设置了对文件内核对象的访问类型,其允许设置的权限可以为读权限GENERIC_READ、写权限GENERIC_WRITE、读写权限GENERIC_READ | GENERIC_WRITE和设备查询权限0。在使用映射文件时,只能打开那些具有可读访问权限的文件,即只能应用GENERIC_READ和 GENERIC_READ | GENERIC_WRITE这两种组合;另一点需要注意的是共享模式参数dwShareMode。该参数定义了对文件内核对象的共享方式,其可能的设置为 FILE_SHARE_READ、FILE_SHARE_WRITE和0,并可对其组合使用。其中,设置为0时不允许共享对象;FILE_SHARE_READ和FILE_SHARE_WRITE分别为在要求只读、只写访问的情况下才允许对象的共享。

      由于通过内存映射文件可以在多个进程间共享数据,因此在进行这种应用时应当考虑dwShareMode参数设置对运行结果的影响。

      2)CreateFileMapping():该函数的作用是创建一个文件映射内核对象,以告知系统文件映射对象需要多大的物理存储器。创建内存映射文件对象对系统资源几乎没有什么影响,也不会影响进程的虚拟地址空间。除了需要用来表示该对象的内部资源之外通常并不用为其分配虚拟内存,但是如果内存映射文件对象是作共享内存之用的话,就要在创建对象时由系统为内存映射文件的使用在系统页文件中保留足够的空间。

      函数第一个参数hFile为标识要映射到进程的地址空间的文件的句柄。虽然由于内存映射文件的物理存储器是来自于磁盘上的文件,而非系统的页文件,使创建内存映射文件就像保留一个地址空间区域并将物理存储器提交给该区域一样。第二个参数为指向文件映射内核对象的SECURITY_ATTRIBUTES 结构的指针,由此来决定子进程能否继承得到返回的句柄。通常为其传递NULL值,以默认的安全属性来禁止返回句柄的被继承。

      接下来的参数用于文件被映射后设定文件映像的保护属性。其可能的取值为PAGE_READONLY、PAGE_READWRITE和 PAGE_WRITECOPY。虽然在创建文件映射对象时,系统并不为其保留地址空间区域,也不将文件的存储器映射到该区域。但是,在系统将存储器映射到进程的地址空间中去时,系统必须确切知道应赋予物理存储器页面的保护属性。在设置保护属性时,必须与用CreateFile()函数打开文件时所指定的访问标识相匹配,否则将导致CreateFileMapping()的执行失败。因此这里设置PAGE_READWRITE属性。除了上述三个页面保护属性外,还有4个区(Section)保护属性也可以一起组合使用:
     

     

    区保护属性 说明
    SEC_COMMIT 为区中的所有页面在内存中或磁盘页面文件中分配物理存储器
    SEC_IMAGE 告知系统,映射的文件是一个可移植的EXE文件映像
    SEC_NOCACHE 告知系统,未将文件的任何内存映射文件放入高速缓存,多供硬件设备驱动程序开发人员使用
    SEC_RESERVE 对一个区的所有页面进行保留而不分配物理存储器

     

     

    [cpp] view plain copy print?

    1. // 选择文件  
    2. CFileDialog fileDlg(TRUE, "*.txt", "*.txt", NULL, "文本文件 (*.txt)|*.txt||", this);  
    3. fileDlg.m_ofn.Flags |= OFN_FILEMUSTEXIST;  
    4. fileDlg.m_ofn.lpstrTitle = "通过内存映射文件读取数据";  
    5. if (fileDlg.DoModal() == IDOK)  
    6. {  
    7.  // 创建文件对象  
    8.  HANDLE hFile = CreateFile(fileDlg.GetPathName(), GENERIC_READ | GENERIC_WRITE,  
    9.    0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);  
    10.  if (hFile == INVALID_HANDLE_VALUE)  
    11.  {  
    12.   TRACE("创建文件对象失败,错误代码:%drn", GetLastError());  
    13.   return;  
    14.  }  
    15.  // 创建文件映射对象  
    16.  HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);  
    17.  if (hFileMap == NULL)  
    18.  {  
    19.   TRACE("创建文件映射对象失败,错误代码:%drn", GetLastError());  
    20.   return;  
    21.  }  
    22.  // 得到系统分配粒度  
    23.  SYSTEM_INFO SysInfo;  
    24.  GetSystemInfo(&SysInfo);  
    25.  DWORD dwGran = SysInfo.dwAllocationGranularity;  
    26.  // 得到文件尺寸  
    27.  DWORD dwFileSizeHigh;  
    28.  __int64 qwFileSize = GetFileSize(hFile, &dwFileSizeHigh);  
    29.  qwFileSize |= (((__int64)dwFileSizeHigh) << 32);  
    30.  // 关闭文件对象  
    31.  CloseHandle(hFile);  
    32.  // 偏移地址   
    33.  __int64 qwFileOffset = 0;  
    34.  // 块大小  
    35.  DWORD dwBlockBytes = 1000 * dwGran;  
    36.  if (qwFileSize < 1000 * dwGran)  
    37.   dwBlockBytes = (DWORD)qwFileSize;  
    38.   while (qwFileSize> 0)  
    39.   {  
    40.    // 映射视图  
    41.    LPBYTE lpbMapAddress = (LPBYTE)MapViewOfFile(hFileMap,FILE_MAP_ALL_ACCESS,   
    42.       (DWORD)(qwFileOffset >> 32), (DWORD)(qwFileOffset & 0xFFFFFFFF),  
    43.       dwBlockBytes);  
    44.    if (lpbMapAddress == NULL)  
    45.    {  
    46.     TRACE("映射文件映射失败,错误代码:%drn", GetLastError());  
    47.     return;  
    48.    }  
    49.    // 对映射的视图进行访问  
    50.    for(DWORD i = 0; i < dwBlockBytes; i++)  
    51.     BYTE temp = *(lpbMapAddress + i);  
    52.     // 撤消文件映像  
    53.     UnmapViewOfFile(lpbMapAddress);  
    54.     // 修正参数  
    55.     qwFileOffset += dwBlockBytes;  
    56.     qwFileSize -= dwBlockBytes;  
    57.   }  
    58.   // 关闭文件映射对象句柄  
    59.   CloseHandle(hFileMap);  
    60.   AfxMessageBox("成功完成对文件的访问");  
    61. }  

    // 选择文件
    CFileDialog fileDlg(TRUE, "*.txt", "*.txt", NULL, "文本文件 (*.txt)|*.txt||", this);
    fileDlg.m_ofn.Flags |= OFN_FILEMUSTEXIST;
    fileDlg.m_ofn.lpstrTitle = "通过内存映射文件读取数据";
    if (fileDlg.DoModal() == IDOK)
    {
     // 创建文件对象
     HANDLE hFile = CreateFile(fileDlg.GetPathName(), GENERIC_READ | GENERIC_WRITE,
       0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
     if (hFile == INVALID_HANDLE_VALUE)
     {
      TRACE("创建文件对象失败,错误代码:%drn", GetLastError());
      return;
     }
     // 创建文件映射对象
     HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
     if (hFileMap == NULL)
     {
      TRACE("创建文件映射对象失败,错误代码:%drn", GetLastError());
      return;
     }
     // 得到系统分配粒度
     SYSTEM_INFO SysInfo;
     GetSystemInfo(&SysInfo);
     DWORD dwGran = SysInfo.dwAllocationGranularity;
     // 得到文件尺寸
     DWORD dwFileSizeHigh;
     __int64 qwFileSize = GetFileSize(hFile, &dwFileSizeHigh);
     qwFileSize |= (((__int64)dwFileSizeHigh) << 32);
     // 关闭文件对象
     CloseHandle(hFile);
     // 偏移地址 
     __int64 qwFileOffset = 0;
     // 块大小
     DWORD dwBlockBytes = 1000 * dwGran;
     if (qwFileSize < 1000 * dwGran)
      dwBlockBytes = (DWORD)qwFileSize;
      while (qwFileSize> 0)
      {
       // 映射视图
       LPBYTE lpbMapAddress = (LPBYTE)MapViewOfFile(hFileMap,FILE_MAP_ALL_ACCESS, 
          (DWORD)(qwFileOffset >> 32), (DWORD)(qwFileOffset & 0xFFFFFFFF),
          dwBlockBytes);
       if (lpbMapAddress == NULL)
       {
        TRACE("映射文件映射失败,错误代码:%drn", GetLastError());
        return;
       }
       // 对映射的视图进行访问
       for(DWORD i = 0; i < dwBlockBytes; i++)
        BYTE temp = *(lpbMapAddress + i);
        // 撤消文件映像
        UnmapViewOfFile(lpbMapAddress);
        // 修正参数
        qwFileOffset += dwBlockBytes;
        qwFileSize -= dwBlockBytes;
      }
      // 关闭文件映射对象句柄
      CloseHandle(hFileMap);
      AfxMessageBox("成功完成对文件的访问");
    }



      后面的两个参数指定了要创建的文件映射对象的最大字节数的高32位值和低32位值,实际也就设定了文件的最大字节数(最大可以处理16EB的文件)。这两个参数可以满足确保文件映射对象能够得到足够的物理存储器这一基本条件。在参数设置的大小小于文件实际大小时,系统将从文件映射指定的字节数。这里将其设置为0,将使所创建的文件映射对象将为文件的当前大小,以上两种情况均无法改变文件的大小。如果设置的参数大于文件的实际大小,系统将会在 CreateFileMapping()函数返回前扩展该文件。需要指出的是,文件映射对象的大小是静态的,一旦创建完毕后将无法更改。如果设置的文件映射对象尺寸偏小将导致无法对文件进行全面的访问。

      在本节开始也曾提到过,创建文件映射对象是不需要花费什么系统资源的,因此遵循"宁多勿缺"的原则,一般应将文件映射对象的大小设置为文件大小的相同值。函数最后的参数将可以为映射对象命名。如果想打开一个已存在的文件映射对象,该对象必须要命名。对该名字字符串的要求仅限于未被其它对象使用过的名字即可。

      CreateFileMapping()在成功执行后将返回一个指向文件映射对象的句柄。如果对一个已经存在的文件映射对象调用了 CreateFileMapping()函数,进程将得到一个指向现有映射对象的句柄。通过调用GetLastError()可以得到返回值 ERROR_ALREADY_EXIST,由此可以判断当前得到的内存映射对象句柄是新创建的还是打开已经存在的。如果系统无法创建文件映射对象,将导致 CreateFileMapping()的执行失败,返回N U L L句柄值。

     

    3)MapViewOfFile():当创建了一个内存映射文件对象并得到其有效句柄后,该句柄即可用来在进程的虚拟地址空间中映射文件的一个映像。在内存映射文件对象已经存在的情况下,映像可被任意映射或取消映射。在文件映像被映射时,仍然必须由系统来为文件的数据保留一个地址空间区域,并将文件的数据作为映射到该区域的物理存储器进行提交。在进程的地址空间中,一个足够大的连续地址空间(通常足以覆盖整个文件映像)将被指定给此文件映像。尽管如此,内存的物理页面还是根据在实际使用中的需求而进行分配的。真正分配一个对应于内存映射文件映像页面的物理内存页面是在发生该页的缺页中断时进行的,这将在第一次读写内存页面中的任一地址时自动完成。MapViewOfFile()即负责映射内存映射文件的一个映像,

      函数的第一个参数为CreateFileMapping()所返回的内存映射文件对象句柄,第二个参数指定了对文件映像的访问类型,可能取值有 FILE_MAP_WRITE、FILE_MAP_READ、FILE_MAP_ALL_ACCESS和FILE_MAP_COPY等几种,具体的设置要根据文件映射对象允许的保护模式而定。根据前面代码的设置,这里应该使用FILE_MAP_ALL_ACCESS参数。这种机制为对象的创建者提供了对映射此对象的方式进行控制的能力。接下来的2个参数分别指定了内存映射文件的64位偏移地址的低32位和高32位地址,该地址是从内存映射文件头位置到映像开始位置的距离。最后的参数指定了视图的大小,如果设置为0,前面的偏移地址将被忽略,系统将会把整个文件映射为一个映像。 MapViewOfFile()如果成功执行,将返回一个指向文件映像在进程的地址空间中的起始地址的指针。如果失败,则返回NULL。在进程中,可以为同一个文件映射对象创建多个文件映像,这些映像可以在系统中共存和重叠,也可以与对应的文件映射对象大小不相一致,但不能大于文件映射对象的大小。

      4)UnmapViewOfFile():当不再需要保留映射到进程地址空间区域中的文件映像数据时,可通过调用UnmapViewOfFile()函数将其释放。该函数结构非常简单,只需要提供映像在进程中的起始地址(区域的基地址)作为参数即可。该函数的输入参数为调用MapViewOfFile()时所返回的指向文件映像在进程的地址空间中的起始地址的指针。在调用 MapViewOfFile()后,必须确保在进程退出之前能够执行UnmapViewOfFile()函数,否则在进程终止之后先前保留的区域将得不到释放,即使再次启动进程重复调用MapViewOfFile()系统也总是在进程的地址空间中保留一个新的区域,而此前保留的所有区域将得不到释放。

      一种比较特殊的情况是,对同一个内存映射文件映射了两个相同的映像的撤消。前面曾经提到过,对于同一个内存映射文件可以有多个映像,这些映像也可以重叠,因此这种情况的存在是合法的。对于这种情况,虽然从表面看上去在单进程的地址空间内是不可能存在两个基地址完全相同的映像的,这将导致无法对这它们的区分。但是事实上,由MapViewOfFile()所返回得到的基地址只是文件映像在进程地址空间中的起始基地址,因此在映射同一内存映射文件的两个相同映像时将会产生对内存映射文件同一部分的两个不同基地址的相同映像,可以用同样的方法调用UnmapViewOfFile()将其从进程的地址空间中予以撤消。

      5)CloseHandle(): 与Win32的大多数对象一样,在使用完毕之后总是要通过CloseHandle()函数将已打开的内核对象关闭。如果忘记关闭对象,在程序继续运行时将会出现资源泄漏。虽然在程序退出运行时,操作系统会自动关闭在进程中已经打开但未关闭的任何对象。但是在进程的运行过程中,势必会积累过多的资源句柄。因此在不再需要使用对象的时候通过CloseHandle()将其予以关闭是有意义的。

      小结

      本文对内存映射文件在大文件处理中的应用作了较为详细的阐述。经实际测试,内存映射文件在处理大数据量文件时表现出了良好的性能,比通常使用 CFile类和ReadFile()和WriteFile()等函数的文件处理方式具有明显的优势。本文所述程序代码在Windows 2000 Professional下由Microsoft Visual C++ 6.0编译通过

    展开全文
  • 内存映射文件原理

    万次阅读 2016-03-26 11:51:56
    一直都对内存映射文件这个概念很模糊,不知道它和虚拟内存有什么区别,而且映射这个词也很让人迷茫,今天终于搞清楚了。。。下面,我先解释一下我对映射这个词的理解,再区分一下几个容易混淆的概念,之后,什么是...

    一直都对内存映射文件这个概念很模糊,不知道它和虚拟内存有什么区别,而且映射这个词也很让人迷茫,今天终于搞清楚了。。。下面,我先解释一下我对映射这个词的理解,再区分一下几个容易混淆的概念,之后,什么是内存映射就很明朗了。

     

    原理

    首先,“映射”这个词,就和数学课上说的“一一映射”是一个意思,就是建立一种一一对应关系,在这里主要是只 硬盘上文件 的位置与进程 逻辑地址空间中 一块大小相同的区域之间的一一对应,如图1中过程1所示。这种对应关系纯属是逻辑上的概念,物理上是不存在的,原因是进程的逻辑地址空间本身就是不存在 的。在内存映射的过程中,并没有实际的数据拷贝,文件没有被载入内存,只是逻辑上被放入了内存,具体到代码,就是建立并初始化了相关的数据结构 (struct address_space),这个过程有系统调用mmap()实现,所以建立内存映射的效率很高。

     

    图1.内存映射原理  

     

     

     

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

     

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

     

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

     

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

     

     

    效率  

    从 代码层面上看,从硬盘上将文件读入内存,都要经过文件系统进行数据拷贝,并且数据拷贝操作是由文件系统和硬件驱动实现的,理论上来说,拷贝数据的效率是一 样的。但是通过内存映射的方法访问硬盘上的文件,效率要比read和write系统调用高,这是为什么呢?原因是read()是系统调用,其中进行了数据 拷贝,它首先将文件内容从硬盘拷贝到内核空间的一个缓冲区,如图2中过程1,然后再将这些数据拷贝到用户空间,如图2中过程2,在这个过程中,实际上完成 了两次数据拷贝 ;而mmap()也是系统调用,如前所述,mmap()中没有进行数据拷贝,真正的数据拷贝是在缺页中断处理时进行的,由于mmap()将文件直接映射到用户空间,所以中断处理函数根据这个映射关系,直接将文件从硬盘拷贝到用户空间,只进行了 一次数据拷贝 。因此,内存映射的效率要比read/write效率高。

     

     

    图2.read系统调用原理

    展开全文
  • ----原来作者的程序有些问题,现在文章中的程序已经修改正确---VC中用内存映射文件处理大文件 摘要:本文通过内存映射文件的使用来对大尺寸文件进行访问操作,同时也对内存映射文件的相关概念和一般编程过程作了...
     
    

    ----原来作者的程序有些问题,现在文章中的程序已经修改正确---

    VC中用内存映射文件处理大文件

     


    摘要:本文通过内存映射文件的使用来对大尺寸文件进行访问操作,同时也对内存映射文件的相关概念和一般编程过程作了较为详细的介绍。

      关键词:内存映射文件;大文件处理;分配粒度

      引言

      文件操作是应用程序最为基本的功能之一,Win32 API和MFC均提供有支持文件处理的函数和类,常用的有Win32 API的CreateFile()、WriteFile()、ReadFile()和MFC提供的CFile类等。一般来说,以上这些函数可以满足大多数场合的要求,但是对于某些特殊应用领域所需要的动辄几十GB、几百GB、乃至几TB的海量存储,再以通常的文件处理方法进行处理显然是行不通的。目前,对于上述这种大文件的操作一般是以内存映射文件的方式来加以处理的,本文下面将针对这种Windows核心编程技术展开讨论。

      内存映射文件概述

      内存文件映射也是Windows的一种内存管理方法,提供了一个统一的内存管理特征,使应用程序可以通过内存指针对磁盘上的文件进行访问,其过程就如同对加载了文件的内存的访问。通过文件映射这种使磁盘文件的全部或部分内容与进程虚拟地址空间的某个区域建立映射关联的能力,可以直接对被映射的文件进行访问,而不必执行文件I/O操作也无需对文件内容进行缓冲处理。内存文件映射的这种特性是非常适合于用来管理大尺寸文件的。

      在使用内存映射文件进行I/O处理时,系统对数据的传输按页面来进行。至于内部的所有内存页面则是由虚拟内存管理器来负责管理,由其来决定内存页面何时被分页到磁盘,哪些页面应该被释放以便为其它进程提供空闲空间,以及每个进程可以拥有超出实际分配物理内存之外的多少个页面空间等等。由于虚拟内存管理器是以一种统一的方式来处理所有磁盘I/O的(以页面为单位对内存数据进行读写),因此这种优化使其有能力以足够快的速度来处理内存操作。

      使用内存映射文件时所进行的任何实际I/O交互都是在内存中进行并以标准的内存地址形式来访问。磁盘的周期性分页也是由操作系统在后台隐蔽实现的,对应用程序而言是完全透明的。内存映射文件的这种特性在进行大文件的磁盘事务操作时将获得很高的效益。

      需要说明的是,在系统的正常的分页操作过程中,内存映射文件并非一成不变的,它将被定期更新。如果系统要使用的页面目前正被某个内存映射文件所占用,系统将释放此页面,如果页面数据尚未保存,系统将在释放页面之前自动完成页面数据到磁盘的写入。

      对于使用页虚拟存储管理的Windows操作系统,内存映射文件是其内部已有的内存管理组件的一个扩充。由可执行代码页面和数据页面组成的应用程序可根据需要由操作系统来将这些页面换进或换出内存。如果内存中的某个页面不再需要,操作系统将撤消此页面原拥用者对它的控制权,并释放该页面以供其它进程使用。只有在该页面再次成为需求页面时,才会从磁盘上的可执行文件重新读入内存。同样地,当一个进程初始化启动时,内存的页面将用来存储该应用程序的静态、动态数据,一旦对它们的操作被提交,这些页面也将被备份至系统的页面文件,这与可执行文件被用来备份执行代码页面的过程是很类似的。图1展示了代码页面和数据页面在磁盘存储器上的备份过程:

    图1 进程的代码页、数据页在磁盘存储器上的备份

      显然,如果可以采取同一种方式来处理代码和数据页面,无疑将会提高程序的执行效率,而内存映射文件的使用恰恰可以满足此需求。

      对大文件的管理

      内存映射文件对象在关闭对象之前并没有必要撤销内存映射文件的所有视图。在对象被释放之前,所有的脏页面将自动写入磁盘。通过CloseHandle()关闭内存映射文件对象,只是释放该对象,如果内存映射文件代表的是磁盘文件,那么还需要调用标准文件I/O函数来将其关闭。在处理大文件处理时,内存映射文件将表示出卓越的优势,只需要消耗极少的物理资源,对系统的影响微乎其微。下面先给出内存映射文件的一般编程流程框图:

    图2 使用内存映射文件的一般流程

      而在某些特殊行业,经常要面对十几GB乃至几十GB容量的巨型文件,而一个32位进程所拥有的虚拟地址空间只有232 = 4GB,显然不能一次将文件映像全部映射进来。对于这种情况只能依次将大文件的各个部分映射到进程中的一个较小的地址空间。这需要对上面的一般流程进行适当的更改:

      1)映射文件开头的映像。

      2)对该映像进行访问。

      3)取消此映像

      4)映射一个从文件中的一个更深的位移开始的新映像。

      5)重复步骤2,直到访问完全部的文件数据。

      下面给出一段根据此描述而写出的对大于4GB的文件的处理代码:

     // 选择文件
     CFileDialog fileDlg(TRUE, "*.txt", "*.txt", NULL, "文本文件 (*.txt)|*.txt||", this);
     fileDlg.m_ofn.Flags |= OFN_FILEMUSTEXIST;
     fileDlg.m_ofn.lpstrTitle = "通过内存映射文件读取数据";
     if (fileDlg.DoModal() == IDOK)
     {
      // 创建文件对象
      HANDLE hFile = CreateFile(fileDlg.GetPathName(), GENERIC_READ | GENERIC_WRITE,
       0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
      if (hFile == INVALID_HANDLE_VALUE)
      {
       TRACE("创建文件对象失败,错误代码:%d/r/n", GetLastError());
       return;
      }
      // 创建文件映射对象
      HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
      if (hFileMap == NULL)
      {
       TRACE("创建文件映射对象失败,错误代码:%d/r/n", GetLastError());
       return;
      }
      // 得到系统分配粒度
      SYSTEM_INFO SysInfo;
      GetSystemInfo(&SysInfo);
      DWORD dwGran = SysInfo.dwAllocationGranularity;
      // 得到文件尺寸
      DWORD dwFileSizeHigh;
      __int64 qwFileSize = GetFileSize(hFile, &dwFileSizeHigh);
      qwFileSize |= (((__int64)dwFileSizeHigh) << 32);
      // 关闭文件对象
      CloseHandle(hFile);
      // 偏移地址
      __int64 qwFileOffset = 0;
      // 块大小
      DWORD dwBlockBytes =  dwGran;
      
      while (qwFileSize > 0)
      {
       // 映射视图
       if (qwFileSize <  dwGran)
       dwBlockBytes = (DWORD)qwFileSize;
       LPBYTE lpbMapAddress = (LPBYTE)MapViewOfFile(hFileMap,FILE_MAP_ALL_ACCESS, (DWORD)(qwFileOffset >> 32), (DWORD)(qwFileOffset & 0xFFFFFFFF), dwBlockBytes);
       if (lpbMapAddress == NULL)
       {
        TRACE("映射文件映射失败,错误代码:%d/r/n", GetLastError());
        return;
       }
       // 对映射的视图进行访问

       for(DWORD i = 0; i < dwBlockBytes; i++)
       {
        BYTE temp = *(lpbMapAddress + i);
        
       }
       
       // 撤消文件映像
       UnmapViewOfFile(lpbMapAddress);
       // 修正参数
       qwFileOffset += dwBlockBytes;
       qwFileSize -= dwBlockBytes;
      }
      // 关闭文件映射对象句柄
      CloseHandle(hFileMap);
      AfxMessageBox("成功完成对文件的访问");
     }

      在本例中,首先通过GetFileSize()得到被处理文件长度(64位)的高32位和低32位值。然后在映射过程中设定每次映射的块大小为1000 倍的分配粒度,如果文件长度小于1000倍的分配粒度时则将块大小设置为文件的实际长度。在处理过程中由映射、访问、撤消映射构成了一个循环处理。其中,每处理完一个文件块后都通过关闭文件映射对象来对每个文件块进行整理。CreateFileMapping()、MapViewOfFile()等函数是专门用来进行内存文件映射处理用的。

      下面分别对这些关键函数进行说明:

      1)CreateFile():CreateFile()函数是一个用途非常广泛的函数,在这里的用法并没有什么特殊的地方,但有几点需要注意:一是访问模式参数dwDesiredAccess。该参数设置了对文件内核对象的访问类型,其允许设置的权限可以为读权限GENERIC_READ、写权限GENERIC_WRITE、读写权限GENERIC_READ | GENERIC_WRITE和设备查询权限0。在使用映射文件时,只能打开那些具有可读访问权限的文件,即只能应用GENERIC_READ和 GENERIC_READ | GENERIC_WRITE这两种组合;另一点需要注意的是共享模式参数dwShareMode。该参数定义了对文件内核对象的共享方式,其可能的设置为 FILE_SHARE_READ、FILE_SHARE_WRITE和0,并可对其组合使用。其中,设置为0时不允许共享对象; FILE_SHARE_READ和FILE_SHARE_WRITE分别为在要求只读、只写访问的情况下才允许对象的共享。

      由于通过内存映射文件可以在多个进程间共享数据,因此在进行这种应用时应当考虑dwShareMode参数设置对运行结果的影响。

      2)CreateFileMapping():该函数的作用是创建一个文件映射内核对象,以告知系统文件映射对象需要多大的物理存储器。创建内存映射文件对象对系统资源几乎没有什么影响,也不会影响进程的虚拟地址空间。除了需要用来表示该对象的内部资源之外通常并不用为其分配虚拟内存,但是如果内存映射文件对象是作共享内存之用的话,就要在创建对象时由系统为内存映射文件的使用在系统页文件中保留足够的空间。

      函数第一个参数hFile为标识要映射到进程的地址空间的文件的句柄。虽然由于内存映射文件的物理存储器是来自于磁盘上的文件,而非系统的页文件,使创建内存映射文件就像保留一个地址空间区域并将物理存储器提交给该区域一样。第二个参数为指向文件映射内核对象的 SECURITY_ATTRIBUTES结构的指针,由此来决定子进程能否继承得到返回的句柄。通常为其传递NULL值,以默认的安全属性来禁止返回句柄的被继承。

      接下来的参数用于文件被映射后设定文件映像的保护属性。其可能的取值为PAGE_READONLY、 PAGE_READWRITE和PAGE_WRITECOPY。虽然在创建文件映射对象时,系统并不为其保留地址空间区域,也不将文件的存储器映射到该区域。但是,在系统将存储器映射到进程的地址空间中去时,系统必须确切知道应赋予物理存储器页面的保护属性。在设置保护属性时,必须与用 CreateFile()函数打开文件时所指定的访问标识相匹配,否则将导致CreateFileMapping()的执行失败。因此这里设置 PAGE_READWRITE属性。除了上述三个页面保护属性外,还有4个区(Section)保护属性也可以一起组合使用:

    区保护属性 说明
    SEC_COMMIT 为区中的所有页面在内存中或磁盘页面文件中分配物理存储器
    SEC_IMAGE 告知系统,映射的文件是一个可移植的EXE文件映像
    SEC_NOCACHE 告知系统,未将文件的任何内存映射文件放入高速缓存,多供硬件设备驱动程序开发人员使用
    SEC_RESERVE 对一个区的所有页面进行保留而不分配物理存储器

    后面的两个参数指定了要创建的文件映射对象的最大字节数的高32位值和低32位值,实际也就设定了文件的最大字节数(最大可以处理16EB的文件)。这两个参数可以满足确保文件映射对象能够得到足够的物理存储器这一基本条件。在参数设置的大小小于文件实际大小时,系统将从文件映射指定的字节数。这里将其设置为0,将使所创建的文件映射对象将为文件的当前大小,以上两种情况均无法改变文件的大小。如果设置的参数大于文件的实际大小,系统将会在CreateFileMapping()函数返回前扩展该文件。需要指出的是,文件映射对象的大小是静态的,一旦创建完毕后将无法更改。如果设置的文件映射对象尺寸偏小将导致无法对文件进行全面的访问。

      在本节开始也曾提到过,创建文件映射对象是不需要花费什么系统资源的,因此遵循"宁多勿缺"的原则,一般应将文件映射对象的大小设置为文件大小的相同值。函数最后的参数将可以为映射对象命名。如果想打开一个已存在的文件映射对象,该对象必须要命名。对该名字字符串的要求仅限于未被其它对象使用过的名字即可。

      3)MapViewOfFile():当创建了一个内存映射文件对象并得到其有效句柄后,该句柄即可用来在进程的虚拟地址空间中映射文件的一个映像。在内存映射文件对象已经存在的情况下,映像可被任意映射或取消映射。在文件映像被映射时,仍然必须由系统来为文件的数据保留一个地址空间区域,并将文件的数据作为映射到该区域的物理存储器进行提交。在进程的地址空间中,一个足够大的连续地址空间(通常足以覆盖整个文件映像)将被指定给此文件映像。尽管如此,内存的物理页面还是根据在实际使用中的需求而进行分配的。真正分配一个对应于内存映射文件映像页面的物理内存页面是在发生该页的缺页中断时进行的,这将在第一次读写内存页面中的任一地址时自动完成。MapViewOfFile()即负责映射内存映射文件的一个映像,

      函数的第一个参数为CreateFileMapping()所返回的内存映射文件对象句柄,第二个参数指定了对文件映像的访问类型,可能取值有FILE_MAP_WRITE、FILE_MAP_READ、FILE_MAP_ALL_ACCESS和 FILE_MAP_COPY等几种,具体的设置要根据文件映射对象允许的保护模式而定。根据前面代码的设置,这里应该使用 FILE_MAP_ALL_ACCESS参数。这种机制为对象的创建者提供了对映射此对象的方式进行控制的能力。接下来的2个参数分别指定了内存映射文件的64位偏移地址的低32位和高32位地址,该地址是从内存映射文件头位置到映像开始位置的距离。最后的参数指定了视图的大小,如果设置为0,前面的偏移地址将被忽略,系统将会把整个文件映射为一个映像。MapViewOfFile()如果成功执行,将返回一个指向文件映像在进程的地址空间中的起始地址的指针。如果失败,则返回NULL。在进程中,可以为同一个文件映射对象创建多个文件映像,这些映像可以在系统中共存和重叠,也可以与对应的文件映射对象大小不相一致,但不能大于文件映射对象的大小。

      4)UnmapViewOfFile():当不再需要保留映射到进程地址空间区域中的文件映像数据时,可通过调用UnmapViewOfFile()函数将其释放。该函数结构非常简单,只需要提供映像在进程中的起始地址(区域的基地址)作为参数即可。该函数的输入参数为调用MapViewOfFile()时所返回的指向文件映像在进程的地址空间中的起始地址的指针。在调用MapViewOfFile()后,必须确保在进程退出之前能够执行UnmapViewOfFile()函数,否则在进程终止之后先前保留的区域将得不到释放,即使再次启动进程重复调用 MapViewOfFile()系统也总是在进程的地址空间中保留一个新的区域,而此前保留的所有区域将得不到释放。

      一种比较特殊的情况是,对同一个内存映射文件映射了两个相同的映像的撤消。前面曾经提到过,对于同一个内存映射文件可以有多个映像,这些映像也可以重叠,因此这种情况的存在是合法的。对于这种情况,虽然从表面看上去在单进程的地址空间内是不可能存在两个基地址完全相同的映像的,这将导致无法对这它们的区分。但是事实上,由MapViewOfFile()所返回得到的基地址只是文件映像在进程地址空间中的起始基地址,因此在映射同一内存映射文件的两个相同映像时将会产生对内存映射文件同一部分的两个不同基地址的相同映像,可以用同样的方法调用 UnmapViewOfFile()将其从进程的地址空间中予以撤消。

      5)CloseHandle():与Win32的大多数对象一样,在使用完毕之后总是要通过 CloseHandle()函数将已打开的内核对象关闭。如果忘记关闭对象,在程序继续运行时将会出现资源泄漏。虽然在程序退出运行时,操作系统会自动关闭在进程中已经打开但未关闭的任何对象。但是在进程的运行过程中,势必会积累过多的资源句柄。因此在不再需要使用对象的时候通过CloseHandle ()将其予以关闭是有意义的。

      小结

      本文对内存映射文件在大文件处理中的应用作了较为详细的阐述。经实际测试,内存映射文件在处理大数据量文件时表现出了良好的性能,比通常使用CFile类和ReadFile()和WriteFile()等函数的文件处理方式具有明显的优势。本文所述程序代码在 Windows 2000 Professional下由Microsoft Visual C++ 6.0编译通过。

    展开全文
  • 使用内存映射文件读写大文件 文件操作是应用程序最为基本的功能之一,Win32 API和MFC均提供有支持文件处理的函数和类。一般来说,这些函数可以满足大多数场合的要求,但是对于某些特殊应用领域所需要的动辄几十GB...

    使用内存映射文件读写大文件

    文件操作是应用程序最为基本的功能之一,Win32 API和MFC均提供有支持文件处理的函数和类。一般来说,这些函数可以满足大多数场合的要求,但是对于某些特殊应用领域所需要的动辄几十GB、几百GB、乃至几TB的海量存储,再以通常的文件处理方法进行处理显然是行不通的。使用字符串变量的方法不仅会加重内存的负担,而且会Unicode和ASCII码的转换会把你弄得焦头烂额。目前,对于上述这种大文件的操作一般是以内存映射文件的方式来加以处理的,比I/O读写要快20倍,所谓I/O操作不是对外围设备直接进行操作,而是对设备与cpu连接的接口电路的操作。而映射文件的方法就是对磁盘直接进行操作。

    内存映射文件无非就是那些文件中的数据被直接映射到进程地址空间中去的文件,与虚拟内存有类似的地方是,通过内存映射文件可以保留一个地址空间的区域,同时将物理存储器提交给此区域,只是内存文件映射的物理存储器来自一个已经存在于磁盘上的文件,而非系统的页文件,而且在对该文件进行操作之前必须首先对文件进行映射,就如同将整个文件从磁盘加载到内存。由此可以看出,使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,这意味着在对文件进行处理时将不必再为文件申请并分配缓存,所有的文件缓存操作均由系统直接管理,由于取消了将文件数据加载到内存、数据从内存到文件的回写以及释放内存块等步骤,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。另外,实际工程中的系统往往需要在多个进程之间共享数据,如果数据量小,处理方法是灵活多变的,如果共享数据容量巨大,那么就需要借助于内存映射文件来进行。实际上,内存映射文件正是解决本地多个进程间数据共享的最有效方法。

    首先要通过CreateFile()函数来创建或打开一个文件内核对象,这个对象标识了磁盘上将要用作内存映射文件的文件。在用CreateFile()将文件映像在物理存储器的位置通告给操作系统后,只指定了映像文件的路径,映像的长度还没有指定。为了指定文件映射对象需要多大的物理存储空间还需要通过CreateFileMapping()函数来创建一个文件映射内核对象以告诉系统文件的尺寸以及访问文件的方式。

    CreateFileMapping()在创建了文件映射对象后,还必须为文件数据保留一个地址空间区域,并把文件数据作为映射到该区域的物理存储器进行提交。由MapViewOfFile()函数负责通过系统的管理而将文件映射对象的全部或部分映射到进程地址空间,实际上相当于加载文件中指定的数据到内存中。此时,对内存映射文件的使用和处理同通常加载到内存中的文件数据的处理方式基本一样,在完成了对内存映射文件的使用时,还要通过一系列的操作完成对其的清除和使用过资源的释放。这部分相对比较简单,可以通过UnmapViewOfFile()完成从进程的地址空间撤消文件数据的映像、通过CloseHandle()关闭前面创建的文件映射对象和文件对象。

    实际上操作文件映射对象就相当于操作VC++文件读写方式下的文件内部指针。

    而在某些特殊行业,经常要面对十几GB乃至几十GB容量的巨型文件,而一个32位进程所拥有的虚拟地址空间只有2^32 = 4GB,显然不能一次将文件映像全部映射进来。对于这种情况只能依次将大文件的各个部分映射到进程中的一个较小的地址空间。这需要对上面的一般流程进行适当的更改:

    1)映射从文件开头的映像;

    2)对该映像进行访问;

    3)取消此映像;

    4)映射一个从文件中的一个更深的位移开始的新映像;

    5)重复步骤2,直到访问完全部的文件数据。

    示例代码:

    在本例中,首先通过GetFileSize()得到被处理文件长度(64位)的高32位和低32位值。然后在映射过程中设定每次映射的块大小为1000倍的分配粒度(系统的数据分块大小),如果文件长度小于1000倍的分配粒度时则将块大小设置为文件的实际长度。在处理过程中由映射、访问、撤消映射构成了一个循环处理。其中,每处理完一个文件块后都通过关闭文件映射对象来对每个文件块进行整理。CreateFileMapping()、MapViewOfFile()等函数是专门用来进行内存文件映射处理用的。

     // 创建文件对象
    
     HANDLE hFile = ::CreateFile(strFile, GENERIC_READ,FILE_SHARE_READ, NULL,
    
      OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL);
    
     if (hFile == INVALID_HANDLE_VALUE)
    
     {
    
      TRACE("创建文件对象失败,错误代码:%d\r\n", GetLastError());
    
      return;
    
     }
    
     // 创建文件映射对象
    
     HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
    
     if (hFileMap == NULL)
    
     {
    
      TRACE("创建文件映射对象失败,错误代码:%d\r\n", GetLastError());  
    
      return;
    
     }
    
     // 得到系统分配粒度
    
     SYSTEM_INFO SysInfo;
    
     GetSystemInfo(&SysInfo);
    
     DWORD dwGran = SysInfo.dwAllocationGranularity;
    
     // 得到文件尺寸
    
     DWORD dwFileSizeHigh;
    
     __int64 qwFileSize = GetFileSize(hFile, &dwFileSizeHigh);
    
     qwFileSize |= (((__int64)dwFileSizeHigh) << 32);///MSDN
    
     // 偏移地址
    
     __int64 qwFileOffset = 0;
    
     __int64 T_newmap = 900 * dwGran;
    
     // 块大小
    
     DWORD dwBlockBytes = 1000 * dwGran;//文件数据分段大小
    
     if (qwFileSize - qwFileOffset < dwBlockBytes)
    
      dwBlockBytes = (DWORD)qwFileSize;
    
     // 映射视图
    
     char *lpbMapAddress = (char *)MapViewOfFile(hFileMap,FILE_MAP_READ,
    
      (DWORD)(qwFileOffset >> 32), (DWORD)(qwFileOffset & 0xFFFFFFFF),dwBlockBytes);
    
     if (lpbMapAddress == NULL)
    
     {
    
      TRACE("映射文件映射失败,错误代码:%d ", GetLastError());
    
      return;
    
     } 
    
     // 关闭文件对象
    
     CloseHandle(hFile); 
    
     ///读文件数据
    
     while(qwFileOffset < qwFileSize)
    
     {
    
      /********************            读文件             ***************************/  
    
      //read_eh(&lpbMapAddress)读取已映射到内存的数据,并将文件指针作相应后移(lpbMapAddress++),返回指针偏移量
    
      qwFileOffset = qwFileOffset + read_eh(&lpbMapAddress); //修改偏移量
    
      if (qwFileOffset > T_newmap)
    
      {//当数据读到90%时,为防数据溢出,需要映射在其后的数据  T_newmap
    
       UnmapViewOfFile(lpbMapAddress);//释放当前映射
    
       if ((DWORD)(qwFileSize - T_newmap) < dwBlockBytes)
    
       dwBlockBytes = (DWORD)(qwFileSize - T_newmap);
    
       lpbMapAddress = (char *)MapViewOfFile(hFileMap,FILE_MAP_READ,
    
       (DWORD)(T_newmap >> 32), (DWORD)(T_newmap & 0xFFFFFFFF),dwBlockBytes);
    
       // 修正参数
    
       lpbMapAddress = lpbMapAddress + qwFileOffset - T_newmap;
    
       T_newmap =T_newmap  + 900 * dwGran;
    
       if (lpbMapAddress == NULL)
    
       {
    
        TRACE("映射文件映射失败,错误代码:%d ", GetLastError());
    
        return;
    
       }
    
      }
    
     }
    
     //释放最后数据块映射
    
     UnmapViewOfFile(lpbMapAddress);
    
     // 关闭文件映射对象句柄
    
     CloseHandle(hFileMap); 


    展开全文
  • 内存映射文件详解-----C++实现 分类: C++ 先不说内存映射文件(即一块内存和一个文件相映射对应)是什么。贴个代码先,。 [cpp] view plaincopyprint? #include  #...
  • 内存映射文件原理探索

    万次阅读 多人点赞 2010-09-17 14:38:00
    内存映射的过程中,并没有实际的数据拷贝,文件没有被载入内存,只是逻辑上被放入了内存,具体到代码,就是建立并初始化了相关的数据结构(struct address_space),这个过程有系统调用mmap()实现,所以建立内存...
  • 内存映射文件

    万次阅读 2015-08-28 16:32:22
    因为一般常规的方法就是先读出磁盘文件的内容到内存中,然后修改,最后写回到磁盘上。读磁盘文件是要经过一次系统调用,先将文件的内容从磁盘拷贝到内核空间的一个缓冲区,然后再将这些数据拷贝到用户空间,实际上是...
  • 文件内存映射(一):它是什么

    千次阅读 2019-09-18 06:30:27
    在综合考虑各方面的因素后,在技术选择上选择了 -- 《文件内存映射》。所以接下来想用几部分来介绍这个技术。一.它是一种文件操作的方式,由操作系统支持有人可能会觉得上一句的后半部分有点废话,但我觉得却是必须....
  • 【Python学习之路】Numpy 内存映射

    万次阅读 2019-12-15 20:52:02
    内存映射文件与虚拟内存有些类似,通过内存映射文件可以保留一个地址空间的区域,同时将物理存储器提交给此区域,内存文件映射的物理存储器来自一个已经存在于磁盘上的文件,而且在对该文件进行操作之前必须首先对...
  • 尽管从JDK 1.4版本开始,Java内存映射文件(Memory Mapped Files)就已经在java.nio包中,但它对很多程序开发者来说仍然是一个相当新的概念。引入NIO后,Java IO已经相当快,而且内存映射文件提供了Java有可能达到的...
  • Linux内存映射: mmap

    万次阅读 2007-07-26 17:49:00
    Linux提供了内存映射函数mmap, 它把文件内容映射到一段内存上(准确说是虚拟内存上), 通过对这段内存的读取和修改, 实现对文件的读取和修改, 先来看一下mmap的函数声明: 头文件: 原型: void *mmap(void *addr, size_t...
  • 内存映射 在 Linux操作系统中非常重要,因为其涉及到高效的跨进程通信 &amp; 文件操作 今天,我将详细讲解操作系统中核心概念:内存映射 目录 1. 定义 关联 进程中的1个虚拟内存区域 &amp; 1个磁盘上的...
  • 内存映射文件”可以将硬盘上的文件映射到虚拟地址空间,这样就不需要将所有东西都放入到页交换文件中,比如系统有许多程序同时运行时,如果将这些程序文件都加载到页交换文件中,页交换文件将会变得非常大。...
  • Qt——文件映射

    千次阅读 2015-09-02 14:57:20
    概述:在处理数据量较大的文件时,往往需要使用文件映射技术,即虚拟内存。 通过文件映射之后,就可以像操作内存一样去直接操作文件。而不需要再调用文件读写方法了。原理:给磁盘上的文件分配地址,类似内存的地址...
  • Linux 内存映射函数 mmap()函数详解

    万次阅读 多人点赞 2016-08-07 00:01:15
    mmap将一个文件或者其它对象映射内存文件映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。mmap在用户空间映射调用系统中作用很大。 头文件 函数原型 void* mmap...
  • CreateFileMapping用法

    万次阅读 2013-02-22 13:26:02
    测试创建和打开文件映射的时候老是得到"句柄无效"的错误, 仔细看了MSDN以后才发觉是函数认识不透, 这里把相关的解释翻译出来 HANDLE CreateFileMapping(  HANDLE hFile, //物理文件句柄  LPSECURITY_...
  • 一、mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这
  • mmap函数使用

    万次阅读 2008-01-22 17:29:00
    UNIX网络编程第二卷进程间通信...2、将特殊文件进行匿名内存映射,可以为关联进程提供共享内存空间;3、为无关联的进程提供共享内存空间,一般也是将一个普通文件映射到内存中。函数:void *mmap(void *start,size_t l
  • 快速读写磁盘数据的方法: 1.一下子将数据读取到内存的(无论是文本还是二进制),而不是一行行的读取。...3.使用内存文件映射, window是CreateFileMapping,MapViewOfFile,UnmapViewOfFile,CloseHandle;linux是用
  • 内存映射机制(mmap)

    千次阅读 2018-11-15 01:04:21
    MMU位于CPU与物理内存之间,它包含从虚地址向物理内存地址转化的映射信息。当CPU引用一个内存位置时,MMU决定哪些页需要驻留(通常通过移位或屏蔽地址的某些位)以及转化虚拟页号到物理页号。 当某个进程读取磁盘...
1 2 3 4 5 ... 20
收藏数 377,043
精华内容 150,817
关键字:

内存映射文件