二、映射到内存的数据文件
Windows系统可以使我们能够把数据文件映射到进程地址空间中,这样一来,对大型数据流进行操作就非常容易。
以颠倒文件内容为例子。四种方法实现。
方法1:一个文件,一块缓存
实现方法具体步骤:
- 分配足够大的内存存放整个文件
- 打开文件,读取文件到内存中,关闭文件。
- 交换第一个字节和最后一个字节,交换第二个字节和倒数第二个字节,以此类推。
缺点:
- 根据文件大小分配内存,如果文件过大,超过2GB,32位应用程序无法调拨那么大的物理存储器,大文件需要其他方法。
- 将颠倒顺序的文件内存写入文件过程中处理被中断,文件内容会被破坏。避免方法是将原始文件先复制一份副本,整个过程成功完成,再删除文件副本。这样需要额外的磁盘空间。
方法2:两个文件,一块缓存
实现方法具体步骤:
- 打开已有文件并在磁盘创建一个长度为0的新文件
- 接着分配一块较小的内部缓存,比如8KB。
- 将文件指针定位到原始文件末尾减去8KB的地方。
- 将8K的内容读取到缓存中,颠倒缓存的内容。
- 将颠倒后的内容写入新创建的文件。
- 重复步骤,定位文件指针,读取文件,颠倒缓存,写入文件的过程一直继续,直到到达原始文件的起始位置。
(如果文件的长度不是8KB的整数倍,需要特殊处理) - 完全处理完原始文件后,将两个文件都关闭并删除原始文件。
优缺点:
- 只分配8KB,内存使用率更高,但是需要文件指针定位操作,所以处理速度慢。
- 新文件逐渐增大,到1G。比实际需要的磁盘空间多了整整1GB。
方法3:一个文件,两块缓存。
实现方法具体步骤:
- 程序初始化时分配两块大小为8KB的缓存。
- 把文件开始的8KB内容读取到第一块缓存中,把文件末尾的8KB内容读取到另一块缓存中。
- 然后把内容颠倒,第一块缓存写回到文件末尾,第二块缓存写回到文件开头i。
- 这样过程一直继续,每次都把靠近文件开头的8KB和靠近文件尾部的8KB内容交换
(如果文件大小不是,16KB的整数倍,最后两块8KB缓存会有重叠,需要特殊处理)
优缺点:
- 整个过程所有数据读取写入到同一个文件,不需要额外磁盘空间。
- 处理过程被打断,可能会导致数据文件损坏。(同方法1)
方法4:一个文件,零个缓存。
实现方法具体步骤:
- 使用内存映射文件来颠倒文件内容时,我们先打开文件并向系统预订一块虚拟地址空间区域。
- 接着让系统把文件的第一个字节映射到该区域的第一个字节。
- 然后就可以访问这块虚拟内存区域,就好像它实际上包含了文件一样。事实上,如果要颠倒的是一个文本文件,而且文件末尾字节为0,则可以把这个文件当作内存中的一个字符串来处理,在这种情况下,直接调用c运行库函数_tcsrev就能颠倒文件中的数据。
优缺点:
- 这种方法最大的优点就是让系统为我们处理文件缓存有关操作时,无需缓存。
- 但遗憾的是,使用内存映射文件的时候,如果操作过程被中途打断(如断电),仍然可能导致数据被破坏
三、使用内存映射文件
使用内存映射文件,需要执行下面三个步骤:
- 创建或打开一个文件内核对象,该对象标识了我们想要用作内存映射文件的哪 个磁盘文件。
- 创建一个文件映射内核对象,来告诉系统文件的大小以及我们打算如何访问文件
- 告诉系统把文件映射对象的部分或全部进程地址空间中。
使用完后清理工作,需要执行以下三个步骤:
- 告诉系统从进程地址空间中取消对文件映射内核对象的映射
- 关闭文件映射内存对象
- 关闭文件内核对象
略,暂时不看 需要实践代码。https://www.cnblogs.com/zhangdongsheng/p/3269515.html
第1步:创建或打开 文件内核对象
第2步 创建文件映射内存对象
第3步 将文件的数据映射到进程地址空间
第4步 从进程的地址空间撤销对文件数据的映射
第5步和第6步 关闭文件映射对象
File Reverse 示例程序
四、用内存映射文件来处理大文件
如何将一个16EB的文件映射到一个较小的地址空间中?
一开始,我们只映射文件开头的部分到视图中,完成对文件的第一个视图的访问后,我们可以撤销对文件这一部分的远射,然后把文件的另一部分映射到视图中。我们一直重复这个过程,直到完成对整个文件的访问。
五、内存映射文件和一致性
结论: 系统允许我们把同一个文件中的数据映射到多个视图中。只要我们映射的是同一个文件映射对象,那么系统会确保各视图中的数据是一致的。
原因: 这是因为即使该页面被多次映射到进程的虚拟地址空间中,系统也还是在同一个内存页面中保存被映射的数据。如果多个进程把同一个数据文件映射到多个视图中,那么数据也仍然会是一致 的,这是因为数据文件中的每个页面在内存中只有一份——但这些内存页面会被映射到多个进程的地址空间中。
说明: Windows允许我们以同一个数据文件为后备存储器来创建多个文件映射对象。Windows并不保证这些不同的文件映射对象的各个视图是一致的。系统只保证在同一文件映射对象的多个视图间保持一致。
简而言之,就是多个程序读取映射文件时,各个程序是读取的是一致的,因为映射文件只有一份,其他程序的地址空间都是指向同一份映射文件。
六、给内存映射文件的指定基地址
在调用VirtualAlloc的时候,我们可以建议系统在指定的基地址预定地址空间。同样,也可以用MapViewOfFileEx函数来代替MapViewOfFile函数,这样就能建议系统把文件映射到指定的地址:
PVOID MapViewOfFileEx( HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap, PVOID pvBaseAddress );
除了最后一个参数 pvBaseAddress ,这个函数的所有参数和返回值都与MapViewOfFile函数完全相同。我们可以用这个参数来给要映射的文件制定一个目标地址。同VirtualAlloc函数一样,指定的目标地址必须是分配粒度的整数倍。 使用内存映射文件跨进程共享数据的时候,MapViewOfFileEx非常有用。给共享的数据文件在不同的进程指定相同的基地址。
七、内存映射文件的实现细节
在进程能够从自己的地址空间中访问内存映射文件的数据之前,Windows要求进程先调用MapViewOfFile。不同进程调用MapViewOfFile时返回的内存地址,很可能是不同的。
八、用内存映射文件再进程间共享数据
Windows提供了多种机制,使得应用程序之间能够快速、方便地共享数据和信息。在Windows中,在同一台机器上共享数据的最底层的机制就是内存映射文件。
让 我们来看一个例子:启动应用程序。当一个应用程序启动时,系统会先调用CreateFile来打开磁盘上的.exe文件。接着系统会调用 CreateFileMapping来创建文件映射对象。最后系统会以新创建的进程的名义调用MapViewOfFileEx(并传入SEC_IMAGE 标志),这样就把.exe文件映射到了进程的地址空间中。值所以调用MapViewOfFileEx而不是MapViewOfFile,是为了把文件映射 到指定的基地址,这个基地址保存在.exe的PE文件头中。系统然后创建进程的主线程,在映射得到的视图中取得可执行代码的第一个字节的地址,把该地址放 到线程的指令指针中,最后让CPU开始执行其中的代码。
如 果用户启动同一个应用程序的第二个实例,那么系统会发现该.exe文件已经有一个文件映射对象,因此就不会再创建一个新的文件对象或文件映射对象。取而代 之的是,系统会再次映射.exe文件的一个视图,但这次是在新创建的进程的地址空间中。至此,系统已经把同一个文件同时映射到了两个地址空间中。显然,由 于物理内存中包含.exe文件可执行代码的那些页面为两个进程所共享,因此内存的使用率更高。
九、以页交换文件为后背存储器的内存映射文件
Microsoft加入了相应的支持,让系统能够创建以页交换文件为后备存储器的内存映射文件,这样就不需要用磁盘上专门的文件来作为后备存储器了。这种方法和为磁盘文件创建内存映射文件的方法几乎完全相同,甚至更简单。 一方面,由于不必创建或打开一个专门的磁盘文件,因此不需要调用CreateFile。
我们只需要像原来那样调用CreateFileMapping,并将INVALID_HANDLE_VALUE作为hFile参数传入。这告诉系统我们创建的文件映射对象的物理存储器不是磁盘上的文件,而是希望系统从页交换文件中调拨物理存储器
。
所需分配的存储器大小由CreateFileMapping的dwMaximumSizeHigh和dwMaximumSizeLow参数决定。
十、 稀疏调拨的内存映射文件
CreateFileMapping为我们提供了一种方法,即在fdwProtect参数中指定SEC_RESERVE或SEC_COMMIT标志。这样我们把先前的那个电子的数据作为一个文件映射对象来共享,但又不希望在一开始就给它调拨所有物理存储器。
SEC_RESERVE:系统不会从页交换文件中调拨物理存储器,它只返回文件映射对象的句柄。现在我们可以调用MapViewOfFile来给这个文件映射对象创建一个视图。MapViewOfFile会预订地址空间,但不会调任何物理存储器。设计图访问会违规。通过使用SEC_RERVER和VirtualAlloc我们不仅能与其它进程共享电子表格的数组,而且还能高效的使用物理存储器。如果内存映射文件是通过SEC_RESERVE标志得到的,便不能用VirtualFree来撤销调拨给它的存储
NT文件系统(NTFS)提供了对稀疏文件的支持。这是一项非常棒的特征。我们可以用这项特性来创建和处理稀疏内存映射文件,这样一来,存储器就不必总是在页交换文件中,而可以在普通文件中。
假设我们要创建一个内存映射文件来存储录音数据。当用户说话时,我们希望把数字音频数据写入到内存缓存中,并以磁盘文件为内存缓存的后备存储器。一个部分调拨的内存映射文件当然是最简单高效的办法。问题是我们不知道用户在单击停止按钮前会说多久,可能是五分钟,但也可以是5小时。差距不可谓不大!我们需要一个足够大的文件来保存这些数据。但是,在使用稀疏调拨的内存映射文件时,大小并没有多大关系。
十一、文件映射进行进程间通信
不同进程对信号量Count进行累加
#include <iostream> #include <Windows.h> using namespace std; int main() { HANDLE hMap; PINT lpMapAddr; // get file mapping's handle hMap = OpenFileMapping( FILE_MAP_ALL_ACCESS, // 权限:所有 FALSE, // 不继承句柄 "TEST_Map" // 名字 ); if (NULL == hMap) { // init hMap = CreateFileMapping( INVALID_HANDLE_VALUE, // 不是真实的文件,所以写 INVALID_HANDLE_VALUE NULL, // 同上 PAGE_READWRITE, // 该句柄对文件映射可读可写 0, // 与下一个参数连用,表示文件映射的大小 1024, // 与上一个参数连用,表示文件映射的大小 "TEST_Map" // 名字 ); if (NULL == hMap) { goto end; } lpMapAddr = (PINT)MapViewOfFile( hMap, // 文件映射句柄 FILE_MAP_ALL_ACCESS, // 权限:所有 0, // 与下一个参数连用,表示文件映射起始地址偏移 0, // 与上一个参数连用,表示文件映射起始地址偏移 0 // 映射整个文件映射对象 ); *lpMapAddr = 1; } // get file mapping's address lpMapAddr = (PINT)MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0); // read cout << "instance's count : " << *lpMapAddr << endl; // write *lpMapAddr = *lpMapAddr + 1; cout << "finished." << endl; getchar(); end: if (hMap) CloseHandle(hMap); getchar(); return 0; }
总结
1.操作系统中,程序启动过程?加载dll错误处理?
共享内存和文件映射区别?
共享内存时一种特殊的文件映射,使用的是磁盘页交换文件为内存交换文件