我正在开发一个跨平台的C++应用程序(主要在Windows和Linux上),现在我需要能够限制同一台机器上可以同时运行的应用程序实例的最大数量。
我已经拥有了一个使用以下共享内存模块:
- Linux:System V共享内存(shmget(),shmat()...) - Windows:内存映射文件(CreateFileMapping(),OpenFileMapping(),MapViewOfFile(),...)
在Linux中,我可以轻松地通过这种代码来控制运行的实例数:
这段代码的好处在于,当应用程序被杀死或崩溃时,系统会负责减少已连接客户端的数量。
现在我的问题是,如何在Windows中实现此功能?(使用内存映射文件)。同样的代码翻译成Windows内存映射文件大致如下:
我已经搜索了相当长的时间,但无法找到如何获取打开视图的数量。
从CreateFileMapping函数中:
取消映射文件的映射视图会使进程地址空间中占用该视图的范围失效,并使该范围可用于其他分配。它会移除进程工作集中每个未映射虚拟页面的工作集条目,并减小进程的工作集大小。此外,它还会减少相应物理页面的共享计数。但我无法获取这个共享计数,关于这个问题唯一的stackoverflow问题没有得到答复:Number of mapped views to a shared memory on Windows,如果有人能帮助我,我将不胜感激。
我已经拥有了一个使用以下共享内存模块:
- Linux:System V共享内存(shmget(),shmat()...) - Windows:内存映射文件(CreateFileMapping(),OpenFileMapping(),MapViewOfFile(),...)
在Linux中,我可以轻松地通过这种代码来控制运行的实例数:
#include <stdio.h>
#include <sys/shm.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
struct shmid_ds shm;
int shmId;
key_t shmKey = 123456; // A unique key...
// Allocating 1 byte shared memory segment
// open it if already existent and rw user permission
shmId = shmget(shmKey, 1, IPC_CREAT|0x0180);
// Attach to the shared memory segment
shmat(shmId, (char *) 0, SHM_RDONLY);
// Get the number of attached "clients"
shmctl(shmId, IPC_STAT, &shm);
// Check limit
if (shm.shm_nattch > 4) {
printf("Limit exceeded: %ld > 4\n", shm.shm_nattch);
exit(1);
}
//...
sleep(30);
}
这段代码的好处在于,当应用程序被杀死或崩溃时,系统会负责减少已连接客户端的数量。
现在我的问题是,如何在Windows中实现此功能?(使用内存映射文件)。同样的代码翻译成Windows内存映射文件大致如下:
void functionName(void)
{
// Create the memory mapped file (in system pagefile)
HANDLE hMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE|SEC_COMMIT,
0, 1, "Global\\UniqueShareName");
// Map the previous memory mapped file into the address space
char *addr = (char*)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
// How can I check now the number of views mapped?
}
我已经搜索了相当长的时间,但无法找到如何获取打开视图的数量。
从CreateFileMapping函数中:
从UnmapViewOfFile函数中:文件映射对象的映射视图维护对对象的内部引用,并且只有在释放所有对它的引用后,文件映射对象才会关闭。因此,要完全关闭文件映射对象,应用程序必须通过调用UnmapViewOfFile取消映射文件映射对象的所有映射视图,并通过调用CloseHandle关闭文件映射对象句柄。这些函数可以按任意顺序调用。
取消映射文件的映射视图会使进程地址空间中占用该视图的范围失效,并使该范围可用于其他分配。它会移除进程工作集中每个未映射虚拟页面的工作集条目,并减小进程的工作集大小。此外,它还会减少相应物理页面的共享计数。但我无法获取这个共享计数,关于这个问题唯一的stackoverflow问题没有得到答复:Number of mapped views to a shared memory on Windows,如果有人能帮助我,我将不胜感激。
解决方案
(注意: 尽管可能不是100%可靠,请查看评论部分)
通过 RbMm 和 eryksun 的评论(感谢!),我能够使用以下代码解决问题:
#include <stdio.h>
#include <windows.h>
#include <winternl.h>
typedef NTSTATUS (__stdcall *NtQueryObjectFuncPointer) (
HANDLE Handle,
OBJECT_INFORMATION_CLASS ObjectInformationClass,
PVOID ObjectInformation,
ULONG ObjectInformationLength,
PULONG ReturnLength);
int main(void)
{
_PUBLIC_OBJECT_BASIC_INFORMATION pobi;
ULONG rLen;
// Create the memory mapped file (in system pagefile) (better in global namespace
// but needs SeCreateGlobalPrivilege privilege)
HANDLE hMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE|SEC_COMMIT,
0, 1, "Local\\UniqueShareName");
// Get the NtQUeryObject function pointer and then the handle basic information
NtQueryObjectFuncPointer _NtQueryObject = (NtQueryObjectFuncPointer)GetProcAddress(
GetModuleHandle("ntdll.dll"), "NtQueryObject");
_NtQueryObject(hMap, ObjectBasicInformation, (PVOID)&pobi, (ULONG)sizeof(pobi), &rLen);
// Check limit
if (pobi.HandleCount > 4) {
printf("Limit exceeded: %ld > 4\n", pobi.HandleCount);
exit(1);
}
//...
Sleep(30000);
}
但正确的做法是使用全局内核命名空间,这需要特权(SeCreateGlobalPrivilege)。因此最终我可能会采用命名管道解决方案(非常好而整洁)。
QueryWorkingSetEx
可以查询共享页面的进程数量,给定虚拟地址,但它仅限于7个。无论如何,我认为这不是正确的方法。您可以使用命名管道并限制实例的数量,这比使用信号量更可靠(即进程可能会崩溃或终止而不释放信号量)。 - Eryk SunObjectInformation
调用NtQueryObject
,并检查OBJECT_BASIC_INFORMATION
的HandleCount
。 - RbMmVirtualLock
锁定了页面,它仍然有限,因为它只是一个3位计数器,最多可以处理7个进程。 - Eryk Sun