logo资料库

CreateFileMapping的使用.doc

第1页 / 共15页
第2页 / 共15页
第3页 / 共15页
第4页 / 共15页
第5页 / 共15页
第6页 / 共15页
第7页 / 共15页
第8页 / 共15页
资料共15页,剩余部分请下载后查看
CreateFileMapping的使用
CreateFileMapping 的使用 测试创建和打开文件映射的时候老是得到"句柄无效"的错误, 仔细看了 MSDN 以后才发觉是函数认识不透, 这里把相关的解释翻译出来 HANDLE CreateFileMapping( HANDLE hFile, LPSECURITY_ATTRIBUTES lpAttributes, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCTSTR lpName ); 1) 物理文件句柄 //物理文件句柄 //安全设置 //保护设置 //高位文件大小 //低位文件大小 //共享内存名称 任何可以获得的物理文件句柄, 如果你需要创建一个物理文件无关的内存映射也无妨, 将它设置成为 0xFFFFFFFF(INVALID_HANDLE_VALUE)就可以了. 如果需要和物理文件关联, 要确保你的物理文件创建的时候的访问模式和"保护设置"匹配, 比如: 物理文件只读, 内存映射需要读写就会发生错误. 推荐你的物理文 件使用独占方式创建. 如果使用 INVALID_HANDLE_VALUE, 也需要设置需要申请的内存空间的大小, 无论物理文件句柄参数是否有效, 这样 CreateFileMapping 就可以创建一个和物理文件大小无 关的内存空间给你, 甚至超过实际文件大小, 如果你的物理文件有效, 而大小参数为 0, 则返回给你的是一个和物理文件大小一样的内存空间地址范围. 返回给你的文件映射 地址空间是可以通过复制, 集成或者命名得到, 初始内容为 0. 2) 保护设置 就是安全设置, 不过一般设置 NULL 就可以了, 使用默认的安全配置. 在 win2k 下如果需要进行限制, 这是针对那些将内存文件映射共享给整个网络上面的应用进程使 用是, 可以考虑进行限制.
3) 高位文件大小 弟兄们, 我想目前我们的机器都是 32 位的东东, 不可能得到超过 32 位进程所能寻址的私有 32 位地址空间, 一般还是设置 0 吧, 我没有也不想尝试将它设置超过 0 的 情况. 4) 低位文件大小 这个还是可以进行设置的, 不过为了让其他共享用户知道你申请的文件映射的相关信息, 我使用的时候是在获得的地址空间头部添加一个结构化描述信息, 记录内存 映射的大小, 名称等, 这样实际申请的空间就比输入的增加了一个头信息结构大小了, 我认为这样类似 BSTR 的方式应该是比较合理的. 5) 共享内存名称 这个就是我今天测试的时候碰壁的祸根, 因为为了对于内存进行互斥访问, 我设置了一个互斥句柄, 而名称我选择和命名共享内存同名, 之下就是因为他们使用共同 的 namespace 导致了错误, 呵呵. 7) 调用 CreateFileMapping 的时候 GetLastError 的对应错误 ERROR_FILE_INVALID 如果企图创建一个零长度的文件映射, 应有此报 ERROR_INVALID_HANDLE 如果发现你的命名内存空间和现有的内存映射, 互斥量, 信号量, 临界区同名就麻烦了 ERROR_ALREADY_EXISTS 表示内存空间命名已经存在 8) 相关服务或者平台的命名保留 Terminal Services: 命名可以包含 "Global" 或者 "Local" 前缀在全局或者会话名空间初级文件映射. 其他部分可以包含任何除了()以外的字符, 可以参考 Kernel Object Name Spaces. 摘要: 本文给出了一种方便实用的解决大文件的读取、存储等处理的方法,并结合相关程序代码对具体的实现过程进行了介绍。 引言
文件操作是应用程序最为基本的功能之一,Win32 API 和 MFC 均提供有支持文件处理的函数和类,常用的有 Win32 API 的 CreateFile()、WriteFile()、ReadFile()和 MFC 提 供的 CFile 类等。一般来说,以上这些函数可以满足大多数场合的要求,但是对于某些特殊应用领域所需要的动辄几十 GB、几百 GB、乃至几 TB 的海量存储,再以通常的文件处理 方法进行处理显然是行不通的。目前,对于上述这种大文件的操作一般是以内存映射文件的方式来加以处理的,本文下面将针对这种 Windows 核心编程技术展开讨论。 内存映射文件 内存映射文件与虚拟内存有些类似,通过内存映射文件可以保留一个地址空间的区域,同时将物理存储器提交给此区域,只是内存文件映射的物理存储器来自一个已经存在 于磁盘上的文件,而非系统的页文件,而且在对该文件进行操作之前必须首先对文件进行映射,就如同将整个文件从磁盘加载到内存。由此可以看出,使用内存映射文件处理存储 于磁盘上的文件时,将不必再对文件执行 I/O 操作,这意味着在对文件进行处理时将不必再为文件申请并分配缓存,所有的文件缓存操作均由系统直接管理,由于取消了将文件数 据加载到内存、数据从内存到文件的回写以及释放内存块等步骤,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。另外,实际工程中的系统往往需要在多个进 程之间共享数据,如果数据量小,处理方法是灵活多变的,如果共享数据容量巨大,那么就需要借助于内存映射文件来进行。实际上,内存映射文件正是解决本地多个进程间数据 共享的最有效方法。 内存映射文件并不是简单的文件 I/O 操作,实际用到了 Windows 的核心编程技术--内存管理。所以,如果想对内存映射文件有更深刻的认识,必须对 Windows 操作系统的内存 管理机制有清楚的认识,内存管理的相关知识非常复杂,超出了本文的讨论范畴,在此就不再赘述,感兴趣的读者可以参阅其他相关书籍。下面给出使用内存映射文件的一般方法: 首先要通过 CreateFile()函数来创建或打开一个文件内核对象,这个对象标识了磁盘上将要用作内存映射文件的文件。在用 CreateFile()将文件映像在物理存储器的位置通 告给操作系统后,只指定了映像文件的路径,映像的长度还没有指定。为了指定文件映射对象需要多大的物理存储空间还需要通过 CreateFileMapping()函数来创建一个文件映射 内核对象以告诉系统文件的尺寸以及访问文件的方式。在创建了文件映射对象后,还必须为文件数据保留一个地址空间区域,并把文件数据作为映射到该区域的物理存储器进行提 交。由 MapViewOfFile()函数负责通过系统的管理而将文件映射对象的全部或部分映射到进程地址空间。此时,对内存映射文件的使用和处理同通常加载到内存中的文件数据的处 理方式基本一样,在完成了对内存映射文件的使用时,还要通过一系列的操作完成对其的清除和使用过资源的释放。这部分相对比较简单,可以通过 UnmapViewOfFile()完成从进 程的地址空间撤消文件数据的映像、通过 CloseHandle()关闭前面创建的文件映射对象和文件对象。 内存映射文件相关函数 在使用内存映射文件时,所使用的 API 函数主要就是前面提到过的那几个函数,下面分别对其进行介绍:
HANDLE CreateFile(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); 函数 CreateFile()即使是在普通的文件操作时也经常用来创建、打开文件,在处理内存映射文件时,该函数来创建/打开一个文件内核对象,并将其句柄返回,在调用该函数 时需要根据是否需要数据读写和文件的共享方式来设置参数 dwDesiredAccess 和 dwShareMode,错误的参数设置将会导致相应操作时的失败。 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,几乎可以满足任何大数据量 文件处理场合的要求。 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()函数,该函数强制系统将修改过的数据部分或全部重新写 入磁盘映像,从而可以确保所有的数据更新能及时保存到磁盘。 使用内存映射文件处理大文件应用示例 下面结合一个具体的实例来进一步讲述内存映射文件的使用方法。该实例从端口接收数据,并实时将其存放于磁盘,由于数据量大(几十 GB),在此选用内存映射文件进行 处理。下面给出的是位于工作线程 MainProc 中的部分主要代码,该线程自程序运行时启动,当端口有数据到达时将会发出事件 hEvent[0],WaitForMultipleObjects()函数等待 到该事件发生后将接收到的数据保存到磁盘,如果终止接收将发出事件 hEvent[1],事件处理过程将负责完成资源的释放和文件的关闭等工作。下面给出此线程处理函数的具体实 现过程: …… // 创建文件内核对象,其句柄保存于 hFile HANDLE hFile = CreateFile("Recv1.zip", GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN,
NULL); // 创建文件映射内核对象,句柄保存于 hFileMapping HANDLE hFileMapping = CreateFileMapping(hFile,NULL,PAGE_READWRITE, 0, 0x4000000, NULL); // 释放文件内核对象 CloseHandle(hFile); // 设定大小、偏移量等参数 __int64 qwFileSize = 0x4000000; __int64 qwFileOffset = 0; __int64 T = 600 * sinf.dwAllocationGranularity; DWORD dwBytesInBlock = 1000 * sinf.dwAllocationGranularity; // 将文件数据映射到进程的地址空间 PBYTE pbFile = (PBYTE)MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, (DWORD)(qwFileOffset>>32), (DWORD)(qwFileOffset&0xFFFFFFFF), dwBytesInBlock); while(bLoop) { // 捕获事件 hEvent[0]和事件 hEvent[1] DWORD ret = WaitForMultipleObjects(2, hEvent, FALSE, INFINITE); ret -= WAIT_OBJECT_0; switch (ret) { // 接收数据事件触发
case 0: // 从端口接收数据并保存到内存映射文件 nReadLen=syio_Read(port[1], pbFile + qwFileOffset, QueueLen); qwFileOffset += nReadLen; // 当数据写满 60%时,为防数据溢出,需要在其后开辟一新的映射视图 if (qwFileOffset > T) { T = qwFileOffset + 600 * sinf.dwAllocationGranularity; UnmapViewOfFile(pbFile); pbFile = (PBYTE)MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, (DWORD)(qwFileOffset>>32), (DWORD)(qwFileOffset&0xFFFFFFFF), dwBytesInBlock); } break; // 终止事件触发 case 1: bLoop = FALSE; // 从进程的地址空间撤消文件数据映像 UnmapViewOfFile(pbFile); // 关闭文件映射对象 CloseHandle(hFileMapping); break;
分享到:
收藏