内存映射文件的应用

2021年10月3日 14点热度 0条评论 来源: Simple Simple

内存映射文件的应用

     内存映射文件是windows开发下常用的一种技术,既可以用来读写磁盘上的大文件,也可以用来实现进程间的通信,本文主要对内存映射的几大用途做一个简述。

一,操作系统加载EXE和DLL

     当一个应用程序启动时,操作系统首先会调用CreateFile来打开磁盘上的.exe文件。接着系统会调用CreateFileMapping来创建文件映射对象。最后系统会以新创建的进程的名义调用MapViewOfFileEx(),这样就把.exe文件映射到了进程的地址空间中。这里使用MapViewOfFileEx的原因是为了将文件映射到进程的指定基地址处(32位程序默认基地址为0x400000)。然后系统创建进程的主线程,在映射得到的视图中取得可执行代码的第一个字节的地址,把该地址放到线程的指令指针中,最后CPU执行其中的代码。

     这时再启动该应用程序的第二个实例,操作系统会发现该.exe文件已经有一个文件映射对象,那么系统会将该文件映射对象视图映射到新创建的进程的地址空间中。显然,由于物理内存中包含.exe文件可执行代码的那些页面为两个进程(或多个进程)所共享,因此内存的使用率更高。

     但是由于该.exe的所有实例都是共享同一个内存映射,那么如果其中一个实例修改了数据页面中的一些全局变量,其他实例也会被修改,当然,操作系统也考虑到了这一点,通过内存管理系统的写时复制特性来防止这种情况的发生。任何时候当应用程序试图写入内存映射文件的时候,系统首先会截获到这类尝试,接着为应用程序试图写入的内存页面分配一块新的内存,然后复制页面内容,最后让应用程序写入到刚分配的内存块。这样,其他实例不会受到任何影响。

二,读写大文件

     C++中提供了类似fopen,fstream等函数来操作文件,这些函数的原理都是直接对文件进行IO读取和缓存,当处理的文件只有几k,几十k,或者几百k的时候,使用这些函数都没有问题。但是如果处理的文件是几百兆,几G的大小,频繁的IO操作会很耗时,甚至由于需要把这么大的数据都加载到程序缓存中,还会影响到程序的内存,因此,我们采用共享内存的方式来解决这个问题。

     共享内存读取大文件主要有以下几个步骤:
1, 创建或打开一个文件内核对象,该对象标识了我们想要用作内存映射文件的那个磁盘文件。
2, 创建一个文件映射内核对象,来告诉操作系统文件的大小以及我们打算如何访问文件。
3, 告诉操作系统把文件映射对象的部分或全部映射到进程的地址空间中。

代码示例如下:

int Func()
{
	clock_t cStart, cEnd;
	cStart = clock();

	//打开一个文件内核对象
	HANDLE hFile = CreateFile(L"test.txt",	//文件名称
		GENERIC_WRITE | GENERIC_READ,	//访问文件的方式(可读可写)
		0,								//此文件共享的方式(0表示其他任何试图打开文件的操作都会失败)
		NULL,							//指向SECURITY_ATTRIBUTES的指针,用以指示安全信息以及句柄继承等属性
		OPEN_EXISTING,					//指定打开文件的标志(OPEN_EXISTING表示打开已有的文件,如果不存在则打开失败)
		FILE_ATTRIBUTE_NORMAL,			//文件属性,默认
		NULL							//文件句柄传空,如果非空,则忽略上面那个参数,使用当前文件句柄的属性
	);
	if (INVALID_HANDLE_VALUE == hFile)
	{
		printf("CreateFile Failed!\n");
		return 0;
	}

	//获取文件大小
	DWORD dwFileSize = GetFileSize(hFile, NULL);

	//创建文件映射内核对象
	HANDLE hFileMap = CreateFileMapping(hFile,	//文件内核对象句柄
		NULL,									//指向SECURITY_ATTRIBUTES的指针,用以指示安全信息以及句柄继承等属性
		PAGE_READWRITE,							//文件保护属性(可读可写)
		0,										//文件大小高位值,如果文件小于4G,则填0
		dwFileSize + 1,							//文件大小低位值,PAGE_READ属性可以直接填0,PAGE_READWRITE的话要>=实际文件大小
		L"FileMapTest"							//文件映射对象的名称
	);
	if (INVALID_HANDLE_VALUE == hFileMap)
	{
		printf("CreateFile Failed!\n");
		return 0;
	}

	//将文件的数据映射到进程的地址空间中
	PVOID pvFile = MapViewOfFile(hFileMap,
		FILE_MAP_WRITE, 0, 0, 0);

	//操作pvFile来遍历数据
	char *pData = (char*)pvFile;
	for (int i = 0; i < dwFileSize; i++)
	{
		
	}

	cEnd = clock();
	printf("MapViewOfFile: %.6f\n", double(cEnd - cStart) / CLOCKS_PER_SEC);

	//释放资源
	UnmapViewOfFile(pvFile);

	CloseHandle(hFileMap);

	CloseHandle(hFile);
}

三,进程间通信

     Windows提供了多种机制可以使得应用程序之间可以快速地,方便地共享数据和信息,包括剪切板,套接字,windows消息(尤其是WM_COPYDATA),DDE,RPCDEN等。在windows系统中,在同一台机器上共享数据的最底层的机制就是内存映射文件,刚才提到的那些进程间通信方式归根结底都会用到内存映射文件。因此如果要求低开销和高性能,内存映射文件无疑是同一台机器上的多进程通信最好的方法。

     举例说明,有一个管理客户端,有一个专门处理业务的后台进程,后台进程处理业务的数据量(比如行情数据量,代码数据量,查询笔数等信息)需要同步到管理端界面显示,这两个进程之间的通信就是通过内存映射来实现。由于这个例子不涉及到多个进程同时写数据,所以不需要考虑到加锁,如果存在多进程写的情况,需要加内核对象锁。

     管理客户端代码:

//创建文件映射对象
	char szMapFileName[MAX_PATH] = { 0 };
	if (NULL == m_hMapHandle)
	{
		_snprintf(szMapFileName, sizeof(szMapFileName) - 1, "statistic_map_%s.dat", g_cHqissueName);
		m_hMapHandle = CreateFileMapping(
			(HANDLE)INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(STATISTIC_DATA), szMapFileName);
		if (m_hMapHandle == NULL)
		{
			return -1;
		}
	}

	//将文件映射到程序的内存地址
	if (NULL == m_pStatisticData)
	{
		m_pStatisticData = (STATISTIC_DATA *)MapViewOfFile(m_hMapHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0);
		if (m_pStatisticData == NULL)
		{
			CloseHandle(m_hMapHandle);
			m_hMapHandle = NULL;
			return -2;
		}

		memset(m_pStatisticData, 0, sizeof(STATISTIC_DATA));
		m_pStatisticData->nSize = sizeof(STATISTIC_DATA);
		m_pStatisticData->dwProcessID = GetCurrentProcessId();
	}

     其中STATISTIC_DATA是我们共享的数据结构。m_pStatisticData也就是映射到当前进程中的地址,直接对其进行访问赋值。
     后台进程代码:

m_hMapHandle = OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, "statistic_map_hqissue1.dat");
		if (!m_hMapHandle)
		{
			return;
		}

		m_pStatisticData = (STATISTIC_DATA*)MapViewOfFile(m_hMapHandle, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
		if (!m_pStatisticData)
		{
			return;
		}

     直接打开命名的内存映射文件,映射到当前进程中,访问m_pStatisticData即可取值。

    原文作者:Simple Simple
    原文地址: https://blog.csdn.net/bajianxiaofendui/article/details/91317086
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。