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);
}

Reply via email to