kerneltest/e32test/mmu/t_mmustress.cpp
author Tom Cosgrove <tom.cosgrove@nokia.com>
Fri, 28 May 2010 16:26:05 +0100
branchRCL_3
changeset 29 743008598095
parent 0 a41df078684a
permissions -rw-r--r--
Fix for bug 2283 (RVCT 4.0 support is missing from PDK 3.0.h) Have multiple extension sections in the bld.inf, one for each version of the compiler. The RVCT version building the tools will build the runtime libraries for its version, but make sure we extract all the other versions from zip archives. Also add the archive for RVCT4.

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