sl@0: // Copyright (c) 1998-2009 Nokia Corporation and/or its subsidiary(-ies). sl@0: // All rights reserved. sl@0: // This component and the accompanying materials are made available sl@0: // under the terms of the License "Eclipse Public License v1.0" sl@0: // which accompanies this distribution, and is available sl@0: // at the URL "http://www.eclipse.org/legal/epl-v10.html". sl@0: // sl@0: // Initial Contributors: sl@0: // Nokia Corporation - initial contribution. sl@0: // sl@0: // Contributors: sl@0: // sl@0: // Description: sl@0: // e32\nkern\win32\ncthrd.cpp sl@0: // sl@0: // sl@0: sl@0: // NThreadBase member data sl@0: #define __INCLUDE_NTHREADBASE_DEFINES__ sl@0: sl@0: #include "nk_priv.h" sl@0: #include sl@0: sl@0: extern "C" void ExcFault(TAny*); sl@0: sl@0: // initial Win32 thread stack size sl@0: const TInt KInitialStackSize = 0x1000; sl@0: sl@0: // maximum size of the parameter block passed to a new thread sl@0: const TInt KMaxParameterBlock = 512; sl@0: sl@0: // data passed to new thread to enable hand-off of the parameter block sl@0: struct SCreateThread sl@0: { sl@0: const SNThreadCreateInfo* iInfo; sl@0: NFastMutex iHandoff; sl@0: }; sl@0: sl@0: /** sl@0: * Set the Win32 thread priority based on the thread type. sl@0: * Interrupt/Event threads must be able to preempt normal nKern threads, sl@0: * so they get a higher priority. sl@0: */ sl@0: static void SetPriority(HANDLE aThread, TEmulThreadType aType) sl@0: { sl@0: TInt p; sl@0: switch (aType) sl@0: { sl@0: default: sl@0: FAULT(); sl@0: case EThreadEvent: sl@0: p = THREAD_PRIORITY_ABOVE_NORMAL; sl@0: break; sl@0: case EThreadNKern: sl@0: p = THREAD_PRIORITY_NORMAL; sl@0: break; sl@0: } sl@0: sl@0: __NK_ASSERT_ALWAYS(SetThreadPriority(aThread, p)); sl@0: SetThreadPriorityBoost(aThread, TRUE); // disable priority boost (for NT) sl@0: } sl@0: sl@0: sl@0: /** Create a Win32 thread for use in the emulator. sl@0: sl@0: @param aType Type of thread (Event or NKern) - determines Win32 priority sl@0: @param aThreadFunc Entry point of thread sl@0: @param aPtr Argument passed to entry point sl@0: @param aRun TRUE if thread should be resumed immediately sl@0: @return The Win32 handle to the thread, 0 if an error occurred sl@0: sl@0: @pre Call either in thread context. sl@0: @pre Do not call from bare Win32 threads. sl@0: sl@0: @see TEmulThreadType sl@0: */ sl@0: EXPORT_C HANDLE CreateWin32Thread(TEmulThreadType aType, LPTHREAD_START_ROUTINE aThreadFunc, LPVOID aPtr, TBool aRun) sl@0: { sl@0: __NK_ASSERT_DEBUG(!TheScheduler.iCurrentThread || NKern::CurrentContext() == NKern::EThread); sl@0: sl@0: __LOCK_HOST; sl@0: sl@0: DWORD id; sl@0: HANDLE handle = CreateThread(NULL , KInitialStackSize, aThreadFunc, aPtr, CREATE_SUSPENDED, &id); sl@0: if (handle) sl@0: { sl@0: SetPriority(handle, aType); sl@0: if (aRun) sl@0: ResumeThread(handle); sl@0: } sl@0: return handle; sl@0: } sl@0: sl@0: sl@0: /** Set some global properties of the emulator sl@0: Called by the Win32 base port during boot. sl@0: sl@0: @param aTrace TRUE means trace Win32 thread ID for every thread created sl@0: @param aSingleCpu TRUE means lock the emulator process to a single CPU sl@0: sl@0: @internalTechnology sl@0: */ sl@0: EXPORT_C void NThread::SetProperties(TBool aTrace, TInt aSingleCpu) sl@0: { sl@0: Win32TraceThreadId = aTrace; sl@0: Win32SingleCpu = aSingleCpu; sl@0: } sl@0: sl@0: #if defined(__CW32__) && __MWERKS__ < 0x3200 sl@0: DWORD NThread__ExceptionHandler(EXCEPTION_RECORD* aException, TAny* /*aRegistrationRecord*/, CONTEXT* aContext) sl@0: // sl@0: // Hook into exception handling for old version of CW sl@0: // sl@0: { sl@0: return NThread::ExceptionHandler(aException, aContext); sl@0: } sl@0: #endif // old __CW32__ sl@0: sl@0: DWORD WINAPI NThread::StartThread(LPVOID aParam) sl@0: // sl@0: // Win32 thread function for nKern threads. sl@0: // sl@0: // The thread first enters this function after the nScheduler has resumed sl@0: // it, following the context switch induced by the hand-off mutex. sl@0: // sl@0: // The parameter block for this thread needs to be copied into its sl@0: // own context, before releasing the mutex and handing control back to sl@0: // the creating thread. sl@0: // sl@0: { sl@0: SCreateThread* init = static_cast(aParam); sl@0: NThread& me=*static_cast(init->iHandoff.iHoldingThread); sl@0: me.iWinThreadId = GetCurrentThreadId(); sl@0: SchedulerRegister(me); sl@0: #ifdef BTRACE_FAST_MUTEX sl@0: BTraceContext4(BTrace::EFastMutex,BTrace::EFastMutexWait,&init->iHandoff); sl@0: #endif sl@0: NKern::Unlock(); sl@0: sl@0: #if defined(__CW32__) && __MWERKS__ < 0x3200 sl@0: // intercept the win32 exception mechanism manually sl@0: asm { sl@0: push ebp sl@0: mov eax, -1 sl@0: push eax sl@0: push eax sl@0: push offset NThread__ExceptionHandler sl@0: push fs:[0] sl@0: mov fs:[0], esp sl@0: sl@0: // realign the stack sl@0: sub esp, 0x20 sl@0: and esp, ~0x1f sl@0: } sl@0: #else // ! old __CW32__ sl@0: // intercept win32 exceptions in a debuggabble way sl@0: __try { sl@0: #endif // old __CW32__ sl@0: sl@0: // save the thread entry point and parameter block sl@0: const SNThreadCreateInfo& info = *init->iInfo; sl@0: TUint8 parameterBlock[KMaxParameterBlock]; sl@0: TAny* parameter=(TAny*)info.iParameterBlock; sl@0: if (info.iParameterBlockSize) sl@0: { sl@0: __NK_ASSERT_DEBUG(TUint(info.iParameterBlockSize)<=TUint(KMaxParameterBlock)); sl@0: parameter=parameterBlock; sl@0: memcpy(parameterBlock,info.iParameterBlock,info.iParameterBlockSize); sl@0: } sl@0: NThreadFunction threadFunction=info.iFunction; sl@0: sl@0: // Calculate stack base sl@0: me.iUserStackBase = (((TLinAddr)¶meterBlock)+0xfff)&~0xfff; // base address of stack sl@0: sl@0: // some useful diagnostics for debugging sl@0: if (Win32TraceThreadId) sl@0: KPrintf("Thread %T created @ 0x%x - Win32 Thread ID 0x%x",init->iHandoff.iHoldingThread,init->iHandoff.iHoldingThread,GetCurrentThreadId()); sl@0: sl@0: #ifdef MONITOR_THREAD_CPU_TIME sl@0: me.iLastStartTime = 0; // Don't count NThread setup in cpu time sl@0: #endif sl@0: sl@0: // start-up complete, release the handoff mutex, which will re-suspend us sl@0: NKern::FMSignal(&init->iHandoff); sl@0: sl@0: // thread has been resumed: invoke the thread function sl@0: threadFunction(parameter); sl@0: sl@0: #if !defined(__CW32__) || __MWERKS__ >= 0x3200 sl@0: // handle win32 exceptions sl@0: } __except (ExceptionFilter(GetExceptionInformation())) { sl@0: // Do nothing - filter does all the work and hooks sl@0: // into EPOC h/w exception mechanism if necessary sl@0: // by thread diversion sl@0: } sl@0: #endif // !old __CW32__ sl@0: sl@0: NKern::Exit(); sl@0: sl@0: return 0; sl@0: } sl@0: sl@0: static HANDLE InitThread() sl@0: // sl@0: // Set up the initial thread and return the thread handle sl@0: // sl@0: { sl@0: HANDLE p = GetCurrentProcess(); sl@0: HANDLE me; sl@0: __NK_ASSERT_ALWAYS(DuplicateHandle(p, GetCurrentThread(), p, &me, 0, FALSE, DUPLICATE_SAME_ACCESS)); sl@0: SetPriority(me, EThreadNKern); sl@0: return me; sl@0: } sl@0: sl@0: TInt NThread::Create(SNThreadCreateInfo& aInfo, TBool aInitial) sl@0: { sl@0: iWinThread = NULL; sl@0: iWinThreadId = 0; sl@0: iScheduleLock = NULL; sl@0: iInKernel = 1; sl@0: iDivert = NULL; sl@0: iWakeup = aInitial ? ERelease : EResumeLocked; // mark new threads as created (=> win32 suspend) sl@0: sl@0: TInt r=NThreadBase::Create(aInfo,aInitial); sl@0: if (r!=KErrNone) sl@0: return r; sl@0: sl@0: // the rest has to be all or nothing, we must complete it sl@0: iScheduleLock = CreateEventA(NULL, FALSE, FALSE, NULL); sl@0: if (iScheduleLock == NULL) sl@0: return Emulator::LastError(); sl@0: sl@0: if (aInitial) sl@0: { sl@0: iWinThread = InitThread(); sl@0: FastCounterInit(); sl@0: #ifdef MONITOR_THREAD_CPU_TIME sl@0: iLastStartTime = NKern::FastCounter(); sl@0: #endif sl@0: iUserStackBase = (((TLinAddr)&r)+0xfff)&~0xfff; // base address of stack sl@0: SchedulerInit(*this); sl@0: return KErrNone; sl@0: } sl@0: sl@0: // create the thread proper sl@0: // sl@0: SCreateThread start; sl@0: start.iInfo = &aInfo; sl@0: sl@0: iWinThread = CreateWin32Thread(EThreadNKern, &StartThread, &start, FALSE); sl@0: if (iWinThread == NULL) sl@0: { sl@0: r = Emulator::LastError(); sl@0: CloseHandle(iScheduleLock); sl@0: return r; sl@0: } sl@0: sl@0: #ifdef BTRACE_THREAD_IDENTIFICATION sl@0: BTrace4(BTrace::EThreadIdentification,BTrace::ENanoThreadCreate,this); sl@0: #endif sl@0: // switch to the new thread to hand over the parameter block sl@0: NKern::Lock(); sl@0: ForceResume(); // mark the thread as ready sl@0: // give the thread ownership of the handoff mutex sl@0: start.iHandoff.iHoldingThread = this; sl@0: iHeldFastMutex = &start.iHandoff; sl@0: Suspend(1); // will defer as holding a fast mutex (implicit critical section) sl@0: // do the hand-over sl@0: start.iHandoff.Wait(); sl@0: start.iHandoff.Signal(); sl@0: NKern::Unlock(); sl@0: sl@0: return KErrNone; sl@0: } sl@0: sl@0: void NThread__HandleException(TWin32ExcInfo aExc) sl@0: // sl@0: // Final stage NKern exception handler. sl@0: // sl@0: // Check for a fatal exception when the kernel is locked sl@0: // sl@0: // Note that the parameter struct is passed by value, this allows for sl@0: // direct access to the exception context created on the call stack by sl@0: // NThread::Exception(). sl@0: // sl@0: { sl@0: if (TheScheduler.iKernCSLocked) sl@0: ExcFault(&aExc); sl@0: sl@0: // Complete the exception data. Note that the call to EnterKernel() in sl@0: // ExceptionFilter() will have incremented iInKernel after the exception sl@0: // occurred. sl@0: NThread* me = static_cast(TheScheduler.iCurrentThread); sl@0: __NK_ASSERT_DEBUG(me->iInKernel); sl@0: aExc.iFlags = me->iInKernel == 1 ? 0 : TWin32ExcInfo::EExcInKernel; sl@0: aExc.iHandler = NULL; sl@0: sl@0: // run NThread exception handler in 'kernel' mode sl@0: me->iHandlers->iExceptionHandler(&aExc, me); sl@0: LeaveKernel(); sl@0: sl@0: // If a 'user' handler is set by the kernel handler, run it sl@0: if (aExc.iHandler) sl@0: aExc.iHandler(aExc.iParam[0], aExc.iParam[1]); sl@0: } sl@0: sl@0: void NKern__Unlock() sl@0: // sl@0: // CW asm ICE workaround sl@0: // sl@0: { sl@0: NKern::Unlock(); sl@0: } sl@0: sl@0: __NAKED__ void NThread::Exception() sl@0: // sl@0: // Trampoline to nKern exception handler sl@0: // must preserve all registers in the structure defined by TWin32Exc sl@0: // sl@0: { sl@0: // this is the TWin32Exc structure sl@0: __asm push Win32ExcAddress // save return address followed by EBP first to help debugger sl@0: __asm push ebp sl@0: __asm mov ebp, esp sl@0: __asm push cs sl@0: __asm pushfd sl@0: __asm push gs sl@0: __asm push fs sl@0: __asm push es sl@0: __asm push ds sl@0: __asm push ss sl@0: __asm push edi sl@0: __asm push esi sl@0: __asm lea esi, [ebp+8] sl@0: __asm push esi // original esp sl@0: __asm push ebx sl@0: __asm push edx sl@0: __asm push ecx sl@0: __asm push eax sl@0: __asm push Win32ExcDataAddress sl@0: __asm push Win32ExcCode sl@0: __asm sub esp, 20 // struct init completed by NThread__HandleException() sl@0: sl@0: __asm call NKern__Unlock sl@0: sl@0: __asm call NThread__HandleException sl@0: sl@0: __asm add esp, 28 sl@0: __asm pop eax sl@0: __asm pop ecx sl@0: __asm pop edx sl@0: __asm pop ebx sl@0: __asm pop esi // original ESP - ignore sl@0: __asm pop esi sl@0: __asm pop edi sl@0: __asm pop ebp // original SS - ignore sl@0: __asm pop ds sl@0: __asm pop es sl@0: __asm pop fs sl@0: __asm pop gs sl@0: __asm popfd sl@0: __asm pop ebp // original CS - ignore sl@0: __asm pop ebp sl@0: __asm ret sl@0: } sl@0: sl@0: LONG WINAPI NThread::ExceptionFilter(EXCEPTION_POINTERS* aExc) sl@0: // sl@0: // Filter wrapper for main Win32 exception handler sl@0: // sl@0: { sl@0: LONG ret = EXCEPTION_CONTINUE_SEARCH; sl@0: sl@0: switch (ExceptionHandler(aExc->ExceptionRecord, aExc->ContextRecord)) sl@0: { sl@0: case ExceptionContinueExecution: sl@0: { sl@0: ret = EXCEPTION_CONTINUE_EXECUTION; sl@0: } sl@0: break; sl@0: case ExceptionContinueSearch: sl@0: default: sl@0: { sl@0: } sl@0: break; sl@0: } sl@0: sl@0: return ret; sl@0: } sl@0: sl@0: // From e32/commmon/win32/seh.cpp sl@0: extern DWORD CallFinalSEHHandler(EXCEPTION_RECORD* aException, CONTEXT* aContext); sl@0: sl@0: extern void DivertHook(); sl@0: sl@0: DWORD NThread::ExceptionHandler(EXCEPTION_RECORD* aException, CONTEXT* aContext) sl@0: // sl@0: // Win32 exception handler for EPOC threads sl@0: // sl@0: { sl@0: if (aException->ExceptionCode == EXCEPTION_BREAKPOINT) sl@0: { sl@0: // Hardcoded breakpoint sl@0: // sl@0: // Jump directly to NT's default unhandled exception handler which will sl@0: // either display a dialog, directly invoke the JIT debugger or do nothing sl@0: // dependent upon the AeDebug and ErrorMode registry settings. sl@0: // sl@0: // Note this handler is always installed on the SEH chain and is always sl@0: // the last handler on this chain, as it is installed by NT in kernel32.dll sl@0: // before invoking the Win32 thread function. sl@0: return CallFinalSEHHandler(aException, aContext); sl@0: } sl@0: sl@0: // deal with conflict between preemption and diversion sl@0: // the diversion will have been applied to the pre-exception context, not sl@0: // the current context, and thus will get 'lost'. Wake-up of a pre-empted sl@0: // thread with a diversion will not unlock the kernel, so need to deal with sl@0: // the possibility that the kernel may be locked if a diversion exists sl@0: sl@0: NThread& me = *static_cast(TheScheduler.iCurrentThread); sl@0: if (me.iDiverted && me.iDivert) sl@0: { sl@0: // The thread is being forced to exit - run the diversion outside of Win32 exception handler sl@0: __NK_ASSERT_ALWAYS(TheScheduler.iKernCSLocked == 1); sl@0: aContext->Eip = (TUint32)&DivertHook; sl@0: } sl@0: else sl@0: { sl@0: if (me.iDiverted) sl@0: { sl@0: // The thread is being prodded to pick up its callbacks. This will happen when the sl@0: // exception handler calls LeaveKernel(), so we can remove the diversion sl@0: __NK_ASSERT_ALWAYS(TheScheduler.iKernCSLocked == 1); sl@0: if (aException->ExceptionAddress == &DivertHook) sl@0: aException->ExceptionAddress = me.iDivertReturn; sl@0: me.iDiverted = EFalse; sl@0: me.iDivertReturn = NULL; sl@0: EnterKernel(FALSE); sl@0: } sl@0: else sl@0: { sl@0: EnterKernel(); sl@0: TheScheduler.iKernCSLocked = 1; // prevent pre-emption sl@0: } sl@0: sl@0: // If the kernel was already locked, this will be detected in the next stage handler sl@0: // run 2nd stage handler outside of Win32 exception context sl@0: Win32ExcAddress = aException->ExceptionAddress; sl@0: Win32ExcDataAddress = (TAny*)aException->ExceptionInformation[1]; sl@0: Win32ExcCode = aException->ExceptionCode; sl@0: aContext->Eip = (TUint32)&Exception; sl@0: } sl@0: return ExceptionContinueExecution; sl@0: } sl@0: sl@0: void NThread::Diverted() sl@0: // sl@0: // Forced diversion go through here, in order to 'enter' the kernel sl@0: // sl@0: { sl@0: NThread& me = *static_cast(TheScheduler.iCurrentThread); sl@0: __NK_ASSERT_ALWAYS(TheScheduler.iKernCSLocked == 1); sl@0: __NK_ASSERT_ALWAYS(me.iDiverted); sl@0: NThread::TDivert divert = me.iDivert; sl@0: me.iDiverted = EFalse; sl@0: me.iDivert = NULL; sl@0: me.iDivertReturn = NULL; sl@0: EnterKernel(FALSE); sl@0: if (divert) sl@0: divert(); // does not return sl@0: NKern::Unlock(); sl@0: LeaveKernel(); sl@0: } sl@0: sl@0: void NThread__Diverted() sl@0: { sl@0: NThread::Diverted(); sl@0: } sl@0: sl@0: __NAKED__ void DivertHook() sl@0: { sl@0: // The stack frame is set up like this: sl@0: // sl@0: // | return address | sl@0: // | frame pointer | sl@0: // | flags | sl@0: // | saved eax | sl@0: // | saved ecx | sl@0: // | saved edx | sl@0: // sl@0: __asm push eax // reserve word for return address sl@0: __asm push ebp sl@0: __asm mov ebp, esp sl@0: __asm pushfd sl@0: __asm push eax sl@0: __asm push ecx sl@0: __asm push edx sl@0: __asm mov eax, [TheScheduler.iCurrentThread] sl@0: __asm mov eax, [eax]NThread.iDivertReturn sl@0: __asm mov [esp + 20], eax // store return address sl@0: __asm call NThread__Diverted sl@0: __asm pop edx sl@0: __asm pop ecx sl@0: __asm pop eax sl@0: __asm popfd sl@0: __asm pop ebp sl@0: __asm ret sl@0: } sl@0: sl@0: sl@0: void NThread::ApplyDiversion() sl@0: { sl@0: // Called with interrupts disabled and kernel locked sl@0: __NK_ASSERT_ALWAYS(TheScheduler.iKernCSLocked == 1); sl@0: if (iDiverted) sl@0: return; sl@0: CONTEXT c; sl@0: c.ContextFlags=CONTEXT_FULL; sl@0: GetThreadContext(iWinThread, &c); sl@0: __NK_ASSERT_ALWAYS(iDivertReturn == NULL); sl@0: iDivertReturn = (TAny*)c.Eip; sl@0: c.Eip=(TUint32)&DivertHook; sl@0: SetThreadContext(iWinThread, &c); sl@0: iDiverted = ETrue; sl@0: } sl@0: sl@0: void NThread::Divert(TDivert aDivert) sl@0: // sl@0: // Divert the thread from its current path sl@0: // The diversion function is called with the kernel locked and interrupts enabled sl@0: // sl@0: { sl@0: iDivert = aDivert; sl@0: if (iWakeup == EResume) sl@0: iWakeup = EResumeDiverted; sl@0: else sl@0: __NK_ASSERT_ALWAYS(iWakeup == ERelease); sl@0: } sl@0: sl@0: void NThread::ExitSync() sl@0: // sl@0: // Diversion used to terminate 'stillborn' threads. sl@0: // On entry, kernel is locked, interrupts are enabled and we hold an interlock mutex sl@0: // sl@0: { sl@0: NThreadBase& me=*TheScheduler.iCurrentThread; sl@0: me.iHeldFastMutex->Signal(); // release the interlock sl@0: me.iNState=EDead; // mark ourselves as dead which will take thread out of scheduler sl@0: TheScheduler.Remove(&me); sl@0: RescheduleNeeded(); sl@0: TScheduler::Reschedule(); // this won't return sl@0: FAULT(); sl@0: } sl@0: sl@0: void NThread::Stillborn() sl@0: // sl@0: // Called if the new thread creation was aborted - so it will not be killed in the usual manner sl@0: // sl@0: // This function needs to exit the thread synchronously as on return we will destroy the thread control block sl@0: // Thus wee need to use an interlock that ensure that the target thread runs the exit handler before we continue sl@0: // sl@0: { sl@0: // check if the Win32 thread was created sl@0: if (!iWinThread) sl@0: return; sl@0: sl@0: NKern::Lock(); sl@0: Divert(&ExitSync); sl@0: ForceResume(); sl@0: // create and assign mutex to stillborn thread sl@0: NFastMutex interlock; sl@0: interlock.iHoldingThread=this; sl@0: iHeldFastMutex=&interlock; sl@0: interlock.Wait(); // interlock on thread exit handler sl@0: interlock.Signal(); sl@0: NKern::Unlock(); sl@0: } sl@0: sl@0: void NThread::ExitAsync() sl@0: // sl@0: // Diversion used to terminate 'killed' threads. sl@0: // On entry, kernel is locked and interrupts are enabled sl@0: // sl@0: { sl@0: NThreadBase& me = *TheScheduler.iCurrentThread; sl@0: me.iCsCount = 0; sl@0: __NK_ASSERT_ALWAYS(static_cast(me).iInKernel>0); sl@0: me.Exit(); sl@0: } sl@0: sl@0: void NThreadBase::OnKill() sl@0: { sl@0: } sl@0: sl@0: void NThreadBase::OnExit() sl@0: { sl@0: } sl@0: sl@0: inline void NThread::DoForceExit() sl@0: { sl@0: __NK_ASSERT_DEBUG(TheScheduler.iKernCSLocked); sl@0: // sl@0: Divert(&ExitAsync); sl@0: } sl@0: sl@0: void NThreadBase::ForceExit() sl@0: // sl@0: // Called to force the thread to exit when it resumes sl@0: // sl@0: { sl@0: static_cast(this)->DoForceExit(); sl@0: } sl@0: sl@0: // sl@0: // We need a global lock in the emulator to avoid scheduling reentrancy problems with the host sl@0: // in particular, some host API calls acquire host mutexes, preempting such services results sl@0: // in suspension of those threads which can cause deadlock if another thread requires that host sl@0: // mutex. sl@0: // sl@0: // Because thread dreaction and code loading also require the same underlying mutex (used sl@0: // by NT to protect DLL entrypoint calling), this would be rather complex with a fast mutex. sl@0: // For now, keep it simple and use the preemption lock. Note that this means that the sl@0: // MS timer DFC may be significantly delayed when loading large DLL trees, for example. sl@0: // sl@0: sl@0: void SchedulerLock() sl@0: // sl@0: // Acquire the global lock. May be called before scheduler running, so handle that case sl@0: // sl@0: { sl@0: if (TheScheduler.iCurrentThread) sl@0: { sl@0: EnterKernel(); sl@0: NKern::Lock(); sl@0: } sl@0: } sl@0: sl@0: void SchedulerUnlock() sl@0: // sl@0: // Release the global lock. May be called before scheduler running, so handle that case sl@0: // sl@0: { sl@0: if (TheScheduler.iCurrentThread) sl@0: { sl@0: NKern::Unlock(); sl@0: LeaveKernel(); sl@0: } sl@0: } sl@0: