You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Windows函数锁定共享内存物理内存遇阻求助

进程间内存共享与物理内存锁定问题

需求背景

为优化进程性能,计划通过独立工具进程常驻约20GB资源在物理内存中,让其他进程直接从内存读取以替代硬盘加载,采用Windows的CreateFileMapping和PrefetchVirtualMemory函数实现。

测试代码

bool CreateMemoryMapping(const char* filename)
{
    long long m_hMappingSize = GetFileSize(filename);
    DWORD dwMaxSizeHigh = static_cast<DWORD>(m_hMappingSize >> 32);
    DWORD dwMaxSizeLow = static_cast<DWORD>(m_hMappingSize & 0xFFFFFFFF);
    m_hMapping = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, dwMaxSizeHigh, dwMaxSizeLow, filename);
    if (INVALID_HANDLE_VALUE == m_hMapping)
    {
        cout << "initial file mapping Handel failed," << endl;
        CloseHandle(m_hMapping);
        CloseHandle(m_hFile);
        return false;
    }

    if (!(m_pBase = MapViewOfFile(m_hMapping, FILE_MAP_ALL_ACCESS, NULL, NULL, NULL)))
    {
        cout << "Mapping a file into the address space of a process failed," << endl;
        CloseHandle(m_hMapping);
        CloseHandle(m_hFile);
        return false;
    }
    unsigned long long start = GetTickCount64();
    FileMappingWriteData(filename,  m_hMappingSize);
    unsigned long long end = GetTickCount64();
    unsigned long long duration = end - start;
    float MByte = (m_hMappingSize / 1024.0) / 1024.0;
    printf("write to sm %f MB:%d ms\n", MByte, duration);
    return true;
}

void FileMappingWriteData(const char* filename,  UINT64 datasize)
{
    /*DWORD dwBytesRead = 0;
    BOOL bSuccess = ReadFile(m_hFile, m_pBase, datasize, &dwBytesRead, NULL);*/
    void* data = malloc(datasize);
    FILE* f = nullptr;
    fopen_s(&f, filename, "rb");
    if (f)
    {
        read(data, datasize, f);
        write(m_pBase, datasize, data);
    }
    WIN32_MEMORY_RANGE_ENTRY  rangeArray;
    rangeArray.VirtualAddress = m_pBase;
    rangeArray.NumberOfBytes = datasize;
    HANDLE hProcess = GetCurrentProcess();
    BOOL success = PrefetchVirtualMemory(hProcess, 1, &rangeArray, 0);

    if (success) cout << "Resident physical memory success";
    Sleep(5000);
    if (VirtualLock(m_pBase, datasize)) cout << "Check to resident physical memory successful";
    CloseHandle(hProcess);
    free(data);
}

当前遇到的问题

  • 从共享内存读取与直接从SSD读取耗时一致,任务管理器显示页面错误数相同,说明数据未驻留物理内存。
  • 对数十MB文件,PrefetchVirtualMemory返回成功但VirtualLock始终检测失败;对1KB资源,收发端均显示锁定成功,但读取速度仍与直接读硬盘一致,页面错误数也相同。

已尝试的无效方案

  • 启用组策略中的「锁定内存页」权限
  • 使用Primo cache等内存映射软件

问题分析与解决方案

1. 核心误区:PrefetchVirtualMemory的作用

PrefetchVirtualMemory仅负责将数据预取到系统缓存,不会锁定物理内存。系统内存紧张时仍会将这些页面置换到分页文件,其他进程访问时依旧触发页面错误,这是导致读取速度无提升的根本原因。

2. VirtualLock失败的关键原因

  • 权限未正确启用:仅开启组策略的「锁定内存页」不够,进程需主动启用SeLockMemoryPrivilege权限,否则大内存范围的VirtualLock会直接失败。
  • 内存页对齐问题:VirtualLock要求锁定区域为完整内存页,起始地址需对齐到页边界(Windows默认页大小4KB),若datasize不是页大小的整数倍,也会导致锁定失败。
  • 内存配额限制:默认情况下,进程可锁定的内存量有限,20GB的一次性锁定需求远超配额,需分段处理。

3. 正确实现步骤

步骤1:启用进程的内存锁定权限

在创建内存映射前添加权限启用逻辑:

BOOL EnableLockMemoryPrivilege()
{
    HANDLE hToken;
    TOKEN_PRIVILEGES tp;
    LUID luid;

    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
        return FALSE;

    if (!LookupPrivilegeValue(NULL, SE_LOCK_MEMORY_NAME, &luid))
    {
        CloseHandle(hToken);
        return FALSE;
    }

    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

    if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL))
    {
        CloseHandle(hToken);
        return FALSE;
    }

    CloseHandle(hToken);
    return GetLastError() == ERROR_SUCCESS;
}

在CreateMemoryMapping开头调用EnableLockMemoryPrivilege()。

步骤2:主动触页+锁定内存

替换原PrefetchVirtualMemory和VirtualLock的调用,通过遍历内存页触发加载,再执行锁定:

void LockMemoryRegion(void* base, UINT64 size)
{
    SYSTEM_INFO sysInfo;
    GetSystemInfo(&sysInfo);
    DWORD pageSize = sysInfo.dwPageSize;
    char* ptr = static_cast<char*>(base);
    UINT64 remaining = size;

    // 主动触页,强制将所有页面加载到物理内存
    while (remaining > 0)
    {
        volatile char dummy = *ptr; // 读取字节触发页加载,volatile防止编译器优化
        UINT64 step = min(static_cast<UINT64>(pageSize), remaining);
        ptr += step;
        remaining -= step;
    }

    // 锁定内存
    if (VirtualLock(base, size))
        cout << "Memory locked successfully" << endl;
    else
        cout << "VirtualLock failed, error code: " << GetLastError() << endl;
}

在FileMappingWriteData中调用LockMemoryRegion(m_pBase, datasize)。

步骤3:修正文件写入逻辑

原代码用C标准库write操作内存映射区域存在风险,建议改用ReadFile直接写入映射地址:

// 替换FileMappingWriteData中的文件读取逻辑
HANDLE hFile = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
    DWORD bytesRead;
    ReadFile(hFile, m_pBase, static_cast<DWORD>(datasize), &bytesRead, NULL);
    CloseHandle(hFile);
}

步骤4:多进程共享注意事项

  • 其他进程映射共享内存后无需再次锁定,只要创建映射的进程保持运行并持有锁定,物理内存页面不会被置换。
  • 若创建进程退出,共享内存会被销毁,物理内存页面会被释放。

4. 20GB大内存优化建议

  • 分段锁定:将20GB内存划分为多个4GB或更小的段,逐个锁定,避免单次锁定内存过大导致失败。
  • 使用大页面:启用2MB/1GB大页面可减少页表开销,提升锁定效率。需在组策略中开启「锁定大页面」权限,映射时使用MapViewOfFileEx并指定FILE_MAP_LARGE_PAGES标志。

内容的提问来源于stack exchange,提问作者xd_m687

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 00:32:49