【问题标题】:Coalescing Win32's Virtual Allocation Placeholders合并 Win32 的虚拟分配占位符
【发布时间】:2021-08-08 13:26:52
【问题描述】:

我正在尝试围绕 Win32 的虚拟分配概念占位符以及如何合并它们。

很遗憾,我在网上找不到任何示例,尽管尝试了各种变体,但我无法让我的代码正常工作。

有人能指出我缺少什么吗?

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

// Helpers
// -------------------------------------------------------------------------

static void
get_page_size(size_t *page_size)
{
    SYSTEM_INFO system_info;

    GetSystemInfo(&system_info);
    *page_size = system_info.dwPageSize;
}

static void
touch_pages(void *buf, size_t size, size_t page_size)
{
    size_t i;

    for (i = 0; i < size; i += page_size) {
        void *ptr;

        ptr = (void *)((uintptr_t)buf + i);
        *((size_t *)ptr) = i;
    }
}

static void
check_pages(void *buf, size_t size, size_t page_size)
{
    size_t i;
    size_t good;

    good = 0;
    for (i = 0; i < size; i += page_size) {
        void *ptr;

        ptr = (void *)((uintptr_t)buf + i);
        if (*((size_t *)ptr) != i) {
            printf("invalid data!\n");
            exit(0);
        }

        ++good;
    }

    printf("all good!\n");
}

static void
print_last_error(void)
{
    char buf[256];

    FormatMessageA(
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        GetLastError(),
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
        buf,
        (sizeof(buf) / sizeof(char)),
        NULL);

    printf("%s\n", buf);
}

// -------------------------------------------------------------------------

int
main(void)
{
    size_t page_size;
    ULARGE_INTEGER alloc_size;

    get_page_size(&page_size);
    alloc_size.QuadPart = 0x10000000;

    // PART 1
    // -------------------------------------------------------------------------

    // Allocate 256 MB at a fixed address.
    // This is done by reserving a block of virtual addresses, creating
    // paging-backed sections, and mapping them onto the allocation block.

    void *addr_1 = (void *)0x10000000; // Aligned with allocation granularity.

    void *placeholder_1 = VirtualAlloc2(
        GetCurrentProcess(),
        addr_1,
        (size_t)alloc_size.QuadPart,
        MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
        PAGE_NOACCESS,
        NULL,
        0
    );

    if (placeholder_1 == NULL) {
        printf("placeholder_1: VirtualAlloc2 failed\n");
        print_last_error();
        exit(0);
    }

    HANDLE section_1 = CreateFileMappingA(
        INVALID_HANDLE_VALUE,
        NULL,
        PAGE_READWRITE,
        alloc_size.HighPart,
        alloc_size.LowPart,
        NULL
    );

    if (section_1 == NULL) {
        printf("section_1: CreateFileMappingA failed\n");
        print_last_error();
        exit(0);
    }

    void *view_1 = MapViewOfFile3(
        section_1,
        GetCurrentProcess(),
        addr_1,
        0,
        (size_t)alloc_size.QuadPart,
        MEM_REPLACE_PLACEHOLDER,
        PAGE_READWRITE,
        NULL,
        0
    );

    if (view_1 == NULL) {
        printf("view_1: MapViewOfFile3 failed\n");
        print_last_error();
        exit(0);
    }

    // Touch each page just to make sure that they are fully committed.
    touch_pages(view_1, (size_t)alloc_size.QuadPart, page_size);

    // PART 2
    // -------------------------------------------------------------------------

    // Grow the previous allocation by another 256 MB.
    // The exercise here is to try replicating Linux's `mremap()` by using
    // `MEM_COALESCE_PLACEHOLDERS`.
    // The idea is to reserve another block of virtual addresses just after
    // the first allocation block, merging both blocks into a single one,
    // creating another bunch of paging-backed sections, and mapping
    // all the pages onto the merged allocation block.

    void *addr_2 = (void *)0x20000000; // Aligned with allocation granularity.

    void *placeholder_2 = VirtualAlloc2(
        GetCurrentProcess(),
        addr_2,
        (size_t)alloc_size.QuadPart,
        MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
        PAGE_NOACCESS,
        NULL,
        0
    );

    if (placeholder_2 == NULL) {
        printf("placeholder_2: VirtualAlloc2 failed\n");
        print_last_error();
        exit(0);
    }

    // It seems that we first need to unmap or else the following call to
    // `VirtualFree()` complains about attempting to access an invalid address.
    if (!UnmapViewOfFile2(
            GetCurrentProcess(),
            view_1,
            MEM_PRESERVE_PLACEHOLDER
        ))
    {
        printf("view_1: UnmapViewOfFile2 failed\n");
        print_last_error();
        exit(0);
    }

    if (!VirtualFree(
            placeholder_1,
            (size_t)alloc_size.QuadPart * 2,
            MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS
        ))
    {
        printf("placeholder_1: VirtualFree failed\n");
        print_last_error();
        exit(0);
    }

    // Create a new allocation block that represents both previous blocks
    // merged into a single one.
    void *placeholder_3 = VirtualAlloc2(
        GetCurrentProcess(),
        addr_1,
        (size_t)alloc_size.QuadPart * 2,
        MEM_RESERVE | MEM_REPLACE_PLACEHOLDER,
        PAGE_NOACCESS,
        NULL,
        0
    );

    if (placeholder_3 == NULL) {
        printf("placeholder_3: VirtualAlloc2 failed\n");
        print_last_error();
        exit(0);
    }

    HANDLE section_2 = CreateFileMappingA(
        INVALID_HANDLE_VALUE,
        NULL,
        PAGE_READWRITE,
        alloc_size.HighPart,
        alloc_size.LowPart,
        NULL
    );

    if (section_2 == NULL) {
        printf("section_2: CreateFileMappingA failed\n");
        print_last_error();
        exit(0);
    }

    // This fails due to an attempt to access an invalid addres.
    void *view_1b = MapViewOfFile3(
        section_1,
        GetCurrentProcess(),
        addr_1,
        0,
        (size_t)alloc_size.QuadPart,
        MEM_REPLACE_PLACEHOLDER,
        PAGE_READWRITE,
        NULL,
        0
    );

    if (view_1b == NULL) {
        printf("view_1b: MapViewOfFile3 failed\n");
        print_last_error();
        exit(0);
    }

    // This fails for the same reason if the previous mapping is commented.
    void *view_2b = MapViewOfFile3(
        section_2,
        GetCurrentProcess(),
        addr_2,
        0,
        (size_t)alloc_size.QuadPart,
        MEM_REPLACE_PLACEHOLDER,
        PAGE_READWRITE,
        NULL,
        0
    );

    if (view_2b == NULL) {
        printf("view_2b: MapViewOfFile3 failed\n");
        print_last_error();
        exit(0);
    }

    // Check that we have the same data as the one we touched.
    check_pages(view_1, (size_t)alloc_size.QuadPart, page_size);

    return 0;
}

【问题讨论】:

    标签: c windows winapi memory-management memory-mapping


    【解决方案1】:

    根据VirtualAlloc2MEM_REPLACE_PLACEHOLDER AllocationType 将占位符替换为普通的私有 allocation。所以只需评论placeholder_3 行。替换占位符后不能MapViewOfFile3
    以下代码适用于我。

    int
    main(void)
    {
        size_t page_size;
        ULARGE_INTEGER alloc_size{};
    
        get_page_size(&page_size);
        //alloc_size.QuadPart = page_size;
        alloc_size.QuadPart = 0x10000000;
    
        // PART 1
        // -------------------------------------------------------------------------
    
        // Allocate 256 MB at a fixed address.
        // This is done by reserving a block of virtual addresses, creating
        // paging-backed sections, and mapping them onto the allocation block.
    
        void* addr_1 = (void*)0x10000000; // Aligned with allocation granularity.
    
        void* placeholder_1 = VirtualAlloc2(
            GetCurrentProcess(),
            addr_1,
            (size_t)alloc_size.QuadPart,
            MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
            PAGE_NOACCESS,
            NULL,
            0
        );
    
        if (placeholder_1 == NULL) {
            printf("placeholder_1: VirtualAlloc2 failed\n");
            print_last_error();
            exit(0);
        }
    
        HANDLE section_1 = CreateFileMappingA(
            INVALID_HANDLE_VALUE,
            NULL,
            PAGE_READWRITE,
            alloc_size.HighPart,
            alloc_size.LowPart,
            NULL
        );
    
        if (section_1 == NULL) {
            printf("section_1: CreateFileMappingA failed\n");
            print_last_error();
            exit(0);
        }
    
        void* view_1 = MapViewOfFile3(
            section_1,
            GetCurrentProcess(),
            addr_1,
            0,
            (size_t)alloc_size.QuadPart,
            MEM_REPLACE_PLACEHOLDER,
            PAGE_READWRITE,
            NULL,
            0
        );
    
        if (view_1 == NULL) {
            printf("view_1: MapViewOfFile3 failed\n");
            print_last_error();
            exit(0);
        }
    
        // Touch each page just to make sure that they are fully committed.
        //touch_pages(view_1, (size_t)alloc_size.QuadPart, page_size);
    
        // PART 2
        // -------------------------------------------------------------------------
    
        // Grow the previous allocation by another 256 MB.
        // The exercise here is to try replicating Linux's `mremap()` by using
        // `MEM_COALESCE_PLACEHOLDERS`.
        // The idea is to reserve another block of virtual addresses just after
        // the first allocation block, merging both blocks into a single one,
        // creating another bunch of paging-backed sections, and mapping
        // all the pages onto the merged allocation block.
    
        //void* addr_2 = (void*)((PCHAR)addr_1 + alloc_size.QuadPart); // Aligned with allocation granularity.
        void* addr_2 = (void*)0x20000000; // Aligned with allocation granularity.
    
        void* placeholder_2 = VirtualAlloc2(
            GetCurrentProcess(),
            addr_2,
            (size_t)alloc_size.QuadPart,
            MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
            PAGE_NOACCESS,
            NULL,
            0
        );
    
        if (placeholder_2 == NULL) {
            printf("placeholder_2: VirtualAlloc2 failed\n");
            print_last_error();
            exit(0);
        }
    
        // It seems that we first need to unmap or else the following call to
        // `VirtualFree()` complains about attempting to access an invalid address.
        if (!UnmapViewOfFile2(
            GetCurrentProcess(),
            view_1,
            MEM_PRESERVE_PLACEHOLDER
        ))
        {
            printf("view_1: UnmapViewOfFile2 failed\n");
            print_last_error();
            exit(0);
        }
    
        alloc_size.QuadPart *= 2;
        if (!VirtualFree(
            addr_1,
            (size_t)alloc_size.QuadPart,
            MEM_RELEASE |MEM_COALESCE_PLACEHOLDERS
        ))
        {
            printf("placeholder_1: VirtualFree failed\n");
            print_last_error();
            exit(0);
        }
    
        // Create a new allocation block that represents both previous blocks
        // merged into a single one.
        /*void* placeholder_3 = VirtualAlloc2(
            GetCurrentProcess(),
            addr_1,
            (size_t)alloc_size.QuadPart,
            MEM_RESERVE | MEM_REPLACE_PLACEHOLDER,
            PAGE_NOACCESS,
            NULL,
            0
        );
    
        if (placeholder_3 == NULL) {
            printf("placeholder_3: VirtualAlloc2 failed\n");
            print_last_error();
            exit(0);
        }*/
    
        HANDLE section_2 = CreateFileMappingA(
            INVALID_HANDLE_VALUE,
            NULL,
            PAGE_READWRITE,
            alloc_size.HighPart,
            alloc_size.LowPart,
            NULL
        );
    
        if (section_2 == NULL) {
            printf("section_2: CreateFileMappingA failed\n");
            print_last_error();
            exit(0);
        }
    
        // This fails due to an attempt to access an invalid addres.
        void* view_1b = MapViewOfFile3(
            section_2,
            GetCurrentProcess(),
            addr_1,
            0,
            (size_t)alloc_size.QuadPart,
            MEM_REPLACE_PLACEHOLDER,
            PAGE_READWRITE,
            NULL,
            0
        );
    
        if (view_1b == NULL) {
            printf("view_1b: MapViewOfFile3 failed\n");
            print_last_error();
            exit(0);
        }
    
        char* lpPtr = (char*)view_1b;
        lpPtr[0x20000000 - 1] = 'a';
        //// This fails for the same reason if the previous mapping is commented.
        //void* view_2b = MapViewOfFile3(
        //  section_2,
        //  GetCurrentProcess(),
        //  addr_2,
        //  0,
        //  (size_t)alloc_size.QuadPart,
        //  MEM_REPLACE_PLACEHOLDER,
        //  PAGE_READWRITE,
        //  NULL,
        //  0
        //);
    
        //if (view_2b == NULL) {
        //  printf("view_2b: MapViewOfFile3 failed\n");
        //  print_last_error();
        //  exit(0);
        //}
    
        return 0;
    }
    

    【讨论】:

    • 感谢@YangXiaoPo,这似乎编译得很好,但是它似乎没有保留使用touch_pages()在第一部分中写入的数据。
    • touch_pages() 不关心。我已经测试过了。
    • 嗯,我也试过了,但有些值似乎不同?我通过添加用于验证数据的check_pages() 函数来编辑问题。
    • 通过调试发现UnmapViewOfFile2view_1之后内存值发生了变化。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-20
    • 1970-01-01
    • 2018-04-02
    • 2019-07-22
    • 2010-10-14
    • 2011-03-08
    • 2012-04-22
    相关资源
    最近更新 更多