南京邮电大学操作系统——实验五:Windows平台多进程共享内存通信

前言

这次的实验代码都给了,需要做的事情是实现简单的生产者-消费者模型,这部分的原理也很简单,所以也就不重新造轮子了。本来这次实验的目的也是想让我们了解一下Windows内核对象的一些背景知识、WINAPI的相关调用过程、DLL的创建过程及如何在主函数中调用DLL。

项目拓扑

这里说一句题外话,原来Visual Studio是可以直接打开VC的项目的…之前还傻傻的复制粘贴。接下来就是项目的拓扑。
在这里插入图片描述
可以看到,整个项目分为三个模块,分别对应着Producer、Customer、SharedDll这三个文件。

Producer和Customer模块

这两个模块相似度非常高,主函数的代码:

int _tmain(int argc, _TCHAR* argv[])
{

	
	int aData = 0;

	while( 1 )
	{
		++aData;
		
		WriteBuffer(aData);

		printf("write %d  to the buffer.....\n", aData);

		
	}


	return 0;
}

两个模块的不同在于WriteBuffer函数和ReadBuffer函数,其他的细节完全相同。另外这两个模块的dll库的导入采用导入表方式导入,而不是采用LoadLibrary动态加载,对应的IDE的设置应该是项目下的引用。
在这里插入图片描述

ShareDll模块

这个模块才是重点,我们首先看一下由三个模块共同引用的ShallDllLib.h这个头文件

ShareDllLib.h

该头文件的主要作用是定义一个用于导入、导出的符号__SHAREDLLLIB,具体的实现代码为这一小段


#ifdef  __SHAREDLLLIB

#else
#define __SHAREDLLLIB   extern "C" __declspec(dllimport)


#endif

这段通过判断是否define了__SHAREDLLLIB来定义不同的__SHAREDLLLIB符号,具体来说,当Customer模块或者Producer模块调用该头文件时,__SHAREDLLLIB被定义为extern "C" __declspec(dllimport)。而当ShareDll模块调用该头文件时,__SHAREDLLLIB被定义为extern "C" __declspec(dllexport)。两者的主要区别在于一个是告诉编译器这个函数(变量)是导入的,另一个告诉编译器是导出的。
使用extern "C"的原因是在于C++会允许源码中的函数重载,但是实际上函数重载的实现机理是编译器对函数的重命名,如将int a(int,int)这样的函数声明修改为int_a_int_int,不同的编译器实现细节不同,这会导致按名索引时出现麻烦,而使用了extern "C"实际上强制编译器使用统一的C规范,同时这也会导致函数无法重载。
接下来头文件中通过调用这个符号来定义了三个需要导入、导出的符号。

__SHAREDLLLIB LONG glInstanceCounter;

__SHAREDLLLIB int WriteBuffer( int & aData);
__SHAREDLLLIB int ReadBuffer( int  & aData);

ShareDll.cpp

这个cpp文件中,主要是做了建立一个共享段,在共享段里面分配了一些数据结构,实现了ReadBufferWriteBuffer的接口、实现了DllMain函数来告诉使用者Dll库已经加载。
建立共享段的例程为:

#pragma data_seg("DvShareData")//定义一个自定义的内存段

//for count the instance
LONG glInstanceCounter    = 0;

#pragma data_seg()

//to allocate the glob buffer in the DvShareData section
__declspec(allocate("DvShareData")) class CRbuffer<int, 7, 1> gBuffer;


//to share the section DvShareData
#pragma comment(linker, "/SECTION:DvShareData,RWS")

其中的#pragma的作用分别为:

  1. #pragma data_seg("xxx")表示建立一个自定义的数据段名称(一般情况下数据段名称为DATA),这个编译选项需要和#pragma data_seg()成对使用,夹在中间的是这个内存段中的数据结构。
  2. __declspec(allocate("xxx")) class CRbuffer<int, 7, 1> gBuffer;则表示在xxx内存段中分配一个数据结构,这里是CRbuffer的一个模板类的对象。
  3. #pragma comment(linker, "/SECTION:DvShareData,RWS")则是添加了一个链接器参数/SECTION:DvShareData,RWS,表示将该内存段设置为RWS,即可读写、共享;

而在ShareDll这个动态库中的DllMain函数主要作用是显示有几个进程加载了ShareDll。
DllMain函数是动态库中在动态库初始化、写在动态库时由Windows调用的回调函数,一般用于初始化、回收一些资源。该函数有四个调用原因,分别是进程初始化(DLL_PROCESS_ATTACH)、线程初始化(DLL_THREAD_ATTACH)、线程卸载(DLL_THREAD_DETACH)、进程卸载(DLL_PROCESS_DETACH)1。另外需要注意的是,在DllMain中有一些函数是不可以被调用的,否则会造成死锁。

Rbuff.h

最重点的是这个头文件,该头文件定义了一个CBuffer类模板,该类模板实现了生产者-消费者模型,在这个类的构造函数中,通过调用CreateMutexCreateSemaphore函数来定义了两个互斥体对象和两个信号量对象,另外一提点,在VC中CreateMutexCreateMutexA的别名。这两个函数的API接口分别为:

HANDLE CreateMutexA(
  LPSECURITY_ATTRIBUTES lpMutexAttributes,
  BOOL                  bInitialOwner,
  LPCSTR                lpName
);

HANDLE CreateSemaphoreA(
  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
  LONG                  lInitialCount,
  LONG                  lMaximumCount,
  LPCSTR                lpName
);

其中的参数意义是:

  • 两者共有的第一个参数是一个安全令牌的指针,当为NULL时,该内核对象使用创建者的令牌或者是模拟令牌,并且不可以被子进程继承。
  • Mutex的第二个参数为ture时,创建者线程获取刚刚创建好的内核对象的所有权。2
  • CreateSemaphoreA的第二和第三参数分别表示信号量模型的初始化数量和最大数量。
  • 而最后一个参数则是表示内核对象的命名。如果在内核的名字空间中已经存在的话,创建函数失败,可以调用GetLastError函数获取错误的原因,另外,该函数可以为空。2
    另外在Windows中信号的信号发送机制是当资源拥有量大于0时,即向队列的第一个进程发送信号。3

ReadBuffer函数和WriteBuffer函数的结构基本上是一致的,他们的大致逻辑为:

  1. 调用WaitForSingleObject函数等待进入临界区;
  2. 在临界区中向缓冲区写入/取出一个Buffer;
  3. 调用ReleaseMutexReleaseSemaphore来释放自己的互斥体/信号量;

WaitForSingleObject的函数声明为:

DWORD WaitForSingleObject(
  HANDLE hHandle,
  DWORD  dwMilliseconds
);

第一个为互斥体的句柄,第二个参数为超时时间,如果为0的话,该函数总是不会阻塞,如果是INFINITE的话(INFINITE的值为0xffffffff),该函数总是等待直到句柄的对象被信号通知。4
ReleaseMutex的函数接口为:

BOOL ReleaseMutex(
  HANDLE hMutex
);

该函数有以下几点注意事项5

  1. 该函数必须被拥有互斥体的线程所使用,如果该线程没有互斥体,则返回错误;
  2. 一个线程可以通过Wait functions来判断自己是否拥有互斥体;
  3. 一旦一个线程从Wait functions的调用中返回时,该线程已经默认获取了对应互斥体的所有权限;

ReleaseSemaphore函数和上述大致一致。


  1. MSDN:https://docs.microsoft.com/en-us/windows/win32/dlls/dllmain ↩︎

  2. MSDN:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createmutexa ↩︎ ↩︎

  3. MSDN:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createsemaphorew ↩︎

  4. MSDN:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject ↩︎

  5. MSDN:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-releasemutex ↩︎

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值