【问题标题】:How to remap view of shared memory via file mapping object?如何通过文件映射对象重新映射共享内存的视图?
【发布时间】:2018-04-10 06:23:47
【问题描述】:

说,如果我有一个共享文件映射对象:

HANDLE hFileMapping = ::CreateFileMapping(INVALID_HANDLE_VALUE, &sa,
    PAGE_READWRITE, 0, 0x8000, L"MyObjectName");

我得到了一小块供查看,如下所示:

BYTE* pData = (BYTE*)::MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0x10);
if(!pData) return false;   //fail
DWORD dwcbFullSize = *(DWORD*)(pData + 0xC);

那么如果我需要分配更多数据,是否可以在不先取消映射pData 的情况下再次调用MapViewOfFile?

BYTE* pFullData = (BYTE*)::MapViewOfFile(hFileMapping,
      FILE_MAP_READ, 0, 0, dwcbFullSize);

PS。我的目标不是在映射整个 32K 共享内存段时浪费 CPU 周期,因为我可能需要读取的内容可能远少于此。

【问题讨论】:

  • @RbMm:是的,谢谢。如果 MS 记录在案,那看起来将是一个很酷的功能。只是好奇,当MapViewOfFile 映射共享内存时,它不是一次加载整个页面吗?还是它只执行最后一个参数指定的字节数?
  • @RbMm:所以它的第一个参数是我的hFileMapping,第二个参数是新的映射视图大小(以字节为单位),而不是添加的大小,对吗?另外我假设我必须从GetProcAddress 加载它ntdll.dll,对吧?
  • @RbMm:好的,谢谢。我需要审查所有这些。
  • @RbMm:好的。对不起,我很难理解你。让我们尝试使用代码。所以你是说如果我做MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 1) 会产生与我做MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0x10000) 相同的效果。对吗?
  • @RbMm:我不是问偏移量,我问的是映射字节数组的大小。

标签: c++ winapi shared-memory file-mapping


【解决方案1】:

这个任务需要在我们创建文件映射(section)时使用SEC_RESERVE属性

如果文件映射对象由操作系统分页支持 文件(hfile 参数是 INVALID_HANDLE_VALUE),指定 当文件的视图映射到进程地址空间时, 整个页面范围保留供进程以后使用,而不是 比承诺。保留页面可以在后续调用中提交 VirtualAlloc 函数。提交页面后,它们不能 使用VirtualFree 函数释放或取消提交。这个属性 对可执行文件支持的文件映射对象无效 图像文件或数据文件(hfile 参数是文件句柄)。 SEC_RESERVE 不能与 SEC_COMMIT 结合使用。

所以在创建带有SEC_RESERVE 属性的部分后,需要一次调用MapViewOfFile - 此调用不保留内存范围但不提交页面。提交页面需要调用VirtualAlloc。最后我们可以调用UnmapViewOfFile释放所有内存

void DoDemo(ULONG cb)
{
    if (!cb)
    {
        return;
    }

    SYSTEM_INFO si;
    GetSystemInfo(&si);

    cb = (cb + (si.dwPageSize - 1)) & ~(si.dwPageSize - 1);

    if (HANDLE hFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE, 0,
        PAGE_READWRITE|SEC_RESERVE, 0, cb, L"MyObjectName"))
    {
        // reserve address space with PAGE_READWRITE initial protection

        PVOID BaseAddress = MapViewOfFile(hFileMapping, 
            FILE_MAP_READ|FILE_MAP_WRITE, 0, 0, cb);

        // hFileMapping no more need
        CloseHandle(hFileMapping);

        if (BaseAddress)
        {
            // check, for test only
            ::MEMORY_BASIC_INFORMATION mbi;
            if (VirtualQuery(BaseAddress, &mbi, sizeof(mbi)) < sizeof(mbi) ||
                mbi.Type != MEM_MAPPED || mbi.State != MEM_RESERVE)
            {
                __debugbreak();
            }

            // map page by page
            PBYTE pb = (BYTE*)BaseAddress;
            do 
            {
                if (!VirtualAlloc(pb, si.dwPageSize, MEM_COMMIT, PAGE_READWRITE))
                {
                    GetLastError();
                    break;
                }
                *pb = '*';//write something
            } while (pb += si.dwPageSize, cb -= si.dwPageSize);

            //unmap all
            UnmapViewOfFile(BaseAddress);
        }
    }
}

然而,所有这些都只对大尺寸部分有意义。对于 32kb(小尺寸),最好的方法是在一次调用中映射所有页面

【讨论】:

  • 谢谢。我最初的错误是我不明白MapViewOfFile 处理虚拟内存的分配。我希望他们在文档的某处强调这一事实。我在comments here 中解释了我的其他发现。
  • @c00000fd - 通常它不处理虚拟内存的分配。 SEC_RESERVE on in memory only section 特殊情况
【解决方案2】:

我可能误解了这个问题,所以请多多包涵。我已经决定使用一些工作代码更容易更清楚地显示我在 cmets 中所说的内容。 @OP,也许从这里您可以进一步澄清这是否不能解决您的问题?

所以我采用了 MSFT 提供的示例代码并将其组合在一起以演示我所说的(因此代码本质上是 MS 示例代码)。

https://msdn.microsoft.com/en-us/library/windows/desktop/aa366551(v=vs.85).aspx

您可以创建一个包含 2 个项目的 VS 解决方案,这是项目/流程 A 的代码:

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>

#define BUF_SIZE 256
TCHAR szName[] = TEXT("Global\\MyFileMappingObject");
TCHAR szMsg[] = TEXT("Message from first process.");

int _tmain()
{
    HANDLE hMapFile;
    LPCTSTR pBuf;

    hMapFile = CreateFileMapping(
        INVALID_HANDLE_VALUE,    // use paging file
        NULL,                    // default security
        PAGE_READWRITE,          // read/write access
        0,                       // maximum object size (high-order DWORD)
        BUF_SIZE,                // maximum object size (low-order DWORD)
        szName);                 // name of mapping object

    if (hMapFile == NULL)
    {
        _tprintf(TEXT("Could not create file mapping object (%d).\n"),
            GetLastError());
        return 1;
    }
    pBuf = (LPTSTR)MapViewOfFile(hMapFile,   // handle to map object
        FILE_MAP_ALL_ACCESS, // read/write permission
        0,
        0,
        BUF_SIZE);

    if (pBuf == NULL)
    {
        _tprintf(TEXT("Could not map view of file (%d).\n"),
            GetLastError());

        CloseHandle(hMapFile);

        return 1;
    }


    CopyMemory((PVOID)pBuf, szMsg, (_tcslen(szMsg) * sizeof(TCHAR)));
    _getch();

    UnmapViewOfFile(pBuf);

    CloseHandle(hMapFile);

    return 0;
}

现在这是第二个项目/流程 B 的一些修改代码。 请参阅此处以供参考:https://msdn.microsoft.com/en-us/library/windows/desktop/aa366548(v=vs.85).aspx

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
#pragma comment(lib, "user32.lib")

#define BUF_SIZE 256
TCHAR szName[] = TEXT("Global\\MyFileMappingObject");

int mapDataAtOffset(DWORD offset, size_t bytesToRead, LPVOID* outData, LPVOID* outMapAddress);

int _tmain()
{
    LPCTSTR pBuf;
    LPVOID outMapAddress = nullptr;
    LPVOID outMapAddress2 = nullptr;

    auto ret = mapDataAtOffset(0, 8, (LPVOID*)&pBuf, &outMapAddress);

    MessageBox(NULL, pBuf, TEXT("Process2"), MB_OK);

    ret = mapDataAtOffset(8, 8, (LPVOID*)&pBuf, &outMapAddress2);

    MessageBox(NULL, pBuf, TEXT("Process2"), MB_OK);

    if(outMapAddress)UnmapViewOfFile(outMapAddress);
    if (outMapAddress2)UnmapViewOfFile(outMapAddress2);

    return 0;
}

int mapDataAtOffset(DWORD offset, size_t bytesToRead, LPVOID* outData, LPVOID* outMapAddress) {
    HANDLE hMapFile;      // handle for the file's memory-mapped region
    BOOL bFlag;           // a result holder
    DWORD dBytesWritten;  // number of bytes written
    DWORD dwFileMapSize;  // size of the file mapping
    DWORD dwMapViewSize;  // the size of the view
    DWORD dwFileMapStart; // where to start the file map view
    DWORD dwSysGran;      // system allocation granularity
    SYSTEM_INFO SysInfo;  // system information; used to get granularity
    LPVOID lpMapAddress;  // pointer to the base address of the
                          // memory-mapped region
    char * pData;         // pointer to the data
    int i;                // loop counter
    int iData;            // on success contains the first int of data
    int iViewDelta;       // the offset into the view where the data
                          //shows up

    DWORD FILE_MAP_START = offset;

    // Get the system allocation granularity.
    GetSystemInfo(&SysInfo);
    dwSysGran = SysInfo.dwAllocationGranularity;

    // Now calculate a few variables. Calculate the file offsets as
    // 64-bit values, and then get the low-order 32 bits for the
    // function calls.

    // To calculate where to start the file mapping, round down the
    // offset of the data into the file to the nearest multiple of the
    // system allocation granularity.
    dwFileMapStart = (FILE_MAP_START / dwSysGran) * dwSysGran;
    _tprintf(TEXT("The file map view starts at %ld bytes into the file.\n"),
        dwFileMapStart);

    // Calculate the size of the file mapping view.
    dwMapViewSize = (FILE_MAP_START % dwSysGran) + bytesToRead;
    _tprintf(TEXT("The file map view is %ld bytes large.\n"),
        dwMapViewSize);

    // How large will the file mapping object be?
    dwFileMapSize = FILE_MAP_START + bytesToRead;
    _tprintf(TEXT("The file mapping object is %ld bytes large.\n"),
        dwFileMapSize);

    // The data of interest isn't at the beginning of the
    // view, so determine how far into the view to set the pointer.
    iViewDelta = FILE_MAP_START - dwFileMapStart;
    _tprintf(TEXT("The data is %d bytes into the view.\n"),
        iViewDelta);

    hMapFile = OpenFileMapping(
        FILE_MAP_ALL_ACCESS,   // read/write access
        FALSE,                 // do not inherit the name
        szName);               // name of mapping object

    if (hMapFile == NULL)
    {
        _tprintf(TEXT("Could not open file mapping object (%d).\n"),
            GetLastError());
        return 1;
    }

    // Map the view and test the results.

    lpMapAddress = MapViewOfFile(hMapFile,            // handle to
                                                      // mapping object
        FILE_MAP_ALL_ACCESS, // read/write
        0,                   // high-order 32
                             // bits of file
                             // offset
        dwFileMapStart,      // low-order 32
                             // bits of file
                             // offset
        dwMapViewSize);      // number of bytes
                             // to map
    if (lpMapAddress == NULL)
    {
        _tprintf(TEXT("lpMapAddress is NULL: last error: %d\n"), GetLastError());
        return 3;
    }

    // Calculate the pointer to the data.
    pData = (char *)lpMapAddress + iViewDelta;
    *outData = pData;
    *outMapAddress = lpMapAddress;

    CloseHandle(hMapFile); // close the file mapping object, doesn't matter as long as view is still mapped
    return 0;
}

所以回答你的问题

是否可以在不先取消映射 pData 的情况下再次调用 MapViewOfFile?

上面的例子实现了这一点。 除非我误解了你的问题,在这种情况下,如果你能澄清一下就好了。

【讨论】:

  • 感谢代码示例。我也做了一些测试。事实证明,我并没有完全理解 MapViewOfFile 的工作原理。我认为它可以让我“直接访问”文件映射对象的共享内存。在那种情况下,如果我已经分配了 16 个字节供查看,为什么不让我查看接下来的 32 个字节,等等。但这显然与 Windows NT 中的内存保护方案不兼容,其中每个进程都有自己的隔离内存集页。所以MapViewOfFile 不能让我“直接”访问共享内存,就像 Windows 9x 所做的那样......
  • 当你调用MapViewOfFile时会发生什么,它将N个虚拟内存页映射到文件映射对象的共享内存。由于每个内存页是 4K 字节,并且 VM 在 RAM 中的 64K 字节上对齐,因此偏移量必须能被 64K 整除,其大小必须能被 4K 整除。如果我调用 MapViewOfFile 1 个字节,然后再次调用 2 个字节,这对我的示例意味着两件事。
  • 1) 第一次调用将返回整个虚拟内存页面,即分配给地址 A 的 4K 字节。2) 第二次调用MapViewOfFile 还将返回整个 4K 字节的 VM 页面, 分配给地址 B. 和 A != B
  • 请记住,它仅限于进程地址空间(32 位与 64 位)。根据您映射到视图中的范围有多大,您可能会用完 32 位进程上的地址空间。这发生在我们的一款应用上,我们选择仅部署 64 位。
  • 我现在也明白为什么 MS 没有 API 来扩展已映射共享内存的视图。由于这种与虚拟内存的联系,如果第一次调用返回,比如说,一页 VM,或者 4K 字节。由于内存也映射到其 VM 地址,因此无法保证当您请求扩展内存时,下一个 VM 块的连续地址将在下一个地址可用。这就是为什么没有 API 可以做到这一点。 (好吧,除了reserving 它,它不处理物理内存地址的分配。)
猜你喜欢
  • 1970-01-01
  • 2011-01-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-06
  • 1970-01-01
  • 2012-09-08
  • 1970-01-01
相关资源
最近更新 更多