首页
论坛
专栏
课程

[原创]Windows系统程序设计之内存映射

2006-8-11 22:03 14425

[原创]Windows系统程序设计之内存映射

2006-8-11 22:03
14425
Windows系统程序设计之内存映射

【作者】北极星2003
【来源】看雪技术论坛(bbs.pediy.com)
【时间】2006年8月11日

        相信对于大家来说,内存映射技术已经是个很熟悉的技术了。在这里我只是作个总结,希望对那些新手朋友有帮助。

        内存映射文件通常有两个用途:一是内存映射磁盘文件,这对于大数据文件的处理比较适合;二是共享内存,作为进程间通信的一种方式。

实例一:内存映射实例--文件分割器



1、        设计目标
设计文件分割器,作为演示内存映射磁盘文件实例。

2、        设计思路
        内存映射磁盘文件的一般步骤:
(1)        打开文件
(2)        创建内存映射内核对象
(3)        映射文件视图
(4)        在内存中处理数据
(5)        卸载文件视图
(6)        关闭内存映射内核对象
(7)        关闭文件

分割思路,把原文件按照设置的文件块大小依次映射到内存,然后生成一个新文件,下图中蓝色箭头所示。
还原思路,把子文件依次映射到内存,然后再回写到原文件的各个部分,下图中红色箭头所示。



3、        详细设计
(1)        定义文件信息块结构
文件信息块保存着分割文件时的信息,也是还原时的必备文件。

typedef struct _FILE_ITEM_INFO {
	DWORD	dwLowFileSize ;		// 文件大小低字段
	DWORD	dwHighFileSize ;	// 文件大小高字段
	DWORD	dwPartSize ;			// 文件块大小
	DWORD	dwPartNum ;		// 文件块数量
	CString	szFileName ;			// 源文件名
} FILE_ITEM_INFO ;
typedef FILE_ITEM_INFO* PFILE_ITEM_INFO ;


(2)        文件分割实现部分

DWORD CFD::FDFileDivide	( PFILE_ITEM_INFO pItem )
{
	// 删除该文件原有的分块文件
	this->DeleteAllPartFiles ( pItem->szFileName ) ;

	// 创建信息文件
	HANDLE hFile = CreateFile ( 
		pItem->szFileName + ".INFO",  
		GENERIC_READ|GENERIC_WRITE, 
		FILE_SHARE_READ|FILE_SHARE_WRITE, 
		NULL, 
		CREATE_ALWAYS, 
		FILE_ATTRIBUTE_NORMAL, 
		NULL ) ;
	if ( hFile == INVALID_HANDLE_VALUE )
		return GetLastError() ;
	
	// 写入信息文件
	DWORD dwWriteByte ;
	WriteFile ( hFile, &(pItem->dwLowFileSize),	sizeof(DWORD),	&dwWriteByte, NULL ) ;
	WriteFile ( hFile, &(pItem->dwPartSize),	sizeof(DWORD),	&dwWriteByte, NULL ) ;
	WriteFile ( hFile, &(pItem->dwPartNum),		sizeof(DWORD),	&dwWriteByte, NULL ) ;
	CloseHandle ( hFile ) ;

	// 打开目标文件
	hFile = CreateFile ( 
		pItem->szFileName,  
		GENERIC_READ, 
		FILE_SHARE_READ|FILE_SHARE_WRITE, 
		NULL, 
		OPEN_EXISTING, 
		FILE_ATTRIBUTE_NORMAL, 
		NULL ) ;
	if ( hFile == INVALID_HANDLE_VALUE )
		return GetLastError() ;

	// 创建文件内存映射内核对象
	HANDLE hMapFile = CreateFileMapping ( 
		hFile, 	
		NULL, 
		PAGE_READONLY, 
		0, 
		0, 
		NULL ) ;
	if ( hMapFile == NULL )
	{
		CloseHandle ( hFile ) ;
		return GetLastError() ;
	}


	CString TempStr ;
	DWORD	dwCurAddr = 0, dwCurPart = 0 ;
	LPVOID lpMapAddr = 0 ;

	// 分块循环映射文件
	for ( UINT i = 1; i <= pItem->dwPartNum; i++ )
	{
		dwCurPart = pItem->dwLowFileSize - dwCurAddr ;
		if ( dwCurPart > pItem->dwPartSize )
			dwCurPart = pItem->dwPartSize ;

		lpMapAddr = MapViewOfFile ( hMapFile, FILE_MAP_READ, 0, dwCurAddr, dwCurPart ) ;
		if ( lpMapAddr == NULL )
		{
			CloseHandle ( hMapFile ) ;
			CloseHandle ( hFile ) ;
			return GetLastError() ;
		}

		dwCurAddr += dwCurPart ;

		TempStr.Format ( "%s.PART_%d", pItem->szFileName, i ) ;
		DeleteFile ( TempStr ) ;
		
		HANDLE hNewFile = CreateFile ( 
			TempStr,
			GENERIC_READ|GENERIC_WRITE, 
			FILE_SHARE_READ|FILE_SHARE_WRITE, 
			NULL, 
			CREATE_ALWAYS, 
			FILE_ATTRIBUTE_NORMAL, 
			NULL ) ;
		if ( hFile == INVALID_HANDLE_VALUE )
			return GetLastError() ;
		
		HANDLE hNewMapFile = CreateFileMapping (
			hNewFile, 	
			NULL, 
			PAGE_READWRITE, 
			0, 
			dwCurPart, 
			NULL ) ;
		if ( hNewMapFile == NULL )
		{
			CloseHandle ( hNewFile ) ;
			return GetLastError() ;
		}

		LPVOID lpNewMapAddr = MapViewOfFile ( hNewMapFile, FILE_MAP_WRITE, 0, 0, 0 ) ;
		if ( lpMapAddr == NULL )
		{
			CloseHandle ( hNewMapFile ) ;
			CloseHandle ( hNewFile ) ;
			return GetLastError() ;
		}

		memcpy ( lpNewMapAddr, lpMapAddr, dwCurPart ) ;
		
		UnmapViewOfFile ( lpMapAddr ) ;
		UnmapViewOfFile ( lpNewMapAddr ) ;
		CloseHandle ( hNewMapFile ) ;
		CloseHandle ( hNewFile ) ;
	}

	CloseHandle ( hMapFile ) ;
	CloseHandle ( hFile ) ;
	return 0 ;
}



(3)        文件还原部分

DWORD CFD::FDFileConnect	( PFILE_ITEM_INFO pItem )
{
	DeleteFile ( pItem->szFileName ) ;

	// 打开目标文件
	HANDLE hFile = CreateFile ( 
		pItem->szFileName,  
		GENERIC_READ|GENERIC_WRITE, 
		FILE_SHARE_READ|FILE_SHARE_WRITE, 
		NULL, 
		CREATE_ALWAYS, 
		FILE_ATTRIBUTE_NORMAL, 
		NULL ) ;
	if ( hFile == INVALID_HANDLE_VALUE )
		return GetLastError() ;

	// 创建文件内存映射内核对象
	HANDLE hMapFile = CreateFileMapping ( 
		hFile, 	
		NULL, 
		PAGE_READWRITE, 
		0, 
		pItem->dwLowFileSize, 
		NULL ) ;
	if ( hMapFile == NULL )
	{
		CloseHandle ( hFile ) ;
		return GetLastError() ;
	}

	CString TempStr ;
	DWORD	dwCurAddr = 0, dwCurPart = 0 ;
	LPVOID lpMapAddr = 0 ;

	// 分块循环映射文件
	for ( UINT i = 1; i <= pItem->dwPartNum; i++ )
	{
		dwCurPart = pItem->dwLowFileSize - dwCurAddr ;
		if ( dwCurPart > pItem->dwPartSize )
			dwCurPart = pItem->dwPartSize ;

		lpMapAddr = MapViewOfFile ( hMapFile, FILE_MAP_WRITE, 0, dwCurAddr, dwCurPart ) ;
		if ( lpMapAddr == NULL )
		{
			CloseHandle ( hMapFile ) ;
			CloseHandle ( hFile ) ;
			return GetLastError() ;
		}

		dwCurAddr += dwCurPart ;

		TempStr.Format ( "%s.PART_%d", pItem->szFileName, i ) ;
		
		HANDLE hNewFile = CreateFile ( 
			TempStr,
			GENERIC_READ, 
			FILE_SHARE_READ|FILE_SHARE_WRITE, 
			NULL, 
			OPEN_EXISTING, 
			FILE_ATTRIBUTE_NORMAL, 
			NULL ) ;
		if ( hFile == INVALID_HANDLE_VALUE )
		{
			UnmapViewOfFile ( lpMapAddr ) ;
			CloseHandle ( hMapFile ) ;
			CloseHandle ( hFile ) ;
			return GetLastError() ;
		}
		
		HANDLE hNewMapFile = CreateFileMapping (
			hNewFile, 	
			NULL, 
			PAGE_READONLY, 
			0, 
			0, 
			NULL ) ;
		if ( hNewMapFile == NULL )
		{	
			UnmapViewOfFile ( lpMapAddr ) ;
			CloseHandle ( hMapFile ) ;
			CloseHandle ( hFile ) ;
			CloseHandle ( hNewFile ) ;
			return GetLastError() ;
		}

		LPVOID lpNewMapAddr = MapViewOfFile ( hNewMapFile, FILE_MAP_READ, 0, 0, 0 ) ;
		if ( lpMapAddr == NULL )
		{	
			UnmapViewOfFile ( lpMapAddr ) ;
			CloseHandle ( hMapFile ) ;
			CloseHandle ( hFile ) ;
			CloseHandle ( hNewMapFile ) ;
			CloseHandle ( hNewFile ) ;
			return GetLastError() ;
		}

		memcpy ( lpMapAddr, lpNewMapAddr, dwCurPart ) ;
		FlushViewOfFile ( lpMapAddr, dwCurPart ) ;

		UnmapViewOfFile ( lpMapAddr ) ;
		UnmapViewOfFile ( lpNewMapAddr ) ;
		CloseHandle ( hNewMapFile ) ;
		CloseHandle ( hNewFile ) ;
	}

	CloseHandle ( hMapFile ) ;
	CloseHandle ( hFile ) ;

	this->DeleteAllPartFiles ( pItem->szFileName ) ;

	return 0 ;
}



(4)        性能测试
对于小文件没有测试的必要,这里选个较大的文件作为测试对象。
测试平台信息:P4 2.26GHz + WinXP sp2 + 256 MB内存
测试对象大小:203 MB
如果块大小为 10 MB, 分割需6秒左右,还原(合并)需7秒左右
如果块大小为 1 MB, 分割需13秒左右,还原(合并)需12秒左右

实例二:共享内存示例




1、        设计目标
在两个相互独立的进程间通过文件映射对象来分配和访问同一个共享内存块的应用实例。

2、        设计思路
这个例子比较简单,从写入端进程把信息写到共享内存,然后读取端进程从共享内存中读取信息。



3、        详细设计
(1)        创建共享内存

void CShareMemDlg::OnSetup() 
{
	if ( bSetup )
	{
		this->MessageBox ( "已经建立共享内存文件" ) ;
		return ;
	}

	hMapFile = CreateFileMapping (
		INVALID_HANDLE_VALUE,
		NULL,
		PAGE_READWRITE | SEC_COMMIT,
		0,
		uShareMemSize,
		"ShareMemSample" ) ;
	if ( hMapFile == NULL )
	{
		this->MessageBox ( "创建共享内存映射文件失败!" ) ;
		return ;
	}

	lpBase	= MapViewOfFile ( 
		hMapFile, 
		FILE_MAP_READ|FILE_MAP_WRITE,
		0,
		0,
		0 ) ;
	if ( this->lpBase == NULL )
	{
		this->MessageBox ( "创建共享内存映射文件失败!" ) ;
		return ;
	}

	this->MessageBox ( "创建内存共享成功!" ) ;
	bSetup = true ;
}


        (2)从共享内存中读取信息
void CShareMemReadDlg::OnRead() 
{
	HANDLE hMapFile = OpenFileMapping ( FILE_MAP_READ, FALSE, "ShareMemSample"); 
	if ( hMapFile == NULL )
	{
		DWORD dwErrorCode = GetLastError () ;
		this->MessageBox ( "打开共享内存映射文件失败!" ) ;
		return ;
	}

	LPVOID lpBase = MapViewOfFile ( 
		hMapFile, 
		FILE_MAP_READ,
		0,
		0,
		0 ) ;
	if ( lpBase == NULL )
	{
		DWORD dwErrorCode = GetLastError () ;
		this->MessageBox ( "打开共享内存映射文件失败!" ) ;
		return ;
	}

	char *pReadInfo = (char*)lpBase ;
	UINT uLength = strlen ( pReadInfo ) ;	
	
	memcpy ( this->m_ReadInfo.GetBuffer( uLength + 1 ), lpBase, uLength + 1 ) ;
	this->UpdateData ( false ) ;

	UnmapViewOfFile ( lpBase ) ;
	CloseHandle ( hMapFile ) ;
}



【参考文献】
[1].Windows核心编程 Jeffrey Richter著

【版权声明】必须注明原创于看雪技术论坛(bbs.pediy.com) 及作者,并保持文章的完整性。



[公告][征集寄语] 看雪20周年年会 | 感恩有你,一路同行

上传的附件:
最新回复 (16)
小虾 10 2006-8-11 22:12
2
0
北极星兄又出手了,学习。
kanxue 8 2006-8-11 22:14
3
0
北极星的文章喜欢看
prince 16 2006-8-11 22:45
4
0
太强了太强了!
笨奔 1 2006-8-11 23:35
5
0
呵.呵.随然看过,得支持一下,,
bookworm 3 2006-8-12 01:22
6
0
MSDN:

To guard against an access violation, use structured exception handling to protect any code that writes to or reads from a memory mapped view. For more information, see Reading and Writing From a File View.

To have a mapping with executable permissions, an application must call CreateFileMapping with either PAGE_EXECUTE_READWRITE or PAGE_EXECUTE_READ, and then call MapViewOfFile with FILE_MAP_EXECUTE | FILE_MAP_WRITE or FILE_MAP_EXECUTE | FILE_MAP_READ.

Example:

DWORD dwLength;

__try
{
   dwLength = *((LPDWORD) lpMapAddress);
}
__except(GetExceptionCode()==EXCEPTION_IN_PAGE_ERROR ?
            EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
   // Failed to read from the view.
}

__try
{
   *((LPDWORD) lpMapAddress) = dwLength;
}
__except (GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR ?
             EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
        // Failed to write to the view.
}
北极星2003 25 2006-8-12 10:55
7
0
Thanks for your suggest!
I havn't got the level of normalization, but i'll try my best to improve this condition!
萝卜 1 2006-8-12 11:08
8
0
今天才看到,一定要顶
lnn1123 13 2006-8-12 11:50
9
0
这个一定要顶的
yujinjianx 2006-8-12 12:01
10
0
强支持一下
WAKU 7 2006-8-13 01:04
11
0
留名以后定要学习
非安全 17 2006-8-13 17:51
12
0
学习.....
fpgn 2006-8-13 18:04
13
0
dddddddddd
masmprogra 2006-8-14 11:21
14
0
好帖就得顶,已收藏!
龙鹰 2006-10-14 03:34
15
0
真的很不错啊,又有了好大的收获
Saver 2006-10-14 10:19
16
0
进来学习学习
bookworm 3 2006-10-14 15:37
17
0
Windows在做File Mapping的时候,是通过Windows memory manager创建一个section object,同时分配一块virtual address space里的memory block。一般有两类section object:

pagefile-based:作为进程间共享数据用,并且可以映射到任何地址,包括user space和kernel space。

file-based:方便对文件的访问。好像从Windows XP开始,拷贝小于32KB的文件,系统实际上是通过基于文件的memory mapping来实现的。
游客
登录 | 注册 方可回帖
返回