os/kernelhwsrv/kernel/eka/nkern/win32/ncthrd.cpp
author sl
Tue, 10 Jun 2014 14:32:02 +0200
changeset 1 260cb5ec6c19
permissions -rw-r--r--
Update contrib.
     1 // Copyright (c) 1998-2009 Nokia Corporation and/or its subsidiary(-ies).
     2 // All rights reserved.
     3 // This component and the accompanying materials are made available
     4 // under the terms of the License "Eclipse Public License v1.0"
     5 // which accompanies this distribution, and is available
     6 // at the URL "http://www.eclipse.org/legal/epl-v10.html".
     7 //
     8 // Initial Contributors:
     9 // Nokia Corporation - initial contribution.
    10 //
    11 // Contributors:
    12 //
    13 // Description:
    14 // e32\nkern\win32\ncthrd.cpp
    15 // 
    16 //
    17 
    18 // NThreadBase member data
    19 #define __INCLUDE_NTHREADBASE_DEFINES__
    20 
    21 #include "nk_priv.h"
    22 #include <emulator.h>
    23 
    24 extern "C" void ExcFault(TAny*);
    25 
    26 // initial Win32 thread stack size
    27 const TInt KInitialStackSize = 0x1000;
    28 
    29 // maximum size of the parameter block passed to a new thread
    30 const TInt KMaxParameterBlock = 512;
    31 
    32 // data passed to new thread to enable hand-off of the parameter block
    33 struct SCreateThread
    34 	{
    35 	const SNThreadCreateInfo* iInfo;
    36 	NFastMutex iHandoff;
    37 	};
    38 
    39 /**
    40  * Set the Win32 thread priority based on the thread type.
    41  * Interrupt/Event threads must be able to preempt normal nKern threads,
    42  * so they get a higher priority.
    43  */
    44 static void SetPriority(HANDLE aThread, TEmulThreadType aType)
    45 	{
    46 	TInt p;
    47 	switch (aType)
    48 		{
    49 	default:
    50 		FAULT();
    51 	case EThreadEvent:
    52 		p = THREAD_PRIORITY_ABOVE_NORMAL;
    53 		break;
    54 	case EThreadNKern:
    55 		p = THREAD_PRIORITY_NORMAL;
    56 		break;
    57 		}
    58 
    59 	__NK_ASSERT_ALWAYS(SetThreadPriority(aThread, p));
    60 	SetThreadPriorityBoost(aThread, TRUE);		// disable priority boost (for NT)
    61 	}
    62 
    63 
    64 /**	Create a Win32 thread for use in the emulator.
    65 
    66 	@param	aType Type of thread (Event or NKern) - determines Win32 priority
    67 	@param	aThreadFunc Entry point of thread
    68 	@param	aPtr Argument passed to entry point
    69 	@param	aRun TRUE if thread should be resumed immediately
    70 	@return	The Win32 handle to the thread, 0 if an error occurred
    71 
    72 	@pre    Call either in thread context.
    73 	@pre	Do not call from bare Win32 threads.
    74 
    75 	@see TEmulThreadType
    76  */
    77 EXPORT_C HANDLE CreateWin32Thread(TEmulThreadType aType, LPTHREAD_START_ROUTINE aThreadFunc, LPVOID aPtr, TBool aRun)
    78 	{
    79 	__NK_ASSERT_DEBUG(!TheScheduler.iCurrentThread || NKern::CurrentContext() == NKern::EThread);
    80 
    81 	__LOCK_HOST;
    82 	
    83 	DWORD id;
    84 	HANDLE handle = CreateThread(NULL , KInitialStackSize, aThreadFunc, aPtr, CREATE_SUSPENDED, &id);
    85 	if (handle)
    86 		{
    87 		SetPriority(handle, aType);
    88 		if (aRun)
    89 			ResumeThread(handle);
    90 		}
    91 	return handle;
    92 	}
    93 
    94 
    95 /** Set some global properties of the emulator
    96 	Called by the Win32 base port during boot.
    97 
    98 	@param	aTrace TRUE means trace Win32 thread ID for every thread created
    99 	@param	aSingleCpu TRUE means lock the emulator process to a single CPU
   100 
   101 	@internalTechnology
   102  */
   103 EXPORT_C void NThread::SetProperties(TBool aTrace, TInt aSingleCpu)
   104 	{
   105 	Win32TraceThreadId = aTrace;
   106 	Win32SingleCpu = aSingleCpu;
   107 	}
   108 
   109 #if defined(__CW32__) && __MWERKS__ < 0x3200
   110 DWORD NThread__ExceptionHandler(EXCEPTION_RECORD* aException, TAny* /*aRegistrationRecord*/, CONTEXT* aContext)
   111 //
   112 // Hook into exception handling for old version of CW
   113 //
   114 	{
   115 	return NThread::ExceptionHandler(aException, aContext);
   116 	}
   117 #endif // old __CW32__
   118 
   119 DWORD WINAPI NThread::StartThread(LPVOID aParam)
   120 //
   121 // Win32 thread function for nKern threads.
   122 //
   123 // The thread first enters this function after the nScheduler has resumed
   124 // it, following the context switch induced by the hand-off mutex.
   125 //
   126 // The parameter block for this thread needs to be copied into its
   127 // own context, before releasing the mutex and handing control back to
   128 // the creating thread.
   129 //
   130 	{
   131 	SCreateThread* init = static_cast<SCreateThread*>(aParam);
   132 	NThread& me=*static_cast<NThread*>(init->iHandoff.iHoldingThread);
   133 	me.iWinThreadId = GetCurrentThreadId();
   134 	SchedulerRegister(me);
   135 #ifdef BTRACE_FAST_MUTEX
   136 	BTraceContext4(BTrace::EFastMutex,BTrace::EFastMutexWait,&init->iHandoff);
   137 #endif
   138 	NKern::Unlock();
   139 
   140 #if defined(__CW32__) && __MWERKS__ < 0x3200
   141 	// intercept the win32 exception mechanism manually
   142     asm {
   143     	push	ebp
   144     	mov		eax, -1
   145     	push	eax
   146     	push	eax
   147     	push	offset NThread__ExceptionHandler
   148     	push	fs:[0]
   149     	mov		fs:[0], esp
   150 
   151 		// realign the stack
   152     	sub		esp, 0x20
   153     	and		esp, ~0x1f
   154 		}
   155 #else // ! old __CW32__
   156 	// intercept win32 exceptions in a debuggabble way
   157 __try {
   158 #endif // old __CW32__
   159 
   160 	// save the thread entry point and parameter block
   161 	const SNThreadCreateInfo& info = *init->iInfo;
   162 	TUint8 parameterBlock[KMaxParameterBlock];
   163 	TAny* parameter=(TAny*)info.iParameterBlock;
   164 	if (info.iParameterBlockSize)
   165 		{
   166 		__NK_ASSERT_DEBUG(TUint(info.iParameterBlockSize)<=TUint(KMaxParameterBlock));
   167 		parameter=parameterBlock;
   168 		memcpy(parameterBlock,info.iParameterBlock,info.iParameterBlockSize);
   169 		}
   170 	NThreadFunction threadFunction=info.iFunction;
   171 
   172 	// Calculate stack base
   173 	me.iUserStackBase = (((TLinAddr)&parameterBlock)+0xfff)&~0xfff; // base address of stack
   174 
   175 	// some useful diagnostics for debugging
   176 	if (Win32TraceThreadId)
   177 		KPrintf("Thread %T created @ 0x%x - Win32 Thread ID 0x%x",init->iHandoff.iHoldingThread,init->iHandoff.iHoldingThread,GetCurrentThreadId());
   178 
   179 #ifdef MONITOR_THREAD_CPU_TIME
   180 	me.iLastStartTime = 0;  // Don't count NThread setup in cpu time
   181 #endif
   182  
   183 	// start-up complete, release the handoff mutex, which will re-suspend us
   184 	NKern::FMSignal(&init->iHandoff);
   185 
   186 	// thread has been resumed: invoke the thread function
   187 	threadFunction(parameter);
   188 
   189 #if !defined(__CW32__) || __MWERKS__ >= 0x3200
   190 	// handle win32 exceptions
   191 } __except (ExceptionFilter(GetExceptionInformation())) {
   192 	// Do nothing - filter does all the work and hooks
   193 	// into EPOC h/w exception mechanism if necessary
   194 	// by thread diversion
   195 }
   196 #endif // !old __CW32__
   197 
   198 	NKern::Exit();
   199 
   200 	return 0;
   201 	}
   202 
   203 static HANDLE InitThread()
   204 //
   205 // Set up the initial thread and return the thread handle
   206 //
   207 	{
   208 	HANDLE p = GetCurrentProcess();
   209 	HANDLE me;
   210 	__NK_ASSERT_ALWAYS(DuplicateHandle(p, GetCurrentThread(), p, &me, 0, FALSE, DUPLICATE_SAME_ACCESS));
   211 	SetPriority(me, EThreadNKern);
   212 	return me;
   213 	}
   214 
   215 TInt NThread::Create(SNThreadCreateInfo& aInfo, TBool aInitial)
   216 	{
   217 	iWinThread = NULL;
   218 	iWinThreadId = 0;
   219 	iScheduleLock = NULL;
   220 	iInKernel = 1;
   221 	iDivert = NULL;
   222 	iWakeup = aInitial ? ERelease : EResumeLocked;	// mark new threads as created (=> win32 suspend)
   223 
   224 	TInt r=NThreadBase::Create(aInfo,aInitial);
   225 	if (r!=KErrNone)
   226 		return r;
   227 
   228 	// the rest has to be all or nothing, we must complete it
   229 	iScheduleLock = CreateEventA(NULL, FALSE, FALSE, NULL);
   230 	if (iScheduleLock == NULL)
   231 		return Emulator::LastError();
   232 
   233 	if (aInitial)
   234 		{
   235 		iWinThread = InitThread();
   236 		FastCounterInit();
   237 #ifdef MONITOR_THREAD_CPU_TIME
   238 		iLastStartTime = NKern::FastCounter();
   239 #endif
   240 		iUserStackBase = (((TLinAddr)&r)+0xfff)&~0xfff; // base address of stack
   241 		SchedulerInit(*this);
   242 		return KErrNone;
   243 		}
   244 
   245 	// create the thread proper
   246 	//
   247 	SCreateThread start;
   248 	start.iInfo = &aInfo;
   249 
   250 	iWinThread = CreateWin32Thread(EThreadNKern, &StartThread, &start, FALSE);
   251 	if (iWinThread == NULL)
   252 		{
   253 		r = Emulator::LastError();
   254 		CloseHandle(iScheduleLock);
   255 		return r;
   256 		}
   257 
   258 #ifdef BTRACE_THREAD_IDENTIFICATION
   259 	BTrace4(BTrace::EThreadIdentification,BTrace::ENanoThreadCreate,this);
   260 #endif
   261 	// switch to the new thread to hand over the parameter block
   262 	NKern::Lock();
   263 	ForceResume();	// mark the thread as ready
   264 	// give the thread ownership of the handoff mutex
   265 	start.iHandoff.iHoldingThread = this;
   266 	iHeldFastMutex = &start.iHandoff;
   267 	Suspend(1);		// will defer as holding a fast mutex (implicit critical section)
   268 	// do the hand-over
   269 	start.iHandoff.Wait();
   270 	start.iHandoff.Signal();
   271 	NKern::Unlock();
   272 
   273 	return KErrNone;
   274 	}
   275 
   276 void NThread__HandleException(TWin32ExcInfo aExc)
   277 //
   278 // Final stage NKern exception handler.
   279 //
   280 // Check for a fatal exception when the kernel is locked
   281 //
   282 // Note that the parameter struct is passed by value, this allows for
   283 // direct access to the exception context created on the call stack by
   284 // NThread::Exception().
   285 //
   286 	{
   287 	if (TheScheduler.iKernCSLocked)
   288 		ExcFault(&aExc);
   289 
   290 	// Complete the exception data. Note that the call to EnterKernel() in
   291 	// ExceptionFilter() will have incremented iInKernel after the exception
   292 	// occurred.
   293 	NThread* me = static_cast<NThread*>(TheScheduler.iCurrentThread);
   294 	__NK_ASSERT_DEBUG(me->iInKernel);
   295 	aExc.iFlags = me->iInKernel == 1 ? 0 : TWin32ExcInfo::EExcInKernel;
   296 	aExc.iHandler = NULL;
   297 
   298 	// run NThread exception handler in 'kernel' mode
   299 	me->iHandlers->iExceptionHandler(&aExc, me);
   300 	LeaveKernel();
   301 
   302 	// If a 'user' handler is set by the kernel handler, run it
   303 	if (aExc.iHandler)
   304 		aExc.iHandler(aExc.iParam[0], aExc.iParam[1]);
   305 	}
   306 
   307 void NKern__Unlock()
   308 //
   309 // CW asm ICE workaround
   310 //
   311 	{
   312 	NKern::Unlock();
   313 	}
   314 
   315 __NAKED__ void NThread::Exception()
   316 //
   317 // Trampoline to nKern exception handler
   318 // must preserve all registers in the structure defined by TWin32Exc
   319 //
   320 	{
   321 	// this is the TWin32Exc structure
   322 	__asm push Win32ExcAddress			// save return address followed by EBP first to help debugger
   323 	__asm push ebp
   324 	__asm mov ebp, esp
   325 	__asm push cs
   326 	__asm pushfd
   327 	__asm push gs
   328 	__asm push fs
   329 	__asm push es
   330 	__asm push ds
   331 	__asm push ss
   332 	__asm push edi
   333 	__asm push esi
   334 	__asm lea esi, [ebp+8]
   335 	__asm push esi		// original esp
   336 	__asm push ebx
   337 	__asm push edx
   338 	__asm push ecx
   339 	__asm push eax
   340 	__asm push Win32ExcDataAddress
   341 	__asm push Win32ExcCode
   342 	__asm sub esp, 20	// struct init completed by NThread__HandleException()
   343 
   344 	__asm call NKern__Unlock
   345 
   346 	__asm call NThread__HandleException
   347 
   348 	__asm add esp, 28
   349 	__asm pop eax
   350 	__asm pop ecx
   351 	__asm pop edx
   352 	__asm pop ebx
   353 	__asm pop esi		// original ESP - ignore
   354 	__asm pop esi
   355 	__asm pop edi
   356 	__asm pop ebp		// original SS - ignore
   357 	__asm pop ds
   358 	__asm pop es
   359 	__asm pop fs
   360 	__asm pop gs
   361 	__asm popfd
   362 	__asm pop ebp		// original CS - ignore
   363 	__asm pop ebp
   364 	__asm ret
   365 	}
   366 
   367 LONG WINAPI NThread::ExceptionFilter(EXCEPTION_POINTERS* aExc)
   368 //
   369 // Filter wrapper for main Win32 exception handler
   370 //
   371 	{
   372 	LONG ret = EXCEPTION_CONTINUE_SEARCH;
   373 
   374 	switch (ExceptionHandler(aExc->ExceptionRecord, aExc->ContextRecord))
   375 		{
   376 		case ExceptionContinueExecution:
   377 			{
   378 			ret = EXCEPTION_CONTINUE_EXECUTION;
   379 			}
   380 			break;
   381 		case ExceptionContinueSearch:
   382 		default:
   383 			{
   384 			}
   385 			break;
   386 		}
   387 
   388 	return ret;
   389 	}
   390 
   391 // From e32/commmon/win32/seh.cpp
   392 extern DWORD CallFinalSEHHandler(EXCEPTION_RECORD* aException, CONTEXT* aContext);
   393 
   394 extern void DivertHook();
   395 
   396 DWORD NThread::ExceptionHandler(EXCEPTION_RECORD* aException, CONTEXT* aContext)
   397 //
   398 // Win32 exception handler for EPOC threads
   399 //
   400 	{
   401 	if (aException->ExceptionCode == EXCEPTION_BREAKPOINT)
   402 		{
   403 		// Hardcoded breakpoint
   404 		//
   405 		// Jump directly to NT's default unhandled exception handler which will
   406 		// either display a dialog, directly invoke the JIT debugger or do nothing
   407 		// dependent upon the AeDebug and ErrorMode registry settings.
   408 		//
   409 		// Note this handler is always installed on the SEH chain and is always
   410 		// the last handler on this chain, as it is installed by NT in kernel32.dll
   411 		// before invoking the Win32 thread function.
   412 		return CallFinalSEHHandler(aException, aContext);
   413 		}
   414 
   415 	// deal with conflict between preemption and diversion
   416 	// the diversion will have been applied to the pre-exception context, not
   417 	// the current context, and thus will get 'lost'. Wake-up of a pre-empted
   418 	// thread with a diversion will not unlock the kernel, so need to deal with
   419 	// the possibility that the kernel may be locked if a diversion exists
   420 
   421 	NThread& me = *static_cast<NThread*>(TheScheduler.iCurrentThread);
   422 	if (me.iDiverted && me.iDivert)
   423 		{
   424 		// The thread is being forced to exit - run the diversion outside of Win32 exception handler
   425 		__NK_ASSERT_ALWAYS(TheScheduler.iKernCSLocked == 1);
   426 		aContext->Eip = (TUint32)&DivertHook;
   427 		}
   428 	else
   429 		{
   430 		if (me.iDiverted)
   431 			{
   432 			// The thread is being prodded to pick up its callbacks.  This will happen when the
   433 			// exception handler calls LeaveKernel(), so we can remove the diversion
   434 			__NK_ASSERT_ALWAYS(TheScheduler.iKernCSLocked == 1);
   435 			if (aException->ExceptionAddress == &DivertHook)
   436 				aException->ExceptionAddress = me.iDivertReturn;
   437 			me.iDiverted = EFalse;
   438 			me.iDivertReturn = NULL;
   439 			EnterKernel(FALSE);
   440 			}
   441 		else
   442 			{
   443 			EnterKernel();
   444 			TheScheduler.iKernCSLocked = 1;	// prevent pre-emption
   445 			}
   446 		
   447 		// If the kernel was already locked, this will be detected in the next stage handler
   448 		// run 2nd stage handler outside of Win32 exception context
   449 		Win32ExcAddress = aException->ExceptionAddress;
   450 		Win32ExcDataAddress = (TAny*)aException->ExceptionInformation[1];
   451 		Win32ExcCode = aException->ExceptionCode;
   452 		aContext->Eip = (TUint32)&Exception;
   453 		}
   454 	return ExceptionContinueExecution;
   455 	}
   456 
   457 void NThread::Diverted()
   458 //
   459 // Forced diversion go through here, in order to 'enter' the kernel
   460 //
   461 	{
   462 	NThread& me = *static_cast<NThread*>(TheScheduler.iCurrentThread);
   463 	__NK_ASSERT_ALWAYS(TheScheduler.iKernCSLocked == 1);
   464 	__NK_ASSERT_ALWAYS(me.iDiverted);
   465 	NThread::TDivert divert = me.iDivert;
   466 	me.iDiverted = EFalse;
   467 	me.iDivert = NULL;
   468 	me.iDivertReturn = NULL;
   469 	EnterKernel(FALSE);
   470 	if (divert)
   471 		divert();	// does not return
   472 	NKern::Unlock();
   473 	LeaveKernel();
   474 	}
   475 
   476 void NThread__Diverted()
   477 	{
   478 	NThread::Diverted();
   479 	}
   480 
   481 __NAKED__ void DivertHook()
   482 	{
   483 	// The stack frame is set up like this:
   484 	//
   485 	//		| return address |
   486 	//		| frame pointer  |
   487 	//		| flags			 |
   488 	//		| saved eax		 |
   489 	//		| saved ecx		 |
   490 	//      | saved edx		 |
   491 	//		
   492 	__asm push eax					// reserve word for return address
   493 	__asm push ebp
   494 	__asm mov ebp, esp 
   495 	__asm pushfd
   496 	__asm push eax
   497 	__asm push ecx
   498 	__asm push edx
   499 	__asm mov eax, [TheScheduler.iCurrentThread]
   500 	__asm mov eax, [eax]NThread.iDivertReturn
   501 	__asm mov [esp + 20], eax		// store return address
   502 	__asm call NThread__Diverted
   503 	__asm pop edx
   504 	__asm pop ecx
   505 	__asm pop eax
   506 	__asm popfd
   507 	__asm pop ebp
   508 	__asm ret
   509 	}
   510 
   511 
   512 void NThread::ApplyDiversion()
   513 	{
   514 	// Called with interrupts disabled and kernel locked
   515 	__NK_ASSERT_ALWAYS(TheScheduler.iKernCSLocked == 1);
   516 	if (iDiverted)
   517 		return;
   518 	CONTEXT c;
   519 	c.ContextFlags=CONTEXT_FULL;
   520 	GetThreadContext(iWinThread, &c);
   521 	__NK_ASSERT_ALWAYS(iDivertReturn == NULL);
   522 	iDivertReturn = (TAny*)c.Eip;
   523 	c.Eip=(TUint32)&DivertHook;
   524 	SetThreadContext(iWinThread, &c);
   525 	iDiverted = ETrue;
   526 	}
   527 
   528 void NThread::Divert(TDivert aDivert)
   529 //
   530 // Divert the thread from its current path
   531 // The diversion function is called with the kernel locked and interrupts enabled
   532 //
   533 	{
   534 	iDivert = aDivert;
   535 	if (iWakeup == EResume)
   536 		iWakeup = EResumeDiverted;
   537 	else
   538 		__NK_ASSERT_ALWAYS(iWakeup == ERelease);
   539 	}
   540 
   541 void NThread::ExitSync()
   542 //
   543 // Diversion used to terminate 'stillborn' threads.
   544 // On entry, kernel is locked, interrupts are enabled and we hold an interlock mutex
   545 //
   546 	{
   547 	NThreadBase& me=*TheScheduler.iCurrentThread;
   548 	me.iHeldFastMutex->Signal();	// release the interlock
   549 	me.iNState=EDead;				// mark ourselves as dead which will take thread out of scheduler
   550 	TheScheduler.Remove(&me);
   551 	RescheduleNeeded();
   552 	TScheduler::Reschedule();	// this won't return
   553 	FAULT();
   554 	}
   555 
   556 void NThread::Stillborn()
   557 //
   558 // Called if the new thread creation was aborted - so it will not be killed in the usual manner
   559 //
   560 // This function needs to exit the thread synchronously as on return we will destroy the thread control block
   561 // Thus wee need to use an interlock that ensure that the target thread runs the exit handler before we continue
   562 //
   563 	{
   564 	// check if the Win32 thread was created
   565 	if (!iWinThread)
   566 		return;
   567 
   568 	NKern::Lock();
   569 	Divert(&ExitSync);
   570 	ForceResume();
   571 	// create and assign mutex to stillborn thread
   572 	NFastMutex interlock;
   573 	interlock.iHoldingThread=this;
   574 	iHeldFastMutex=&interlock;
   575 	interlock.Wait();			// interlock on thread exit handler
   576 	interlock.Signal();
   577 	NKern::Unlock();
   578 	}
   579 
   580 void NThread::ExitAsync()
   581 //
   582 // Diversion used to terminate 'killed' threads.
   583 // On entry, kernel is locked and interrupts are enabled
   584 //
   585 	{
   586 	NThreadBase& me = *TheScheduler.iCurrentThread;
   587 	me.iCsCount = 0;
   588 	__NK_ASSERT_ALWAYS(static_cast<NThread&>(me).iInKernel>0);
   589 	me.Exit();
   590 	}
   591 
   592 void NThreadBase::OnKill()
   593 	{
   594 	}
   595 
   596 void NThreadBase::OnExit()
   597 	{
   598 	}
   599 
   600 inline void NThread::DoForceExit()
   601 	{
   602 	__NK_ASSERT_DEBUG(TheScheduler.iKernCSLocked);
   603 //
   604 	Divert(&ExitAsync);
   605 	}
   606 
   607 void NThreadBase::ForceExit()
   608 //
   609 // Called to force the thread to exit when it resumes
   610 //
   611 	{
   612 	static_cast<NThread*>(this)->DoForceExit();
   613 	}
   614 
   615 //
   616 // We need a global lock in the emulator to avoid scheduling reentrancy problems with the host
   617 // in particular, some host API calls acquire host mutexes, preempting such services results
   618 // in suspension of those threads which can cause deadlock if another thread requires that host
   619 // mutex.
   620 //
   621 // Because thread dreaction and code loading also require the same underlying mutex (used
   622 // by NT to protect DLL entrypoint calling), this would be rather complex with a fast mutex.
   623 // For now, keep it simple and use the preemption lock. Note that this means that the
   624 // MS timer DFC may be significantly delayed when loading large DLL trees, for example.
   625 //
   626 
   627 void SchedulerLock()
   628 //
   629 // Acquire the global lock. May be called before scheduler running, so handle that case
   630 //
   631 	{
   632 	if (TheScheduler.iCurrentThread)
   633 		{
   634 		EnterKernel();
   635 		NKern::Lock();
   636 		}
   637 	}
   638 
   639 void SchedulerUnlock()
   640 //
   641 // Release the global lock. May be called before scheduler running, so handle that case
   642 //
   643 	{
   644 	if (TheScheduler.iCurrentThread)
   645 		{
   646 		NKern::Unlock();
   647 		LeaveKernel();
   648 		}
   649 	}
   650