kerneltest/e32test/mmu/t_mmustress.cpp
changeset 0 a41df078684a
child 215 8096a832df02
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kerneltest/e32test/mmu/t_mmustress.cpp	Mon Oct 19 15:55:17 2009 +0100
@@ -0,0 +1,669 @@
+// Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
+// All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of the License "Eclipse Public License v1.0"
+// which accompanies this distribution, and is available
+// at the URL "http://www.eclipse.org/legal/epl-v10.html".
+//
+// Initial Contributors:
+// Nokia Corporation - initial contribution.
+//
+// Contributors:
+//
+// Description:
+// e32test\mmu\t_mmustress.cpp
+// Stress test for memory management services performed by the kernel's memory model.
+// 
+//
+
+/**
+ @file
+*/
+
+#define __E32TEST_EXTENSION__
+#include <e32test.h>
+#include "u32std.h"
+#include <u32hal.h>
+#include <e32svr.h>
+#include <dptest.h>
+#include <e32def.h>
+#include <e32def_private.h>
+#include "d_memorytest.h"
+#include "..\defrag\d_pagemove.h"
+
+TBool TRACE = 0;
+
+LOCAL_D RTest test(_L("T_MMUSTRESS"));
+
+TUint32 MemModelAttributes;
+TUint32 MemModel;
+TInt PageSize;
+TInt PageMask;
+
+#if !defined(__WINS__) && !defined(__X86__)
+const TPtrC KMoveLddFileName=_L("D_PAGEMOVE.LDD");
+RPageMove MoveLdd;
+#endif
+
+RMemoryTestLdd Ldd;
+
+const TUint KNumTestChunks = 6;
+RChunk Chunks[KNumTestChunks];
+TInt Committed[KNumTestChunks] = {0}; // for each chunk, is the 'owned' region uncommited(0), commited(1) or mixed(-1)
+class TNicePtr8 : public TPtr8 { public: TNicePtr8() : TPtr8(0,0) {} } ChunkPtr[KNumTestChunks];
+
+const TUint KNumSlaveProcesses = 4;
+RProcess Slaves[KNumSlaveProcesses];
+TRequestStatus SlaveLogons[KNumSlaveProcesses];
+TRequestStatus SlaveRendezvous[KNumSlaveProcesses];
+
+TInt SlaveNumber = -1; // master process is slave -1
+
+const TInt KLocalIpcBufferSize = 0x10000;
+TUint8* LocalIpcBuffer = 0;
+
+RSemaphore StartSemaphore;
+
+//
+// Random number generation
+//
+
+TUint32 RandomSeed;
+
+TUint32 Random()
+	{
+	RandomSeed = RandomSeed*69069+1;
+	return RandomSeed;
+	}
+
+TUint32 Random(TUint32 aRange)
+	{
+	return (TUint32)((TUint64(Random())*TUint64(aRange))>>32);
+	}
+
+void RandomInit(TUint32 aSeed)
+	{
+	RandomSeed = aSeed+(aSeed<<8)+(aSeed<<16)+(aSeed<<24);
+	Random();
+	Random();
+	}
+
+
+
+//
+// Chunk utils
+//
+
+TBuf<KMaxKernelName> ChunkName(TInt aChunkNumber)
+	{
+	TBuf<KMaxKernelName> name;
+	name.Format(_L("T_MMUSTRESS-Chunk%d"),aChunkNumber);
+	return name;
+	}
+
+#ifdef __WINS__
+TInt KChunkShift = 16;
+#elif defined(__X86__)
+TInt KChunkShift = 22;
+#else
+TInt KChunkShift = 20;
+#endif
+
+TInt ChunkSize(TInt aChunkNumber)
+	{
+	// biggest chunk (number 0) is big enough for each slave to own a region which is
+	// 2 page tables ('chunks') in size...
+	return (2*KNumSlaveProcesses)<<(KChunkShift-aChunkNumber);
+	}
+
+// check smallest chunk is less than 'chunk' size...
+__ASSERT_COMPILE((2*KNumSlaveProcesses>>(KNumTestChunks-1))==0);
+
+
+/* Memory region 'owned' by this slave process */
+void ChunkOwnedRegion(TInt aChunkNumber,TInt& aOffset,TInt& aSize)
+	{
+	TInt size = ChunkSize(aChunkNumber)/KNumSlaveProcesses;
+	aSize = size;
+	aOffset = SlaveNumber*size;
+	test_Equal(0,size&PageMask);
+	}
+
+void ChunkMarkRegion(TInt aChunkNumber,TInt aOffset,TInt aSize)
+	{
+	TInt pageSize = PageSize;
+	TUint32 mark = aOffset|aChunkNumber|(SlaveNumber<<4);
+	TUint8* ptr = Chunks[aChunkNumber].Base()+aOffset;
+	TUint8* ptrEnd = ptr+aSize;
+	while(ptr<ptrEnd)
+		{
+		((TUint32*)ptr)[0] = mark;
+		((TUint32*)ptr)[1] = ~mark;
+		mark += pageSize;
+		ptr += pageSize;
+		}
+	}
+
+void ChunkCheckRegion(TInt aChunkNumber,TInt aOffset,TInt aSize)
+	{
+	TInt pageSize = PageSize;
+	TUint32 mark = aOffset|aChunkNumber|(SlaveNumber<<4);
+	TUint8* ptr = Chunks[aChunkNumber].Base()+aOffset;
+	TUint8* ptrEnd = ptr+aSize;
+	while(ptr<ptrEnd)
+		{
+		test_Equal(mark,((TUint32*)ptr)[0]);
+		test_Equal(~mark,((TUint32*)ptr)[1]);
+		mark += pageSize;
+		ptr += pageSize;
+		}
+	}
+
+TInt ChunkOpen(TInt aChunkNumber)
+	{
+	RChunk& chunk = Chunks[aChunkNumber];
+	if(chunk.Handle()!=0)
+		return KErrNone;
+
+	if(TRACE) RDebug::Printf("%d %d Open",SlaveNumber,aChunkNumber);
+	TInt r = chunk.OpenGlobal(ChunkName(aChunkNumber),false);
+	if(r!=KErrNoMemory)
+		test_KErrNone(r);
+	return r;
+	}
+
+//
+// Server utils
+//
+
+TBuf<KMaxKernelName> ServerName(TInt aSlaveNumber)
+	{
+	TBuf<KMaxKernelName> name;
+	name.Format(_L("T_MMUSTRESS-Server%d"),aSlaveNumber);
+	return name;
+	}
+
+RServer2 Server;
+RMessage2 ServerMessage;
+TRequestStatus ServerStatus;
+
+class RTestSession : public RSessionBase
+	{
+public:
+	TInt Connect(TInt aServerNumber)
+		{
+		return CreateSession(ServerName(aServerNumber),TVersion(),1,EIpcSession_Unsharable,0,&iStatus);
+		}
+	TInt Send(TInt aChunkNumber)
+		{
+		return RSessionBase::Send(0,TIpcArgs(SlaveNumber,aChunkNumber,&ChunkPtr[aChunkNumber]));
+		}
+	TRequestStatus iStatus;
+	};
+RTestSession Sessions[KNumSlaveProcesses];
+
+
+//
+//
+//
+
+void SlaveInit()
+	{
+	RDebug::Printf("Slave %d initialising",SlaveNumber);
+
+	TBuf<KMaxKernelName> name;
+	name.Format(_L("T_MMUSTRESS-Slave%d"),SlaveNumber);
+	User::RenameThread(name);
+
+	test_KErrNone(StartSemaphore.Open(2));
+	TInt r;
+#if !defined(__WINS__) && !defined(__X86__)
+	// Move ldd may not be in the ROM so needs to be loaded.
+	r=User::LoadLogicalDevice(KMoveLddFileName);
+	test_Value(r, r==KErrNone || r==KErrAlreadyExists);
+	test_KErrNone(MoveLdd.Open());
+#endif
+
+	test_KErrNone(Ldd.Open());
+	test_KErrNone(Ldd.CreateVirtualPinObject());
+
+	LocalIpcBuffer = (TUint8*)User::Alloc(KLocalIpcBufferSize);
+	test(LocalIpcBuffer!=0);
+
+	test_KErrNone(Server.CreateGlobal(ServerName(SlaveNumber)));
+
+	TUint i;
+
+	// create sessions with other slaves...
+	for(i=0; i<KNumSlaveProcesses; i++)
+		{
+		for(;;)
+			{
+			r = Sessions[i].Connect(i);
+//			RDebug::Printf("%d Session %d = %d,%d",SlaveNumber,i,r,Sessions[i].iStatus.Int());
+			if(r==KErrNotFound)
+				{
+				// give other slaves time to create their servers...
+				User::After(10000);
+				continue;
+				}
+			test_KErrNone(r);
+			break;
+			}
+		}
+
+	// process session connect messages...
+	for(i=0; i<KNumSlaveProcesses; i++)
+		{
+		RMessage2 m;
+//		RDebug::Printf("%d Server waiting for connect message",SlaveNumber);
+		Server.Receive(m);
+		test_Equal(RMessage2::EConnect,m.Function())
+		m.Complete(KErrNone);
+		}
+
+	// wait for our session connections...
+	for(i=0; i<KNumSlaveProcesses; i++)
+		{
+//		RDebug::Printf("%d Session wait %d",SlaveNumber,i);
+		User::WaitForRequest(Sessions[i].iStatus);
+		}
+
+	// prime server for receiving mesages...
+	Server.Receive(ServerMessage,ServerStatus);
+
+	// synchronise with other processes...
+	RDebug::Printf("Slave %d waiting for trigger",SlaveNumber);
+	RProcess::Rendezvous(KErrNone);
+	StartSemaphore.Wait();
+	RDebug::Printf("Slave %d started",SlaveNumber);
+	}
+
+
+
+//
+// Test by random operations...
+//
+
+void DoTest()
+	{
+	RandomInit(SlaveNumber);
+	TInt r;
+	for(;;)
+		{
+		// select random chunk...
+		TInt chunkNumber = Random(KNumTestChunks);
+		RChunk& chunk = Chunks[chunkNumber];
+
+		// get the region of this chunk which this process 'owns'...
+		TInt offset;
+		TInt size;
+		ChunkOwnedRegion(chunkNumber,offset,size);
+
+		// calculate a random region in the owned part...
+		TInt randomOffset = offset+(Random(size)&~PageMask);
+		TInt randomSize = (Random(size-(randomOffset-offset))+PageMask)&~PageMask;
+		if(!randomSize)
+			continue; // try again
+
+		// pick a random slave...
+		TInt randomSlave = Random(KNumSlaveProcesses);
+
+		// open chunk if it isn't already...
+		r = ChunkOpen(chunkNumber);
+		if(r==KErrNoMemory)
+			continue; // can't do anything with chunk if we can't open it
+
+		// check our contents of chunk...
+		if(Committed[chunkNumber]==1)
+			{
+			if(TRACE) RDebug::Printf("%d %d Check %08x+%08x",SlaveNumber,chunkNumber,offset,size);
+			ChunkCheckRegion(chunkNumber,offset,size);
+			}
+
+		// perform random operation...
+		switch(Random(12))
+			{
+		case 0:
+		case 1:
+			// close chunk...
+			if(TRACE) RDebug::Printf("%d %d Close",SlaveNumber,chunkNumber);
+			chunk.Close();
+			break;
+
+		case 2:
+			// commit all...
+			if(TRACE) RDebug::Printf("%d %d Commit all %08x+%08x",SlaveNumber,chunkNumber,offset,size);
+			if(Committed[chunkNumber]!=0)
+				{
+				r = chunk.Decommit(offset,size);
+				test_KErrNone(r);
+				Committed[chunkNumber] = 0;
+				}
+			r = chunk.Commit(offset,size);
+			if(r!=KErrNoMemory)
+				{
+				test_KErrNone(r);
+				Committed[chunkNumber] = 1;
+				ChunkMarkRegion(chunkNumber,offset,size);
+				}
+			break;
+
+		case 3:
+			// decommit all...
+			if(TRACE) RDebug::Printf("%d %d Decommit all %08x+%08x",SlaveNumber,chunkNumber,offset,size);
+			r = chunk.Decommit(offset,size);
+			test_KErrNone(r);
+			Committed[chunkNumber] = 0;
+			break;
+
+		case 4:
+		case 5:
+			// commit random...
+			if(TRACE) RDebug::Printf("%d %d Commit %08x+%08x",SlaveNumber,chunkNumber,randomOffset,randomSize);
+			r = chunk.Commit(randomOffset,randomSize);
+			if(r!=KErrNoMemory)
+				{
+				if(Committed[chunkNumber]==0)
+					{
+					test_KErrNone(r);
+					Committed[chunkNumber] = -1;
+					}
+				else if(Committed[chunkNumber]==1)
+					{
+					test_Equal(KErrAlreadyExists,r);
+					}
+				else
+					{
+					if(r!=KErrAlreadyExists)
+						test_KErrNone(r);
+					}
+				}
+			break;
+
+		case 6:
+		case 7:
+			// decommit random...
+			if(TRACE) RDebug::Printf("%d %d Decommit %08x+%08x",SlaveNumber,chunkNumber,randomOffset,randomSize);
+			r = chunk.Decommit(randomOffset,randomSize);
+			test_KErrNone(r);
+			if(Committed[chunkNumber]==1)
+				Committed[chunkNumber] = -1;
+			break;
+
+		case 8:
+			if(TRACE) RDebug::Printf("%d %d IPC Send->%d",SlaveNumber,chunkNumber,randomSlave);
+//			ChunkPtr[chunkNumber].Set(chunk.Base(),ChunkSize(chunkNumber),ChunkSize(chunkNumber));
+			ChunkPtr[chunkNumber].Set(chunk.Base()+offset,size,size);
+			Sessions[randomSlave].Send(chunkNumber);
+			break;
+
+		case 9:
+			// process IPC messages...
+			if(ServerStatus.Int()==KRequestPending)
+				continue;
+			User::WaitForRequest(ServerStatus);
+
+			{
+			TInt sourceSlave = ServerMessage.Int0();
+			chunkNumber = ServerMessage.Int1();
+			if(TRACE) RDebug::Printf("%d %d IPC Receive<-%d",SlaveNumber,chunkNumber,sourceSlave);
+			test_Equal(0,ServerMessage.Function());
+
+			// get local descriptor for owned region in chunk...
+			size = ServerMessage.GetDesMaxLength(2);
+			test_NotNegative(size);
+			if(size>KLocalIpcBufferSize)
+				size = KLocalIpcBufferSize;
+			TPtr8 local(LocalIpcBuffer,size,size);
+
+//			if(Random(2))
+				{
+				// IPC read from other slave...
+				if(TRACE) RDebug::Printf("%d %d IPC Read<-%d",SlaveNumber,chunkNumber,sourceSlave);
+				TInt panicTrace = Ldd.SetPanicTrace(EFalse);
+				r = ServerMessage.Read(2,local);
+				Ldd.SetPanicTrace(panicTrace);
+				if(r!=KErrBadDescriptor)
+					test_KErrNone(r);
+				}
+//			else
+//				{
+//				// IPC write to other slave...
+//				if(TRACE) RDebug::Printf("%d %d IPC Write->%d",SlaveNumber,chunkNumber,sourceSlave);
+//				r = ServerMessage.Write(2,local,offset);
+//				if(r!=KErrBadDescriptor)
+//					test_KErrNone(r);
+//				if(Committed[chunkNumber]==1)
+//					ChunkMarkRegion(chunkNumber,offset,size);
+//				}
+			}
+
+			ServerMessage.Complete(KErrNone);
+			Server.Receive(ServerMessage,ServerStatus);
+			break;
+
+		case 10:
+		case 11:
+			// pin memory...
+			{
+			test_KErrNone(Ldd.UnpinVirtualMemory());
+			for(TInt tries=10; tries>0; --tries)
+				{
+				TInt chunkSize = ChunkSize(chunkNumber);
+				offset = Random(chunkSize);
+				TInt maxSize = chunkSize-offset;
+				if(maxSize>0x1000)
+					maxSize = 0x1000;
+				size = Random(maxSize);
+				r = Ldd.PinVirtualMemory((TLinAddr)chunk.Base()+offset, size);
+				if(r!=KErrNotFound && r!=KErrNoMemory)
+					{
+					test_KErrNone(r);
+					break;
+					}
+				}
+			}
+			break;
+		case 12:
+		case 13:
+			// Move any page in the chunk, not just the owned region.
+			{
+#if !defined(__WINS__) && !defined(__X86__)
+			for(TInt tries=10; tries>0; --tries)
+				{
+				TInt chunkSize = ChunkSize(chunkNumber);
+				offset = Random(chunkSize);
+				MoveLdd.TryMovingUserPage((TAny*)(chunk.Base()+offset), ETrue);
+				// Allow the move to fail for any reason as the page of the chunk
+				// may or may not be currently committed, pinned, or accessed.
+				}
+#endif
+			}
+			break;
+		default:
+			test(false); // can't happen
+			break;
+			}
+		}
+	}
+
+
+
+TInt E32Main()
+	{
+	// get system info...
+	MemModelAttributes = UserSvr::HalFunction(EHalGroupKernel, EKernelHalMemModelInfo, NULL, NULL);
+	MemModel = MemModelAttributes&EMemModelTypeMask;
+	UserHal::PageSizeInBytes(PageSize);
+	PageMask = PageSize-1;
+
+	// see if we are a slave process...
+	if(User::GetTIntParameter(1,SlaveNumber)==KErrNone)
+		{
+		// do testing...
+		SlaveInit();
+		DoTest();
+		return KErrGeneral; // shouldn't have returned from testing
+		}
+
+	// master process...
+	TBool pass = true; // final test result
+	test.Title();
+	if((MemModelAttributes&EMemModelAttrVA)==false)
+		{
+		test.Start(_L("TESTS NOT RUN - Not relevent for the memory model"));
+		test.End();
+		return KErrNone;
+		}
+
+	// get time to run tests for...
+	TInt timeout = 10; // time in seconds
+	TInt cmdLineLen = User::CommandLineLength();
+	if(cmdLineLen)
+		{
+		// get timeout value from command line
+		RBuf cmdLine;
+		test_KErrNone(cmdLine.Create(cmdLineLen));
+		User::CommandLine(cmdLine);
+		test_KErrNone(TLex(cmdLine).Val(timeout));
+		if(timeout==0)
+			timeout = KMaxTInt;
+		}
+	TTimeIntervalMicroSeconds32 tickTime;
+	test_KErrNone(UserHal::TickPeriod(tickTime));
+	TInt ticksPerSecond = 1000000/tickTime.Int();
+	TInt timeoutTicks;
+	if(timeout<KMaxTInt/ticksPerSecond)
+		timeoutTicks = timeout*ticksPerSecond;
+	else
+		{
+		timeoutTicks = KMaxTInt;
+		timeout = timeoutTicks/ticksPerSecond;
+		}
+
+	// master process runs at higher priority than slaves so it can timeout and kill them...
+	RThread().SetPriority(EPriorityMore);
+
+	test.Start(_L("Creating test chunks"));
+	TUint i;
+	for(i=0; i<KNumTestChunks; i++)
+		{
+		test.Printf(_L("Size %dkB\r\n"),ChunkSize(i)>>10);
+		test_KErrNone(Chunks[i].CreateDisconnectedGlobal(ChunkName(i),0,0,ChunkSize(i)));
+		}
+
+	test.Next(_L("Spawning slave processes"));
+	test_KErrNone(StartSemaphore.CreateGlobal(KNullDesC,0));
+	TFileName processFile(RProcess().FileName());
+	for(i=0; i<KNumSlaveProcesses; i++)
+		{
+		test.Printf(_L("Slave %d\r\n"),i);
+		RProcess& slave = Slaves[i];
+		test_KErrNone(slave.Create(processFile,KNullDesC));
+		test_KErrNone(slave.SetParameter(1,i));
+		test_KErrNone(slave.SetParameter(2,StartSemaphore));
+		slave.Logon(SlaveLogons[i]);
+		test_Equal(KRequestPending,SlaveLogons[i].Int());
+		slave.Rendezvous(SlaveRendezvous[i]);
+		test_Equal(KRequestPending,SlaveRendezvous[i].Int());
+		}
+
+	test.Next(_L("Create timer"));
+	RTimer timer;
+	test_KErrNone(timer.CreateLocal());
+
+	test.Next(_L("Resuming slave processes"));
+	for(i=0; i<KNumSlaveProcesses; i++)
+		Slaves[i].Resume();
+
+	// this test must now take care not to die (e.g. panic due to assert fail)
+	// until it has killed the slave processes
+
+	test.Next(_L("Change paging cache size"));
+	TUint cacheOriginalMin = 0;
+	TUint cacheOriginalMax = 0;
+	TUint cacheCurrentSize = 0;
+	DPTest::CacheSize(cacheOriginalMin, cacheOriginalMax, cacheCurrentSize);
+	DPTest::SetCacheSize(1, 2*ChunkSize(0)); // big enough for all the test chunks
+
+	test.Next(_L("Wait for slaves to initialise"));
+	TRequestStatus timeoutStatus;
+	timer.After(timeoutStatus,10*1000000); // allow short time for slaves to initialise
+	for(i=0; i<KNumSlaveProcesses; i++)
+		{
+		User::WaitForAnyRequest(); // wait for a rendexvous
+		if(timeoutStatus.Int()!=KRequestPending)
+			{
+			test.Printf(_L("Timeout waiting for slaves to initialise\r\n"));
+			pass = false;
+			break;
+			}
+		}
+
+	test.Next(_L("Restore paging cache size"));
+	DPTest::SetCacheSize(cacheOriginalMin, cacheOriginalMax);
+
+	if(pass)
+		{
+		timer.Cancel();
+		User::WaitForAnyRequest(); // swallow timer signal
+
+		test.Next(_L("Check slaves are ready"));
+		for(i=0; i<KNumSlaveProcesses; i++)
+			{
+			if(SlaveRendezvous[i].Int()!=KErrNone || Slaves[i].ExitType()!=EExitPending)
+				{
+				test.Printf(_L("Slaves not ready or died!\r\n"));
+				pass = false;
+				break;
+				}
+			}
+		}
+
+	if(pass)
+		{
+		test.Next(_L("Setup simulated kernel heap failure"));
+		__KHEAP_SETFAIL(RAllocator::EDeterministic,100);
+
+		TBuf<80> text;
+		text.Format(_L("Stressing for %d seconds..."),timeout);
+		test.Next(text);
+		timer.AfterTicks(timeoutStatus,timeoutTicks);
+		StartSemaphore.Signal(KNumSlaveProcesses); // release slaves to start testing
+		User::WaitForAnyRequest(); // wait for timeout or slave death via logon completion
+
+		pass = timeoutStatus.Int()==KErrNone; // timeout means slaves are still running OK
+
+		test.Next(_L("Check slaves still running"));
+		for(i=0; i<KNumSlaveProcesses; i++)
+			if(Slaves[i].ExitType()!=EExitPending)
+				pass = false;
+
+		test.Next(_L("Clear kernel heap failure"));
+		TUint kheapFails = __KHEAP_CHECKFAILURE;
+		__KHEAP_RESET;
+		test.Printf(_L("Number of simulated memory failures = %d\r\n"),kheapFails);
+		}
+
+	test.Next(_L("Killing slave processes"));
+	for(i=0; i<KNumSlaveProcesses; i++)
+		Slaves[i].Kill(0);
+
+	test.Next(_L("Assert test passed"));
+	test(pass);
+
+	test.End();
+
+	for(i=0; i<KNumSlaveProcesses; i++)
+		Slaves[i].Close();
+	for(i=0; i<KNumTestChunks; i++)
+		Chunks[i].Close();
+	timer.Close();
+	for(i=0; i<KNumSlaveProcesses; i++)
+		User::WaitForRequest(SlaveLogons[i]);
+
+	UserSvr::HalFunction(EHalGroupKernel, EKernelHalSupervisorBarrier, (TAny*)5000, 0);
+
+	return KErrNone;
+	}