There are major differences in the handling of virtual memory in Wine vs WinXP that are causing problems for my winelib application. Can someone provide background and/or workarounds for these issues?
As near as I can tell the main differences are: * VirtualLock does nothing in Wine * Wine makes no distinction between MEM_RESERVE and MEM_COMMIT * Wine has no implementation of Windows' process working sets * Wine limits MEM_RESERVE to 1GB, but WinXP goes up to 2GB The above problems seem relatively harmless on systems with < 1GB RAM. But when our units go greater than 1GB we are seeing spectacular crash-and-burn failures. The code-only DLLs we are running on such systems are crashing at a stage when I suspect they are attempting to lock RAM. I suspect that they are confused by the report from GlobalMemoryStatus that more than 1GB of RAM is installed, but that they are only able to VirtualLock up to 1GB. And in this unexpected situation, they are crashing. Further, these DLLs are audio processing plugins. The apparent fact that VirtualLock doesn't _actually_ lock memory into RAM for real time processing is a disaster for our system, in that it causes audio glitches when a page fault is handled. But that is not a problem which crashes the system. Any suggestions on what to do? Is there any pending work on this area out there? I've attached a little table that describes what I found. It shows the differences in how Wine and WinXP handle memory related calls. I have also attached a simple program which can be run to see these differences. Thanks for any help... mo
Memory Function Differences Between Wine and WinXP All sizes are in Kbytes CALL WINXP WINE ================================================================= 1. VirtualAlloc(MEM_RESERVE) 2,096,912 1,020,976 2. VirtualAlloc(MEM_COMMIT) 1,011,712 (3) 1,020,976 3. VirtualAlloc(MEM_COMMIT) 1,024 (1) 1,020,976 +VirtualLock() 4. VirtualAlloc(MEM_COMMIT) 259,072 1,020,976 (2) +SetProcessWorkingSetSize(260,000) +VirtualLock() 5. GlobalMemoryStatus RAM 392,688 386,324 Page 942,820 522,072 Virtual 2,097,024 2,097,024 6. GetProcessWorkingSetSize() hi/lo 1,380/200 32,768/32,768 (2) tests: 1. calling VirtualAlloc with MEM_RESERVE 1MB at a time until it fails. This is reserving virtual space only. Without the /3GB switch windows memory is limited to 2GB. So each app should be able to reserve its 2GB of space. call: drink-memory.exe.so -reserve-only -no-lock 2. VirtualAlloc(MEM_COMMIT) 1MB at at time until it fails. On WinXP this seems to be bounded by how big the paging file is. In Wine...? dunno. call: drink-memory.exe.so -no-lock 3. VirtualAlloc(MEM_COMMIT) + VirtualLock() 1MB at at time until it fails. On WinXP this craps out right away because the working set is very small, 1.3M (see line 6) call: drink-memory.exe.so 4. Same as 2. but with SetProcessWorkingSetSize(260,000). That was the largest value WinXP allowed me to set the working set size to. WinXP let me lock down RAM up to its limit, as expected. Wine, again, isn't doing anything special call: drink-memory.exe.so -lock-limit=260000 5. & 6. For reference the memory status reported WinXP and Wine notes: (1) VirtualLock fails - 1453 Insufficient quota (2) FIXME: stub (3) VirtualAlloc fails - 1455 Paging file is too small
#if _MFC #include <stdafx.h> #else #include <stdio.h> // printf #include <wine/windows/windows.h> #endif // process command line options const char* CheckOption(const char* check, const char* match); // like perror() for Windows void PrintLastError(const char* msg); int main(int argc, char *argv[]) { bool usage = false; bool forever = false; bool noLock = false; bool reserve = false; int chunk = 1048576; int lockLimit = -1; for (int arg = 1; !usage && arg < argc; arg++) { const char* result; if ((result = CheckOption(argv[arg], "-chunk=")) != 0) chunk = atoi(result) * 1024; else if (CheckOption(argv[arg], "-forever")) forever = true; else if (CheckOption(argv[arg], "-help")) usage = true; else if ((result = CheckOption(argv[arg], "-lock-limit=")) != 0) lockLimit = atoi(result); else if (CheckOption(argv[arg], "-no-lock")) noLock = true; else if (CheckOption(argv[arg], "-reserve-only")) reserve = true; else usage = true; } if (usage) { puts("usage: wine drink-memory.exe.so [OPTIONS]"); puts("Allocate chunks of memory and lock them into RAM until"); puts("you can't do it anymore"); puts(""); puts("OPTIONS:"); puts(" -chunk=SIZE block size in kbytes to allocate, default 1024k"); puts(" -reserve-only reserve, don't commit the memory"); puts(" -help print this message"); puts(" -lock-limit=SIZE size in k for the new limit of for lockable memory"); puts(" -no-lock do not lock memory, allocate it only"); return(1); } MEMORYSTATUS status; ::GlobalMemoryStatus(&status); printf("Memory: RAM = %ldk, Page file = %ldk, Virtual memory = %ldk\n", status.dwTotalPhys/1024, status.dwTotalPageFile/1024, status.dwTotalVirtual/1024); printf("Chunk size: %dk (%d)\n", chunk / 1024, chunk); printf("Locking: %s\n", noLock? "off" : "on"); printf("Commit: %s\n", reserve? "off" : "on"); HANDLE me = ::GetCurrentProcess(); SIZE_T lo, hi; if (lockLimit != -1) { lo = lockLimit * 1024; hi = lockLimit * 1024; if (!::SetProcessWorkingSetSize(me, lo, hi)) PrintLastError("SetProcessWorkingSetSize"); } if (::GetProcessWorkingSetSize(me, &lo, &hi)) printf("Memlock limit: %luk (%luk low)\n", hi / 1024, lo / 1024); else PrintLastError("GetProcessWorkingSetSize"); int total = 0; for (;;) { LPVOID leak; if (reserve) leak = ::VirtualAlloc(NULL, chunk, MEM_RESERVE, PAGE_NOACCESS); else leak = ::VirtualAlloc(NULL, chunk, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (leak == 0) { printf("\n"); PrintLastError("VirtualAlloc"); break; } total += chunk; if (!noLock) { if (!::VirtualLock(leak, chunk)) { printf("\n"); PrintLastError("VirtualLock"); break; } } printf("Allocated %dk\r", total / 1024); } while (forever) { ++total; } puts("~FIN~"); return(0); } const char* CheckOption(const char* check, const char* match) { size_t length = strlen(match); if (match[length - 1] != '=') { if (strcmp(check, match) == 0) return ""; } else if (strlen(check) > length) { if (memcmp(check, match, length) == 0) return check + length; } return(0); } void PrintLastError(const char* msg) { DWORD err = ::GetLastError(); LPVOID bufPtr; ::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), LPTSTR(&bufPtr), 0, NULL); // bufPtr has a \n printf("%s (%ld): %s", msg, err, LPTSTR(bufPtr)); ::LocalFree(bufPtr); }