Изменение размера файла, отображаемого в памяти, на окнах без отмены указателей - PullRequest
0 голосов
/ 08 марта 2019

Я хотел бы изменить размер файла отображения памяти в Windows, не делая недействительным указатель, полученный из предыдущего вызова MapViewOfFileEx. Таким образом, все указатели на любые данные файла, которые хранятся в приложении, не аннулируются операцией изменения размера.

Я нашел решение проблемы, но я не уверен, действительно ли этот подход будет работать во всех случаях.

Это мой подход: Я резервирую большую область памяти с VirtualAlloc:

reserved_pages_ptr = (char*)VirtualAlloc(nullptr, MAX_FILE_SIZE, MEM_RESERVE, PAGE_NOACCESS);
base_address = reserved_pages_ptr;

Каждый раз при изменении размера карты памяти я закрываю старое сопоставление файлов, освобождаю зарезервированные страницы и резервирую остальные страницы, которые не нужны для текущего размера файла:

filemapping_handle = CreateFileMappingW(...);

SYSTEM_INFO info;
GetSystemInfo(&info);
const DWORD page_size = info.dwAllocationGranularity;

const DWORD pages_needed = file_size / page_size + size_t(file_size % page_size != 0);

// release reserved pages:
VirtualFree(reserved_pages_ptr, 0, MEM_RELEASE);
// reserve rest pages:
reserved_pages_ptr = (char*)VirtualAlloc(
    base_address + pages_needed * page_size, 
    MAX_FILE_SIZE - pages_needed * page_size, 
    MEM_RESERVE, PAGE_NOACCESS
);

if(reserved_pages_ptr != base_address + pages_needed * page_size)
{
    //I hope this never happens...
}

Тогда я могу отобразить вид с помощью MapViewOfFileEx:

data_ = (char*)MapViewOfFileEx(filemapping_handle, ... , base_address);

if (data_ != base_address)
{
    //I hope this also never happens...    
}

Достаточно ли стабилен этот подход, чтобы гарантировать, что потенциальные проблемы никогда не возникнут? Нужна ли синхронизация, чтобы избежать проблем с многопоточностью?

РЕДАКТИРОВАТЬ: Я знаю, что наиболее стабильный подход заключается в изменении остальной части приложения, чтобы позволить аннулировать все указатели данных файла, но это решение может быть простым подходом, который отражает поведение mmap в Linux.

1 Ответ

2 голосов
/ 08 марта 2019

Решение зависит от того, поддерживается ли объект сопоставления файлов файлом подкачки операционной системы (параметр hFile INVALID_HANDLE_VALUE) или каким-либо файлом на диске.

В этом случае вы используетеобъект сопоставления файлов поддерживается файлом подкачки операционной системы, необходимо использовать флаг SEC_RESERVE:

указывает, что при просмотре файлаотображается в адресное пространство процесса, весь диапазон страниц зарезервирован для последующего использования процессом, а не зафиксирован.Зарезервированные страницы могут быть зафиксированы для последующих вызовов функции VirtualAlloc .После фиксации страниц они не могут быть освобождены или распакованы с помощью функции VirtualFree .

Код может выглядеть следующим образом:

#define MAX_FILE_SIZE 0x10000000

void ExtendInMemorySection()
{
    if (HANDLE hSection = CreateFileMapping(INVALID_HANDLE_VALUE, 0, 
            PAGE_READWRITE|SEC_RESERVE, 0, MAX_FILE_SIZE, NULL))
    {
        PVOID pv = MapViewOfFile(hSection, FILE_MAP_WRITE, 0, 0, 0);

        CloseHandle(hSection);

        if (pv)
        {
            SYSTEM_INFO info;
            GetSystemInfo(&info);

            PBYTE pb = (PBYTE)pv;
            int n = MAX_FILE_SIZE / info.dwPageSize;
            do 
            {
                if (!VirtualAlloc(pb, info.dwPageSize, MEM_COMMIT, PAGE_READWRITE))
                {
                    break;
                }

                pb += info.dwPageSize;

            } while (--n);
            UnmapViewOfFile(pv);
        }
    }
}

Но из SEC_RESERVE

Этот атрибут не влияет на объекты сопоставления файлов, которые поддерживаются исполняемыми файлами изображений или файлами данных (параметр hfile является дескриптором файла).

Для этого (и только для этого) случая существует недокументированный API:

NTSYSCALLAPI
NTSTATUS
NTAPI
NtExtendSection(
    _In_ HANDLE SectionHandle,
    _Inout_ PLARGE_INTEGER NewSectionSize
    );

Этот API позволяет увеличить размер раздела (и файла резервной копии).Кроме того, SectionHandle должно иметь право доступа SECTION_EXTEND_SIZE в этом случае, но CreateFileMapping создает дескриптор раздела без этого доступа.Поэтому нам нужно использовать только NtCreateSection здесь, тогда нам нужно использовать ZwMapViewOfSection API с AllocationType = MEM_RESERVE и ViewSize = MAX_FILE_SIZE - это резервный ViewSize регион памяти, но не коммитэто, но после вызова NtExtendSection действительные данные (страницы фиксации) будут автоматически расширены.До win 8.1, MapViewOfFile не такой функционал для типа размещения pass MEM_RESERVE для ZwMapViewOfSection, но начиная с win 8 (или 8.1) существует недокументированный флаг FILE_MAP_RESERVE, который позволяет это делать.

В общем случае демонстрационный код может выглядеть следующим образом:

#define MAX_FILE_SIZE 0x10000000

void ExtendFileSection()
{
    HANDLE hFile = CreateFile(L"d:/ee.tmp", GENERIC_ALL, 0, 0, CREATE_ALWAYS, 0, 0);

    if (hFile != INVALID_HANDLE_VALUE)
    {
        HANDLE hSection;

        SYSTEM_INFO info;
        GetSystemInfo(&info);
        // initially only 1 page in the file
        LARGE_INTEGER SectionSize = { info.dwPageSize };

        NTSTATUS status = NtCreateSection(&hSection, 
            SECTION_EXTEND_SIZE|SECTION_MAP_READ|SECTION_MAP_WRITE, 0, 
            &SectionSize, PAGE_READWRITE, SEC_COMMIT, hFile);

        CloseHandle(hFile);

        if (0 <= status)
        {
            PVOID BaseAddress = 0;
            SIZE_T ViewSize = MAX_FILE_SIZE;

            //MapViewOfFile(hSection, FILE_MAP_WRITE|FILE_MAP_RESERVE, 0, 0, MAX_FILE_SIZE);
            status = ZwMapViewOfSection(hSection, NtCurrentProcess(), &BaseAddress, 0, 0, 0, 
                &ViewSize, ViewUnmap, MEM_RESERVE, PAGE_READWRITE);

            if (0 <= status)
            {   
                SIZE_T n = MAX_FILE_SIZE / info.dwPageSize - 1;
                do 
                {
                    SectionSize.QuadPart += info.dwPageSize;

                    if (0 > NtExtendSection(hSection, &SectionSize))
                    {
                        break;
                    }

                } while (--n);

                UnmapViewOfFile(BaseAddress);
            }
            CloseHandle(hSection);
        }
    }
}
...