作者: 未知
本文讨论Windows 95环境下,两个或多个进程之间通过Win32 API实现内存共享的方
法。共享内存的方法可以归纳为四种:内存映像文件、共享内存页、动态申请和静
态申请、定制资源。内存映像文件是其它内存共享方法的基础,也是本文介绍的重
点。同时简单介绍了进程之间对共享数据进行访问的同步。本文的程序采用Visual
C++的MFC实现。
关键字:内存共享 内存映像文件 进程同步
一、 共享内存
Windows 95采用页式内存管理,把进程私有页面和共享页面放在不同地址空间。私
用页放在4MB到2GB之间,当进行从一个Win32进程到另一个进程的上下文转换时,一
组私有页被映射出去,另外一组页面被影射到这个地址范围,这是通过更新CPU的页
表来实现的。另一方面,把Windows 95共享页面放在2GB到3GB之间。当一个上下文
转换发生时,Windows95内存管理者不用操心这段区域的页与页表。由于把共享页放
在一边,Windows95的内存管理使得进程之间的内存共享简单快速。
在Win32上的所有共享内存依赖于Win32的内存映像I/O支持。一个内存映像文件使用
共享内存来提供公共的基于文件的数据映像到共享数据的进程。内存映像文件I/O的
一个不太引人注意的特点是它支持内存共享。在Win32进程间共享内存,使用了一个
象内存映像文件一样的操作系统虚内存页文件。共享页属于系统的页文件,而不属
于一个永久的命名的文件。
本文从内存映像文件开始介绍内存共享,因为它是其它类型共享的基础。然后讨论
共享内存页,这里介绍两种共享页类型:动态申请和静态申请。我们将讨论的第四
种内存共享是定制资源,这种类型基于只读取,它提供了一种存储和查询大量数
据,根据需要从执行文件编页到内存的有效方法。
本文最后给出一个可以实际运行的例子供程序员参考。例子涉及到内存共享的另一
个问题:同步。多个进程可以拥有同一信号灯、事件或互斥等对象的句柄,所以这
些对象可用于实现进程之间的同步。例子中采用了互斥方法。
二、 内存映像文件I/O
内存映像文件I/O是Win32 API处理磁盘文件的一种方式。如果虚拟内存管理不控制
数据缓冲和内存缓冲,则当一个文件被映像到内存区,从映像内存读写数据和从文
件读写数据产生同样的效果。内存映像文件I/O效率很高,且使用非常方便。内存映
像文件I/O允许两个或多个进程共享基于文件的数据。每个和共享有关的进程直接存
取一组公共页。由于共享占用的资源最小,当大量的数据必须被共享时,通过内存
映像文件I/O共享就显得非常有用。
内存映像文件支持需要三个Windows内核对象:文件、文件映像和视图。为了映像一
个文件到内存,首先从磁盘打开一个文件,从而建立文件对象,然后将文件对象连
接到内存映像文件对象,为了在不同进程间同步数据,它提供了一个基于磁盘文件
的逻辑连接。为了访问文件的数据块,还需要一个数据指针,视图对象正是用于此
目的。一个内存映像文件对象可以创建多个视图,分别存取文件的不同部分。下图
示意了三个对象之间的关系。
三、 内存映像文件的使用和共享
内存映像文件I/O的使用由以下步骤组成:
·打开磁盘文件。这一步可采用如CreateFile()或OpenFile()等函数。CreateFile()
功能较强,但OpenFile()参数较少,易于使用。为了打开一个基于磁盘的文件,宜
采用OpenFile():
HFILE WINAPI OpenFile (
LPCSTR lpFileName,
LPOFSTRUCT lpOF,
UINT uStyle )
注意OpenFile的返回值为-1时表示打开文件失败。参数uStyle定义了打开文件标志
(OF_READ, OF_WRITE, OF_READWRITE等),不同标志标明了怎样存取内存;文件被其
它进程共享的方式(OF_SHARE_EXCLUTIVE, OF_SHARE_DENY_WRITE,等),及文件打开
时采取的动作(OF_CREATE, OF_DELETE, OF_EXIST等)。为了关闭文件,可以调用函
数CloseHandle()。
·创建文件映像对象。为了使用内存映像文件,第二个步骤是通过函数
CreateFileMapping() 创建文件映像对象。这个函数接受上一步得到的文件句柄作
为参数。
HANDLE WINAPI CreateFileMapping (
HANDLE hFile,
LPSECURITY _ATTRIBUTES lpsa,
DWORD dwPROTECT,
DWORD dwMaxSizeHigh,
DWORD dwMaxSizeLow,
LPCSTR lpszMapName)
hFile是从OpenFile()调用返回的文件句柄;dwPROTECT是页保护标识
(PAGE_READONLY等),它必须和文件打开时定义的文件存取标志一致;两个
dwMaxSize共同定义了最大文件尺寸;lpszMapName定义了文件映像对象的名字。
如果你向hFile传送的参数为-1(无效的文件句柄),并不意味着函数调用失败,相反
地,这种情况创建共享内存而非文件共享存取,本文后面将讨论这一点。
lpszMapName是你给文件映像对象起的名字,必须确保其唯一性,因为和一个未知进
程的名字冲突会产生非希望的共享。要共享此文件的进程通过相同的文件映像名实
现共享,具体做法可以有三种情况:CreateFileMapping(), OpenFileMapping(),
DuplicateHandle()。第二个(或以后的)进程调用CreateFileMapping()实际上并
没有创建新的对象,而只是与已有对象连接。若采用OpenFileMapping(),需假定文
件映像对象已由前面运行的进程用CreateFileMapping()创建。通过
DuplicateHandle()共享的做法需要经过别的途径获得对象句柄。
·创建视图对象。经过前面两个步骤后,存取内存映像文件或共享文件的第三个步骤
是调用MapViewOfFile()创建视图对象。可以在一个文件中创建多个视图,以便分别
访问文件的不同部分。调用MapViewOfFile()时需传递视图在文件的起始位置偏移和
要映射的字节数。
四、 共享内存页
分动态和静态两种情况介绍共享内存页。
·动态分配共享页。内存共享的第二种情况是动态申请共享页。这里要用到与共享内
存映像文件相同的函数。我们首先用文件句柄参数为-1来调用CreateFileMapping()
创建一个文件映像对象,并通过文件尺寸参数设定共享内存区域的大小。文件映像
对象将自己连接到系统页文件。然后调用MapViewOfFile()创建内存视图,以确定要
访问的特定内存区域,包括起始点和大小。
·静态分配共享页。Microsoft Visual C++编译器提供在不同进程间共享全局变量的
能力。编译和连接程序在执行文件中分配共享页,在运行时刻,加载程序允许这些
页面同时映射到几个进程的地址空间。为了共享全局变量,须要满足以下三个条
件:初始化全局变量、声明要共享的全局变量、给执行程序中包含共享全局变量的
部分赋予共享属性。例如前两个条件可以如下实现:
#program data_seg (".shared") // 定义名为".shared "的数据段
char achPublic[1000]=""; // 声明并初始化共享的全局变量
#program data_seg()
第三个条件的实现是在模块定义文件中产生一个入口。需要在SECTIONS下将共享的
数据段定义为具有SHARED属性的,例如:
SECTIONS
.shared READ WRITE SHARED
五、定制资源
这里采用定制资源的叫法以区别于Windows的预先定义资源(如图标、菜单、字符串
表等)。定制资源象其它资源一样被编在资源文件中,并被链接程序捆绑到WIN32执
行文件中(如.EXE或.DLL)。任何大段的只读数据都可用做定制资源。
用定制资源方法保存只读数据有以下优越性:由于数据被捆绑在执行文件中,查询
数据很安全;提高内存使用效率;简化编程任务。
定制资源时,第一步工作是使用你喜爱的编辑器准备你的数据文件(假设文件名
为"MyRes.data",定义资源数据格式时,最好与你计划使用数据的格式一致,这样
能简化编程量。
第二步是在工程的资源文件中加入你定义的资源。逐步执行下列动作:从
Developer Studio中选择Insert | Resource菜单;从对话框按钮中选择Custom;在
Resource Type输入框中定义资源的类型(假设"MyResType");右击资源标识,选
择"Properties";修改ID标识(假设为"ID_MYRES"),并在File Name后面输入
MyRes.data,确保选择了External File检查框。
第三步是在程序中使用你的数据。假设你用名为MyResStruct的数据结构访问你的数
据:
HINSTANCE hinst = AfxGetResourceHandle ();
HRSRC hresl = FindResource ( hinst,
MAKEINTRESOURCE( ID_MYRES, "MyResType" );
HGLOBAL hglb = LoadResource ( hinst, hresl );
MyResStruct * pMyData = (MyResStruct *) LockResource ( hglb );
其中hinst是包含资源的模块句柄,如果资源包含在动态库里,可以用LoadLibrary
()和GetModuleHandle()得到模块句柄。其它函数的使用以及参数的用法请参阅VC++
的联机Help文档。
六、内存共享示例
下面给出一个共享内存的例子。在我们开发的中心监控显示软件中,工作站上的监
视软件由几个独立进程组成,各自监视相应的子系统;由各监视进程共享的网络通
讯模块负责网上监视信息的接收,并经由子线程写入对应监视进程的数据区。信息
接收模块和监视进程共享内存数据区,由互斥控制对象保证内存读写访问的同步。
信息接收模块的程序如下:
CMutex mMutex1(TRUE, "Info1_Mutex" ); // 互斥对象
CSingleLock mSLock1 ( & mMutex1 );
DWORD dSize1; // 信息区大小
HANDLE hMem1; // 文件映像对象
InfoStruct1 pInfo1; // 信息数据结构
hMem1 = CreateFileMapping(
(HANDLE(-1), 0, PAGE_READWRITE, 0,ShSize, "System1_Info");
if ( hMem1 == 0 ) // 创建文件映像对象失败
{ MessageBox(hwnd, "Cannot Create FileMap for SubSystem1 ","Error", MB_OK);
return false;
}
if (GetLastError() == ERROR_ALREADY_EXIST) // 对象已经存在
{ MessageBox(hwnd, "FileMap Already Exists", "Error", MB_OK);
return false;
}
pInfo1 = (InfoStruct1 *) MapViewOfFile( hMem1, FILE_MAP_WRITE, 0,0,0);
if ( pInfo1 == 0)
{ MessageBox(hwnd, "Cannot Create View of File Mapping", "Error", MB_OK);
CloseHandle( hMem1);
return false;
}
子线程的工作:
mSLock1.Lock();
write information to pInfo1 …
mSLock1.Unlock();
监视进程的程序结构同接收模块类似,如下所示:
………
if (GetLastError() != ERROR_ALREADY_EXIST) // 对象不存在
{ MessageBox(hwnd, "FileMap doesnot Exists", "Error", MB_OK);
return false;
}
………
mSLock1.Lock();
read information from pInfo1 …
mSLock1.Unlock();
七、结论
随着硬件速度的发展和软件规模的扩大,多任务操作系统下进程之间的通讯量也在
增加,进程之间通讯的手段多种多样,其中内存共享方法的效率高,使用方便,愿
本文的介绍对你有所裨益,希望能为你的学习和工作节省一点宝贵的时间。