文件对象和文件映射对象

文件对象和文件映射对象

转载自:https://blog.csdn.net/sunnymov/article/details/5410449

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

2.内存映射文件的一般方法:   
     首先要通过CreateFile()函数来创建或打开一个文件内核对象,这个对象标识了磁盘上将要用作内存映射文件的文件。在用CreateFile()将文件映像在物理存储器的位置通告给操作系统后,只指定了映像文件的路径,映像的长度还没有指定。为了指定文件映射对象需要多大的物理存储空间还需要通过CreateFileMapping()函数来创建一个文件映射内核对象以告诉系统文件的尺寸以及访问文件的方式。在创建了文件映射对象后,还必须为文件数据保留一个地址空间区域【不是实际的物理内存,分配个地址,但是实际是对应磁盘上的位置】,并把文件数据作为映射到该区域的物理存储器进行提交。由MapViewOfFile()函数负责通过系统的管理而将文件映射对象的全部或部分映射到进程地址空间。此时,对内存映射文件的使用和处理同通常加载到内存中的文件数据的处理方式基本一样,在完成了对内存映射文件的使用时,还要通过一系列的操作完成对其的清除和使用过资源的释放。这部分相对比较简单,可以通过UnmapViewOfFile()完成从进程的地址空间撤消文件数据的映像、通过CloseHandle()关闭前面创建的文件映射对象和文件对象。   

3.内存映射文件相关函数   

     在使用内存映射文件时,所使用的API函数主要就是前面提到过的那几个函数,下面分别对其进行介绍:   

(1).CreateFile()

  HANDLE CreateFile(
  LPCTSTR lpFileName
  DWORD dwDesiredAccess
  DWORD dwShareMode
  LPSECURITY_ATTRIBUTES lpSecurityAttributes
  DWORD dwCreationDisposition
  DWORD dwFlagsAndAttributes
  HANDLE hTemplateFile
); 

函数CreateFile()即使是在普通的文件操作时也经常用来创建、打开文件,在处理内存映射文件时,该函数来创建/打开一个文件内核对象,并将其句柄返回,在调用该函数时需要根据是否需要数据读写和文件的共享方式来设置参数dwDesiredAccess和dwShareMode,错误的参数设置将会导致相应操作时的失败。

(2).CreateFileMapping()

  HANDLE CreateFileMapping(
  HANDLE hFile, 
  LPSECURITY_ATTRIBUTES lpFileMappingAttributes, 
  DWORD flProtect, 
  DWORD dwMaximumSizeHigh, 
  DWORD dwMaximumSizeLow, 
  LPCTSTR lpName 
);   

CreateFileMapping()函数创建一个文件映射内核对象,通过参数hFile指定待映射到进程地址空间的文件句柄(该句柄由CreateFile()函数的返回值获取)。由于内存映射文件的物理存储器实际是存储于磁盘上的一个文件,而不是从系统的页文件中分配的内存,所以系统不会主动为其保留地址空间区域,也不会自动将文件的存储空间映射到该区域,为了让系统能够确定对页面采取何种保护属性,需要通过参数flProtect来设定,保护属性PAGE_READONLY、PAGE_READWRITE和PAGE_WRITECOPY分别表示文件映射对象被映射后,可以读取、读写文件数据。在使用PAGE_READONLY时,必须确保CreateFile()采用的是GENERIC_READ参数;PAGE_READWRITE则要求CreateFile()采用的是GENERIC_READ | GENERIC_WRITE参数;至于属性PAGE_WRITECOPY则只需要确保CreateFile()采用了GENERIC_READ和GENERIC_WRITE其中之一即可。DWORD型的参数dwMaximumSizeHigh和dwMaximumSizeLow也是相当重要的,指定了文件的最大字节数,由于这两个参数共64位,因此所支持的最大文件长度为16EB,几乎可以满足任何大数据量文件处理场合的要求。

(3).MapViewOfFile():

  LPVOID MapViewOfFile( 
  HANDLE hFileMappingObject, 
  DWORD dwDesiredAccess, 
  DWORD dwFileOffsetHigh, 
  DWORD dwFileOffsetLow, 
  DWORD dwNumberOfBytesToMap 
);

MapViewOfFile()函数负责把文件数据映射到进程的地址空间,参数hFileMappingObject为CreateFileMapping()返回的文件映像对象句柄。参数dwDesiredAccess则再次指定了对文件数据的访问方式,而且同样要与CreateFileMapping()函数所设置的保护属性相匹配。虽然这里一再对保护属性进行重复设置看似多余,但却可以使应用程序能更多的对数据的保护属性实行有效控制。MapViewOfFile()函数允许全部或部分映射文件,在映射时,需要指定数据文件的偏移地址以及待映射的长度。其中,文件的偏移地址由DWORD型的参数dwFileOffsetHigh和dwFileOffsetLow组成的64位值来指定,而且必须是操作系统的分配粒度的整数倍,对于Windows操作系统,分配粒度固定为64KB。

当然,也可以通过如下代码来动态获取当前操作系统的分配粒度:

SYSTEM_INFO   sinf;
GetSystemInfo(&sinf);
DWORD dwAllocationGranularity = sinf.dwAllocationGranularity;

参数dwNumberOfBytesToMap指定了数据文件的映射长度,这里需要特别指出的是,对于Windows   9x操作系统,如果MapViewOfFile()无法找到足够大的区域来存放整个文件映射对象,将返回空值(NULL);但是在Windows   2000下,MapViewOfFile()只需要为必要的视图找到足够大的一个区域即可,而无须考虑整个文件映射对象的大小。

在完成对映射到进程地址空间区域的文件处理后,需要通过函数UnmapViewOfFile()完成对文件数据映像的释放,该函数原型声明如下: BOOL   UnmapViewOfFile(LPCVOID   lpBaseAddress);

唯一的参数lpBaseAddress指定了返回区域的基地址,必须将其设定为MapViewOfFile()的返回值。在使用了函数MapViewOfFile()之后,必须要有对应的UnmapViewOfFile()调用,否则在进程终止之前,保留的区域将无法释放。除此之外,前面还曾由CreateFile()和CreateFileMapping()函数创建过文件内核对象和文件映射内核对象,在进程终止之前有必要通过CloseHandle()将其释放,否则将会出现资源泄漏的问题。

除了前面这些必须的API函数之外,在使用内存映射文件时还要根据情况来选用其他一些辅助函数。例如,在使用内存映射文件时,为了提高速度,系统将文件的数据页面进行高速缓存,而且在处理文件映射视图时不立即更新文件的磁盘映像。为解决这个问题可以考虑使用FlushViewOfFile()函数,该函数强制系统将修改过的数据部分或全部重新写入磁盘映像,从而可以确保所有的数据更新能及时保存到磁盘。

以下是一篇感觉有用的文章,提供参考:

映射文件的第一步是调用CreateFile函数来打开这个文件。为了确保被映射的文件不能被其他的进程写入,你应该使用专门的权限打开这个文件。另外,文件句柄要一直打开,直到进程不再需要它。一个得到专门的权限的简单方法是,将CreateFile的fdwShareMode参数设置为零。CreateFileMapping函数使用CreateFile函数返回的句柄来创建一个文件映射对象。
   CreateFileMapping函数给文件映射对象返回一个句柄。当创建文件视图的时候会用到这个句柄,使得你可以访问共享的内存。当你调用CreateFileMapping函数时,你要指定一个对象名(句柄,也就是打开的那个待映射文件),从文件中映射多少字节,以及映射内存的读写许可。第一个调用CreateFileMapping函数的进程会创建一个文件映射对象。如果文件映像已经存在,进程调用CreateFileMapping函数函数会得到这个文件映像对象的句柄。调用GetLastError函数,你就可以知道调用CreateFileMapping函数来创建或者打开一个文件映射对象是否成功了。GetLastError给创建的进程返回NO_ERROR,给之后的进程返回ERROR_ALREADY_EXISTS。
    如果CreateFileMapping函数中的权限标志和CreateFile函数中的权限标志不一致,则会执行失败。举个例子,读写一个文件:
    1、给CreateFile函数的fdwAccess参数附GENERIC_READ和GENERIC_WRITE。
    2、给CreateFileMapping函数的fdwProtect参数附PAGE_READWRITE。
    创建一个文件映射对象并不开辟物理内存,只是预定

文件映射大小
    文件映射对象的大小和映射的文件大小没有关系。但是,如果文件映射对象比文件要大,系统会在CreateFileMapping返回之前,扩大文件。如果文件映射对象比文件小,系统只从文件中映射指定的字节数。
   CreateFileMapping函数dwMaximumSizeHigh和dwMaximumSizeLow参数是你可以指定从文件中映射的字节数:
    1、如果你确实不想改变文件的大小(比如,映射一个只读文件时),调用CreateFileMapping函数并将dwMaximumSizeHigh和dwMaximumSizeLow参数设置为零。这样一来,文件映射对象的大小和文件的大小是一致的。否则,你应该估算一下已经完成的文件的大小,因为文件映射对象的大小是固定的;一旦被建立,他们的大小就不能被增大或减小。如果用这样的方式来映射一个长度为零的文件会引起ERROR_FILE_INVALID错误。程序应该检验文件的长度,并且拒绝长度为零的文件:
    2、以文件支持的文件映射对象的大小是受磁盘大小限制的。文件视图的大小,受最大的、未被预约的连续虚拟内存限制。这最多是2G减去进程已经预约的虚拟内存。
    你选择的文件映射大小,控制了你利用内存映射能“看到”的文件的范围。如果你创建了一个500Kb大小的文件映射对象,不管文件有多大,你只可以访问文件的前500Kb。既然创建更大的文件映射对象不会消耗系统资源,建立和文件一样大的映射对象(将CreateFileMapping的dwMaximumSizeHigh和dwMaximumSizeLow参数设置为零),及时你不需要查看整个文件。当创建文件视图以及访问他们的时候,才会消耗系统资源。
    加入你想要访问的部分不是从文件开头开始的,你就必须建立一个文件映射对象。对象的大小是你想要访问的那一部分的大小加上它在文件中的偏移量。

猜你喜欢

转载自blog.csdn.net/SwordArcher/article/details/82149764