Доступ к количеству представлений сопоставленных файлов совместно используемой памяти (Windows) - PullRequest
0 голосов
/ 01 мая 2018

Я занимаюсь разработкой мультиплатформенного приложения C ++ (в основном, Windows и Linux), и теперь я сталкиваюсь с необходимостью ограничить максимальное количество экземпляров приложения, которое может работать одновременно (на одной машине) .

У меня уже есть модуль общей памяти, который использует:

  • 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 и закрытия сопоставления файлов дескриптор объекта, вызывая CloseHandle. Эти функции могут быть вызваны в любой заказ.

С Функция UnmapViewOfFile :

Отмена отображения сопоставленного файла делает недействительным диапазон, занимаемый представление в адресном пространстве процесса и делает диапазон доступны для других распределений. Удаляет запись рабочего набора для каждая не отображенная виртуальная страница, которая была частью рабочего набора процесс и уменьшает размер рабочего набора процесса. Это также уменьшает количество акций соответствующей физической страницы .

Но я не могу получить этот общий счетчик , и единственный вопрос stackoverflow по этому вопросу (который я обнаружил) остается без ответа: Количество отображенных представлений в общей памяти в 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). Так что в конце я могу прибегнуть к решению именованной трубы (очень красиво и аккуратно).

1 Ответ

0 голосов
/ 01 мая 2018

Как отметил Эриксун, самый надежный способ сделать это - использовать функцию CreateNamedPipe. мы можем использовать параметр nMaxInstances - Максимальное количество экземпляров, которые могут быть созданы для этого канала. следующим образом. при каждом запуске приложения при запуске попробуйте создать экземпляр канала. если все в порядке - мы можем бежать, в противном случае предел достигнут.

код может быть следующим:

BOOL IsLimitReached(ULONG MaxCount)
{
    SECURITY_DESCRIPTOR sd;
    InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
    SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);

    SECURITY_ATTRIBUTES sa = { sizeof(sa), &sd, FALSE };

    HANDLE hPipe = CreateNamedPipe(L"\\\\.\\pipe\\<some pipe>", 
        PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE|PIPE_READMODE_BYTE, MaxCount, 0, 0, 0, &sa);

    if (hPipe == INVALID_HANDLE_VALUE)
    {
        ULONG dwError = GetLastError();

        if (dwError != ERROR_PIPE_BUSY)
        {
            // handle error 
        }
        return TRUE;
    }

    return FALSE;
}

и используйте, скажем, для N экземпляров

    if (!IsLimitReached(N))
    {
        MessageBoxW(0, L"running..",0,0);
    }
...