//////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: Thread.cs // // Note: // // The Thread class and the Scheduler interact through three mechanisms. // // First, the synchronization operations acquire the Scheduler's dispatch // lock (via Scheduler.DispatchLock() and Scheduler.DispatchRelease() // to ensure that no two processors ever attempt to dispatch on the block // or release threads at exactly the same time. // // Second, the Thread class notifies the Scheduler of important events // in the life of each thread. These notifications are done via overrides // on the thread class. The mixin overrides are: // Scheduler.OnThreadStateInitialize(): Thread has been created. // Scheduler.OnThreadStart(): Thread is ready to start. // Scheduler.OnThreadBlocked(): Thread just blocked on a handle. // Scheduler.OnThreadUnblocked(): Thread is now runnable. // Scheduler.OnThreadYield(): Thread yields processor. // Scheduler.OnThreadStop(): Thread is ready to stop. // Scheduler.OnThreadFreezeIncrement(): Freeze thread, incr count. // Scheduler.OnThreadFreezeDecrement(): Decrement count, if 0 then unfreeze // // Third, the Scheduler calls Thread.Stopped() when it has finish with a // thread that is no longer runnable. // // #define DEBUG_SWITCH namespace System.Threading { using System.Threading; using System.Runtime.InteropServices; using System; using System.Diagnostics; using System.Globalization; using System.GCs; using System.Collections; using System.Runtime.CompilerServices; using Microsoft.Singularity; using Microsoft.Singularity.Channels; using Microsoft.Singularity.Hal; using Microsoft.Singularity.Scheduling; using Microsoft.Singularity.Security; using Microsoft.Singularity.V1.Threads; using Microsoft.Singularity.X86; using Microsoft.Singularity.Memory; using Microsoft.Bartok.Runtime; [CLSCompliant(false)] public enum ThreadEvent : ushort { CreateIdle = 12, Create = 13, WaitAny = 30, WaitFail = 31, SwitchTo = 3, ThreadPackageInit = 10 } //| [CCtorIsRunDuringStartup] [CLSCompliant(false)] [RequiredByBartok] public class Thread { // GC fields. [RequiredByBartok] // Thread-specific alloc heap internal SegregatedFreeList segregatedFreeList; [RequiredByBartok] // Thread-specific bump allocator internal BumpAllocator bumpAllocator; // MultiUseWord (object header) fields. internal UIntPtr externalMultiUseObjAllocListHead; internal UIntPtr externalMultiUseObjAllocListTail; // Scheduling fields. internal int processThreadIndex; internal int threadIndex; private ThreadStart threadStart; private ThreadState threadState; // acquire Process.processLock when changing unstarted->running private AutoResetEvent autoEvent; private bool gcEventBlocked; // Are we blocked on the gcEvent? private bool gcEventSignaled; // Has the gcEvent been signaled private ManualResetEvent joinEvent; internal Thread blockingCctorThread; internal ThreadLocalServiceRequest localServiceRequest; // Scheduler dispatching fields. [AccessedByRuntime("referenced from c++")] internal bool blocked; // Thread blocked on something. [AccessedByRuntime("referenced from c++")] private ThreadEntry[] blockedOn; // WaitHandles blocked on (if any). [AccessedByRuntime("referenced from c++")] private int blockedOnCount; // Number of valid WaitHandles. [AccessedByRuntime("referenced from c++")] private SchedulerTime blockedUntil; // Timeout of Wait. public SchedulerTime BlockedUntil { [NoHeapAllocation] get { return blockedUntil; } } private volatile int unblockedBy; // WaitHandle signaled in unblock. internal int freezeCount; // >0 prevents thread from being scheduled. [AccessedByRuntime("referenced from c++")] public ThreadEntry schedulerEntry; // Entry for some scheduler queue. public Processor ActiveProcessor; // Processor running this thread (null if not running). public Processor AffinityProcessor; // Soft affinity. public Processor LockedProcessor; // non-null if thread dedicated. // Singularity specific fields [AccessedByRuntime("referenced from c++")] internal ThreadContext context; [AccessedByRuntime("referenced from c++")] internal Process process; internal ThreadHandle threadHandle; internal UIntPtr threadLocalValue; // Most recently thrown exception object that the thread // did not catch at all (i.e. that propagated to the bottom // of the stack without encountering an appropriate catch clause). internal Exception lastUncaughtException; // Monitor fields. // Remove these (& Monitor) as soon as stack is out of kernel. internal Thread nextThread; // Link for linked lists of threads private Object exceptionStateInfo; // Exception info latched to the thread on a thread abort // Bartok specific fields [RequiredByBartok] internal TryAllManager tryAllManager; private static long totalArrayAllocations; private static long totalArrayBytesAllocated; private static long totalBytes; private static long totalStructBytesAllocated; internal static Thread[] threadTable; private static SpinLock threadTableLock; private static LocalDataStore localDataStore; internal static Thread initialThread; private static SpinLock threadStopLock; private static ProcessStopException processStopException; #if PAGING // For use when we temporarily switch to a different domain private ProtectionDomain tempDomain; #endif // This is used by the Bartok backend. When Bartok try to generate // callback stub for delegate, it checks to see if it is // ThreadProc, if it is not, Bartok adds leaveGCSafeState and // enterGCSafeState around the delegate call. [RequiredByBartok] private unsafe delegate uint ThreadProc(void *param); ////////////////////////////////////////////////////////////////////// // This manager is responsible for storing the global data that is // shared amongst all the thread local stores. // static private LocalDataStoreMgr m_LocalDataStoreMgr; ////////////////////////////////////////////////////////////////////// // Creates a new Thread object which will begin execution at // start.ThreadStart on a new thread when the Start method is called. // // Exceptions: ArgumentNullException if start == null. // //| internal const int maxThreads = 1024; // Must be power of 2 >= 64 private static int threadIndexGenerator; private Thread(Process process) { this.processThreadIndex = -1; this.threadIndex = -1; this.threadState = ThreadState.Unstarted; this.SetKernelMode(); this.process = process; context.threadIndex = unchecked((ushort)-1); context.processThreadIndex = unchecked((ushort)-1); context.processId = unchecked((ushort)-1); Transitions.InitializeStatusWord(ref context); // Allocate the kernel objects needed by the thread. autoEvent = new AutoResetEvent(false); joinEvent = new ManualResetEvent(false); localServiceRequest = new ThreadLocalServiceRequest(); schedulerEntry = new ThreadEntry(this); this.GetWaitEntries(1); // Cache allows wait without allocation // Try to put the thread in the thread table. bool iflag = Processor.DisableInterrupts(); try { threadTableLock.Acquire(CurrentThread); try { for (int i = 0; i < threadTable.Length; i++) { int index = (threadIndexGenerator + i) % threadTable.Length; if (threadTable[index] == null) { threadTable[index] = this; this.threadIndex = index; threadIndexGenerator = index + 1; // NB: We call this once, subsequently the GC visitor calls it. context.UpdateAfterGC(this); break; } } } finally { threadTableLock.Release(CurrentThread); } // Save the TID and PID into the thread context. context.threadIndex = unchecked((ushort)(threadIndex)); Transitions.InitializeStatusWord(ref context); if (process != null) { context.processId = unchecked((ushort)(process.ProcessId)); } } finally { Processor.RestoreInterrupts(iflag); } // Allocate the thread's stack. context.stackBegin = 0; context.stackLimit = 0; } // Constructor for processor idle threads. protected Thread(bool idle) : this(Process.idleProcess) { UIntPtr stackSegment = Stacks.GetInitialStackSegment(ref context); DebugStub.Assert(stackSegment != UIntPtr.Zero); #if PAGING context.InitializeIdle(threadIndex, stackSegment, unchecked((uint)Process.kernelProcess.Domain.AddressSpace.PdptPage.Value)); #else context.InitializeIdle(threadIndex, stackSegment, 0); #endif Monitoring.Log(Monitoring.Provider.Thread, (ushort)ThreadEvent.CreateIdle, 0, (uint)threadIndex, 0, 0, 0, 0); } // Constructor for all other threads. protected Thread(Process process, ThreadStart start) : this(process) { if (start == null) { throw new ArgumentNullException("start"); } this.threadStart = start; #if PAGING context.abiStackHead = Stacks.GetStackSegment(0, ref context); context.abiStackBegin = context.stackBegin; context.abiStackLimit = context.stackLimit; context.stackBegin = 0; context.stackLimit = 0; #endif UIntPtr stackSegment = Stacks.GetInitialStackSegment(ref context); DebugStub.Assert(stackSegment != UIntPtr.Zero); #if PAGING context.Initialize(threadIndex, stackSegment, unchecked((uint)(Process.Domain.AddressSpace.PdptPage.Value))); #else context.Initialize(threadIndex, stackSegment, 0); #endif Monitoring.Log(Monitoring.Provider.Thread, (ushort)ThreadEvent.Create, 0, (uint)threadIndex, 0, 0, 0, 0); } // To create a new idle thread. public static Thread CreateIdleThread(Processor processor) { // Allocate the thread. Thread idle = new Thread(true); if (idle.threadIndex < 0) { Tracing.Log(Tracing.Warning, "Thread table is full."); DebugStub.Break(); return null; } //MemoryBarrier();? DebugStub.WriteLine("CreateIdleThread tid={0:x3}, proc={1:x3}", __arglist(idle.threadIndex, processor.processorIndex)); idle.LockedProcessor = processor; return idle; } // Entry point for kernel to create new process threads. public static Thread CreateThread(Process process, ThreadStart start) { if (process == null) { process = CurrentProcess; } // Allocate the thread. Thread thread = new Thread(process, start); if (thread.threadIndex < 0) { Tracing.Log(Tracing.Warning, "Thread table is full."); DebugStub.Break(); return null; } ThreadHandle handle; process.AddThread(thread, out handle); //MemoryBarrier();? // Tell the scheduler to initialize the thread. Scheduler.OnThreadStateInitialize(thread, true); PerfCounters.IncrementThreadsCreated(); return thread; } ////////////////////////////////////////////////////////////////////// // Spawns off a new thread which will begin executing at the // ThreadStart delegate passed in the constructor. Once the thread is // dead, it cannot be restarted with another call to Start. // // Exceptions: ThreadStateException if the thread has already been started. // //| public void Start() { process.StartThread(ref threadState); StartRunningThread(); } // precondition: process.processLock held internal void SetMainThreadRunning() { VTable.Assert(threadState == ThreadState.Unstarted); process.StartMainThread(ref threadState); } internal void StartRunningThread() { VTable.Assert(threadState == ThreadState.Running); // Tell the GC that we have created the thread GC.NewThreadNotification(this, false); // Tell the scheduler to start the thread. bool iflag = Processor.DisableInterrupts(); try { Scheduler.DispatchLock(); try { //Kernel.Waypoint(1); Scheduler.OnThreadStart(this); } finally { Scheduler.DispatchRelease(); } } finally { //Kernel.Waypoint(114); Processor.RestoreInterrupts(iflag); } } // ThreadContext.InitializeIdle sets ThreadIdleStub as the first instruction to be // executed in a new thread context. [AccessedByRuntime("referenced from c++")] private static unsafe void ThreadIdleStub(int index) { for (;;) { // Check for a debug break. // Tell the scheduler to start the thread. bool iflag = Processor.DisableInterrupts(); try { Processor.NextSampleIsIdle(); if (DebugStub.PollForBreak()) { DebugStub.Print("Debugger breakin.\n"); DebugStub.Break(); } } finally { //Kernel.Waypoint(114); Processor.RestoreInterrupts(iflag); } Processor.HaltUntilInterrupt(); } } // ThreadContext.Initialize sets ThreadStub as the first instruction to be // executed in a new thread context. [AccessedByRuntime("referenced from c++")] private static unsafe void ThreadStub(int threadIndex) { Thread currentThread = threadTable[threadIndex]; #if PAGING // Give our Protection Domain a chance to set up // if we're first in here. Run this before anything // else! currentThread.process.Domain.InitHook(); #endif Transitions.ThreadStart(); GC.ThreadStartNotification(threadIndex); Tracing.Log(Tracing.Trace, "ThreadStub() entered"); ThreadStart startFun = currentThread.threadStart; Processor.InitFpu(); try { startFun(); } catch (ProcessStopException) { // Ok, exit thread without failure. } catch (Exception e) { // Not ok, fail. Tracing.Log(Tracing.Notice, "Thread failed with exception {0}.{1}", e.GetType().Namespace, e.GetType().Name); Tracing.Log(Tracing.Trace, "Exception message was {0}", e.Message); DebugStub.Assert(e == null, "Thread {0} failed w/ exception {1}.{2}: {3}", __arglist(threadIndex, e.GetType().Namespace, e.GetType().Name, e.Message)); } Tracing.Log(Tracing.Trace, "{0:x} ThreadStub() stopping", Kernel.AddressOf(currentThread)); currentThread.threadState = ThreadState.Stopping; currentThread.joinEvent.Set(); // The scheduler takes care of exiting MutatorState, so we // don't need the following call. // Transitions.ThreadEnd(threadIndex); bool iflag = Processor.DisableInterrupts(); try { Thread target = null; // Block the ServiceStopped method until we acquire DispatchLock // (but don't acquire DispatchLock yet, because // CurrentThreadStopped would try to double-acquire it). threadStopLock.Acquire(); // Tell service thread to call currentThread.ServiceStopped() ThreadLocalServiceRequest.CurrentThreadStopped(); Scheduler.DispatchLock(); try { threadStopLock.Release(); Kernel.Waypoint(1); // We change to the Stopped state here so that // Process.ServiceSuspend can stop waiting for us. // (We can't wait for the service thread to // set the Stopped state, because if the // service thread is running Process.ServiceSuspend // it could deadlock.) currentThread.threadState = ThreadState.Stopped; target = Scheduler.OnThreadStop(currentThread); Scheduler.SelectingThread(target); } catch(Exception e) { Scheduler.DispatchRelease(); throw e; } if (target == null) { target = Processor.CurrentProcessor.IdleThread; } target.SwitchTo(); } finally { //Kernel.Waypoint(114); Processor.RestoreInterrupts(iflag); } DebugStub.Break(); } // ServiceStopped is called by the service thread when the thread is stopped. internal void ServiceStopped() { // Make sure ThreadStub has gotten a chance to exit before // we deallocate its stack: bool iflag = Processor.DisableInterrupts(); try { threadStopLock.Acquire(); Scheduler.DispatchLock(); Scheduler.DispatchRelease(); threadStopLock.Release(); } finally { Processor.RestoreInterrupts(iflag); } VTable.Assert(threadState == ThreadState.Stopped); VTable.Assert(threadIndex > 0); VTable.Deny(Transitions.InMutatorState(threadIndex)); GC.DeadThreadNotification(this); #if !PAGING while (context.stackBegin != 0) { Stacks.ReturnStackSegment(ref context); } #else int count = 0; while (context.stackBegin != 0) { // HACK: if the thread stops abruptly, the stack // may contain the abi segment if (context.stackBegin == context.abiStackBegin) { context.abiStackBegin = 0; context.abiStackLimit = 0; } Stacks.ReturnStackSegment(ref context); count++; } // VTable.Assert(count == 1); // due to unimplemented exception handling code, count may be >1 #endif VTable.Assert(context.stackLimit == 0); VTable.Assert(context.stackBegin == 0); #if PAGING // See HACK above for why abiStackBegin may be 0 if (context.abiStackBegin != 0) { context.stackLimit = context.abiStackLimit; context.stackBegin = context.abiStackBegin; Stacks.ReturnStackSegment(ref context); } #endif Thread currentThread = Thread.CurrentThread; iflag = Processor.DisableInterrupts(); try { threadTableLock.Acquire(currentThread); try { threadTable[threadIndex] = null; Transitions.DeadThreadNotification(threadIndex); } finally { threadTableLock.Release(currentThread); } } finally { Processor.RestoreInterrupts(iflag); } if (process != null) { process.ServiceOnThreadStop(this); } } ////////////////////////////////////////////////////////////////////// // Returns true if the thread has been started and is not dead. // //| public bool IsAlive { get { return (threadState == ThreadState.Running); } } #if false // Returns a TimeSpan target for timeout. static internal SchedulerTime GetTimeoutTarget(TimeSpan delay) { if (delay == TimeSpan.Infinite) { #if DEBUG_SLEEP DebugStub.Print(" GetTimeTarget(Infinite) = {0}\n", __arglist(SchedulerTime.MaxValue.Ticks)); #endif return SchedulerTime.MaxValue; } else if (delay < TimeSpan.Zero) { throw new ArgumentOutOfRangeException("delay", "ArgumentOutOfRange_AddTimeout"); } SchedulerTime now = SchedulerTime.Now; #if DEBUG_SLEEP DebugStub.Print(" GetTimeTarget({0}) = {1:d12} + {2:d12} => {3:d12}\n", __arglist( delay.ToString(), now.Ticks, delay.Ticks, (now + delay).Ticks)); #endif return now + delay; } #endif [Inline] static internal bool TimeoutTargetPassed(SchedulerTime target) { return SchedulerTime.Now >= target; } ////////////////////////////////////////////////////////////////////// // Waits for the thread to die. // // Exceptions: ThreadStateException if the thread has not been started yet. // //| public void Join() { Join(SchedulerTime.MaxValue); } ////////////////////////////////////////////////////////////////////// // Waits for the thread to die or for timeout milliseconds to elapse. // Returns true if the thread died, or false if the wait timed out. // // Exceptions: ArgumentException if timeout < 0. // ThreadStateException if the thread has not been started. // //| public bool Join(TimeSpan timeout) { if (threadState == ThreadState.Unstarted) { throw new ThreadStateException(); } else if (threadState == ThreadState.Stopped) { return true; } return joinEvent.WaitOne(timeout); } public bool Join(SchedulerTime timeout) { if (threadState == ThreadState.Unstarted) { throw new ThreadStateException(); } else if (threadState == ThreadState.Stopped) { return true; } return joinEvent.WaitOne(timeout); } ////////////////////////////////////////////////////////////////////// // Suspends the current thread for timeout milliseconds. If timeout // == 0, forces the thread to give up the remainder of its timeslice. // // Exceptions: ArgumentException if timeout < 0. // public static void Sleep(int milliseconds) { Sleep(TimeSpan.FromMilliseconds(milliseconds)); } //| public static void Sleep(SchedulerTime stop) { Tracing.Log(Tracing.Audit, "Sleep until stop"); Thread.CurrentThread.WaitAny(null, 0, stop); Tracing.Log(Tracing.Audit, "Sleep until stop finished"); } //| public static void Sleep(TimeSpan timeout) { Tracing.Log(Tracing.Audit, "Sleep until time"); SchedulerTime stop = SchedulerTime.Now + timeout; Thread.CurrentThread.WaitAny(null, 0, stop); Tracing.Log(Tracing.Audit, "Sleep until time finished"); } ////////////////////////////////////////////////////////////////////// // Wait for a length of time proportional to 'iterations'. Each // iteration is should only take a few machine instructions. Calling // this method is preferable to coding an explicit busy loop because the // hardware can be informed that it is busy waiting. // //| [NoHeapAllocation] public static void SpinWait(int iterations) { for (int i = iterations; i > 0; i--) { // Ensure that the optimizer doesn't remove this NativeNoOp(); } } [Intrinsic] [NoHeapAllocation] public static extern void NativeNoOp(); internal static int GetCurrentProcessIndex() { return Thread.CurrentProcess.ProcessId; } internal bool WaitForMonitor(SchedulerTime stop) { return autoEvent.WaitOne(stop); } internal bool WaitForEvent(SchedulerTime stop) { return autoEvent.WaitOne(stop); } internal bool WaitForEvent(TimeSpan timeout) { return autoEvent.WaitOne(timeout); } internal static void WaitForGCEvent(int currentThreadIndex) { Tracing.Log(Tracing.Audit, "WaitForGCEvent({0})", (UIntPtr) currentThreadIndex); VTable.Deny(Scheduler.IsIdleThread(currentThreadIndex)); bool iflag = Processor.DisableInterrupts(); try { bool didLock = Scheduler.EnsureDispatchLock(currentThreadIndex); Thread target = null; Thread currentThread = threadTable[currentThreadIndex]; try { if (currentThread.gcEventSignaled) { // Reset the flag, fall out and keep running. currentThread.gcEventSignaled = false; if (didLock) { Scheduler.DispatchRelease(); } return; } currentThread.gcEventBlocked = true; target = Scheduler.OnThreadBlocked(currentThread, SchedulerTime.MaxValue); if (target == null) { target = Processor.CurrentProcessor.IdleThread; } else { // This should not be called passing the idle thread Scheduler.SelectingThread(target); } } catch { if (didLock) { Scheduler.DispatchRelease(); } throw; } Processor.CurrentProcessor.NumContextSwitches++; // This has the side effect of releasing the dispatcher lock. Processor.SwitchToThreadContextNoGC(ref target.context); } finally { Processor.RestoreInterrupts(iflag); } } internal void SignalEvent() { autoEvent.Set(); } internal void SignalMonitor() { autoEvent.Set(); } [Inline] internal static void SignalGCEvent(int threadIndex) { SignalGCEvent(Thread.GetCurrentThreadIndex(), threadIndex); } internal static void SignalGCEvent(int currentThreadIndex, int threadIndex) { Tracing.Log(Tracing.Audit, "SignalGCEvent({0})", (UIntPtr) threadIndex); VTable.Deny(Scheduler.IsIdleThread(threadIndex)); bool iflag = Processor.DisableInterrupts(); try { bool didLock = Scheduler.EnsureDispatchLock(currentThreadIndex); try { Thread targetThread = threadTable[threadIndex]; if (targetThread == null) { // There is nothing to signal, so just keep going. } else if (targetThread.gcEventBlocked) { targetThread.gcEventBlocked = false; Scheduler.OnThreadUnblocked(targetThread); } else { // Nobody was waiting for the event. targetThread.gcEventSignaled = true; } } finally { if (didLock) { Scheduler.DispatchRelease(); } } } finally { Processor.RestoreInterrupts(iflag); } } //| public static extern Thread CurrentThread { [NoStackLinkCheck] [NoHeapAllocation] [Intrinsic] get; } public static Process CurrentProcess { [NoStackLinkCheck] [NoHeapAllocation] get { return Processor.GetCurrentThread().process; } } public Process Process { [NoHeapAllocation] get { return process; } } public ThreadHandle Handle { [NoHeapAllocation] get { return threadHandle; } } [NoStackLinkCheck] [RequiredByBartok] [NoHeapAllocation] private static Thread GetCurrentThreadNative() { return Processor.GetCurrentThread(); } [NoStackLinkCheck] [RequiredByBartok] [NoHeapAllocation] public static int GetCurrentThreadIndex() { return Processor.GetCurrentThread().threadIndex; } [NoHeapAllocation] public static UIntPtr GetThreadLocalValue() { return Processor.GetCurrentThread().threadLocalValue; } [NoHeapAllocation] public static void SetThreadLocalValue(UIntPtr value) { Processor.GetCurrentThread().threadLocalValue = value; } ////////////////////////////////////////////////////////////////////// // Return the thread state as a consistent set of bits. This is more // general then IsAlive or IsBackground. // //| public ThreadState ThreadState { [NoHeapAllocation] get { return threadState; } } [NoHeapAllocation] public int GetThreadId() { return threadIndex; } // Return true if the thread is in kernel mode, false if the // thread is in process mode. // Note that by the time this method returns, the thread might // have already switched to a different mode; in other words, // don't rely on this result of this method being up-to-date unless // the thread is suspended or blocked. [NoHeapAllocation] public unsafe bool IsInKernelMode() { return context.IsInKernelMode(); } [NoHeapAllocation] internal unsafe void SetKernelMode() { context.SetKernelMode(); } [NoHeapAllocation] internal unsafe void SetProcessMode() { context.SetProcessMode(); } // Briefly suspend a thread in order to modify its state. // When Freeze() returns, the thread is guaranteed to: // - be unscheduled (not running on any processor) // - not to be inside a DisableInterrupts block // (unless that block voluntarily context switches) // - not to hold the dispatch lock (because // CurrentThread holds the dispatch lock) // These guarantees hold only until the dispatch lock // is released. // // No other guarantees are made about the thread state; in particular, // the thread could be in the middle of a kernel operation. (This // distinguishes Freeze from Suspend.) // // A thread should not try to freeze itself // (this would deadlock, as can many other uses of Freeze). // // precondition: interrupts enabled // precondition: dispatch lock not acquired // (so that other processor schedulers can run) // postcondition: interrupts disabled // postcondition: dispatch lock acquired // postcondition: ActiveProcessor == null // postcondition: freezeCount > 0 // // Example: // t.Freeze(); // ...(do some stuff, without releasing dispatch lock)... // Scheduler.DispatchRelease(); // Processor.RestoreInterrupts(true); internal void Freeze() { Debug.Assert(this != CurrentThread); bool iflag = Processor.DisableInterrupts(); Debug.Assert(iflag); try { Scheduler.DispatchLock(); try { switch (threadState) { case ThreadState.Unstarted: case ThreadState.Stopped: case ThreadState.Suspended: break; case ThreadState.Running: case ThreadState.Stopping: if (ActiveProcessor == null) { break; } Scheduler.OnThreadFreezeIncrement(this); WaitUntilInactive(); // (Try to be courteous to the scheduler -- don't // ask it to resume a stopped thread.) if (threadState != ThreadState.Stopped) { Scheduler.OnThreadFreezeDecrement(this); } break; default: Debug.Assert(false, "unreachable default case"); return; } } catch(Exception e) { Scheduler.DispatchRelease(); throw e; } } catch(Exception e) { Processor.RestoreInterrupts(iflag); throw e; } Debug.Assert(ActiveProcessor == null); Scheduler.AssertDispatchLockHeld(); } internal void Unfreeze() { Scheduler.DispatchRelease(); Processor.RestoreInterrupts(true); } // precondition: interrupts disabled with iflag==true // precondition: dispatch lock acquired // invariant: freezeCount > 0 // postcondition: interrupts disabled with iflag==true // postcondition: dispatch lock acquired // postcondition: ActiveProcessor == null private void WaitUntilInactive() { Scheduler.AssertDispatchLockHeld(); Debug.Assert(this != CurrentThread); Debug.Assert(freezeCount > 0); if (ActiveProcessor != null) { // TODO: send interrupt to ActiveProcessor // spin for exponentially backed off delay int count = 31; while (ActiveProcessor != null) { Scheduler.DispatchRelease(); Processor.RestoreInterrupts(true); Thread.SpinWait(count); count = (count == 0x7ffffffu) ? count : count + count + 1; bool iflag = Processor.DisableInterrupts(); Debug.Assert(iflag); Scheduler.DispatchLock(); } } } // Do not call Thread.Suspend unless you are suspending all the // threads in a process. This should only be called from the // kernel service thread. // // Return value: // true means the thread is now suspended, unstarted, or stopped. // false means the thread must be allowed to run a little longer, // so try calling Suspend later. // // precondition: interrupts enabled // precondition: dispatch lock not acquired // postcondition: interrupts enabled // postcondition: dispatch lock not acquired internal bool Suspend(bool aboutToStop) { /* case (kernel, unblocked, running): return false; case (kernel, unblocked, stopping): return false; case (process, unblocked, running): DoSuspend(); return true; case (kernel, blocked, running): DoSuspend(); return true; case (kernel, unblocked, unstarted): return true; case (_, _, suspended): return true; case (kernel, unblocked, stopped): return true; case _: error // Also: in the (kernel, blocked, running) case, if // aboutToStop==true, call WaitDone(). */ Freeze(); try { if (threadState == ThreadState.Suspended) { return true; } else if (IsInKernelMode()) { // in kernel mode, not suspended if (blocked) { if (threadState == ThreadState.Running) { DoSuspend(); if (aboutToStop) { WaitDone(null); } return true; } else { Debug.Assert(false, "unexpected thread state"); return false; } } else { // in kernel mode, unblocked, not suspended switch (threadState) { case ThreadState.Running: case ThreadState.Stopping: // Let the thread run a while longer, but // let it know that eventually, it should // suspend: // MULTIPROCESSOR NOTE: halforgc.asm reads // suspendAlert with no explicit // synchronization. We don't need the // read to be consistent with the // following write immediately, but it // must become consistent eventually. context.suspendAlert = true; return false; case ThreadState.Unstarted: case ThreadState.Stopped: // Note: now that process.state==Suspending[Recursive], a barrier // prevents starting threads, an Unstarted thread stays // Unstarted. return true; case ThreadState.Suspended: Debug.Assert(false, "unexpected thread state"); return false; default: Debug.Assert(false, "unreachable default case"); return false; } } } else { // in process mode, not suspended if (!blocked && threadState == ThreadState.Running) { DoSuspend(); return true; } else { Debug.Assert(false, "unexpected thread state"); return false; } } } finally { Scheduler.DispatchRelease(); Processor.RestoreInterrupts(true); } } private void DoSuspend() { threadState = ThreadState.Suspended; Scheduler.OnThreadFreezeIncrement(this); } // Do not call Thread.Resume unless you are resuming all the // threads in a process. This should only be called from the // kernel service thread. internal void Resume() { bool iflag = Processor.DisableInterrupts(); try { Scheduler.DispatchLock(); try { if (threadState == ThreadState.Suspended) { threadState = ThreadState.Running; Scheduler.OnThreadFreezeDecrement(this); } } finally { Scheduler.DispatchRelease(); } } finally { Processor.RestoreInterrupts(iflag); } } // Do not call Thread.Stop unless you are stopping all the // threads in a process. This should only be called from the // kernel service thread. // // precondition: interrupts enabled // precondition: dispatch lock not acquired // postcondition: interrupts enabled // postcondition: dispatch lock not acquired internal void Stop() { /* case (kernel, unblocked, unstarted): return; case (kernel, unblocked, stopped): return; case (kernel, _, suspended): if (blocked) WaitDone(null); t.throw ProcessStopException threadState = ThreadState.Running; Scheduler.OnThreadFreezeDecrement(this); return; case (process, unblocked, suspended): t.(pop frames and throw ProcessStopException) threadState = ThreadState.Running; Scheduler.OnThreadFreezeDecrement(this); return; case _: error */ Freeze(); try { bool kernelMode = IsInKernelMode(); if (threadState == ThreadState.Suspended) { if (kernelMode) { if (blocked) { WaitDone(null); } // context.eip = __throwBeyondMarker; // context.eax = context.stackMarkers; // kernel->kernel marker; // context.ecx = processStopException; setStopContext(this, processStopException); } else if (!kernelMode && !blocked) { // context.eip = __throwBeyondMarker; // context.eax = context.stackMarkers; // kernel->process marker; // context.ecx = processStopException; setStopContext(this, processStopException); } else { Debug.Assert(false, "unexpected thread state"); } Debug.Assert(!blocked); threadState = ThreadState.Running; Scheduler.OnThreadFreezeDecrement(this); } else if ( kernelMode && !blocked && ( threadState == ThreadState.Stopped || threadState == ThreadState.Unstarted)) { // nothing needs to happen here. } else { Debug.Assert(false, "unexpected thread state"); } } finally { Scheduler.DispatchRelease(); Processor.RestoreInterrupts(true); } } [MethodImpl(MethodImplOptions.InternalCall)] [NoHeapAllocation] [StackBound(4)] private static extern void setStopContext(Thread t, Exception exn); ////////////////////////////////////////////////////////////////////// // Allocates an un-named data slot. The slot is allocated on ALL the // threads. //| public static LocalDataStoreSlot AllocateDataSlot() { return m_LocalDataStoreMgr.AllocateDataSlot(); } ////////////////////////////////////////////////////////////////////// // Allocates a named data slot. The slot is allocated on ALL the // threads. Named data slots are "public" and can be manipulated by // anyone. //| public static LocalDataStoreSlot AllocateNamedDataSlot(String name) { return m_LocalDataStoreMgr.AllocateNamedDataSlot(name); } ////////////////////////////////////////////////////////////////////// // Looks up a named data slot. If the name has not been used, a new // slot is allocated. Named data slots are "public" and can be // manipulated by anyone. //| public static LocalDataStoreSlot GetNamedDataSlot(String name) { return m_LocalDataStoreMgr.GetNamedDataSlot(name); } ////////////////////////////////////////////////////////////////////// // Frees a named data slot. The slot is allocated on ALL the // threads. Named data slots are "public" and can be manipulated by // anyone. //| public static void FreeNamedDataSlot(String name) { m_LocalDataStoreMgr.FreeNamedDataSlot(name); } ////////////////////////////////////////////////////////////////////// // Retrieves the value from the specified slot on the current thread. //| public static Object GetData(LocalDataStoreSlot slot) { m_LocalDataStoreMgr.ValidateSlot(slot); if (localDataStore != null) { return localDataStore.GetData(slot); } return null; } ////////////////////////////////////////////////////////////////////// // Sets the data in the specified slot on the currently running thread. //| public static void SetData(LocalDataStoreSlot slot, Object data) { // Create new DLS if one hasn't been created for this thread. if (localDataStore == null) { localDataStore = m_LocalDataStoreMgr.CreateLocalDataStore(); } localDataStore.SetData(slot, data); } /*=============================================================*/ internal Object ExceptionState { [NoHeapAllocation] get { return exceptionStateInfo;} [NoHeapAllocation] set { exceptionStateInfo = value;} } // // This is just designed to prevent compiler warnings. // This field is used from native, but we need to prevent the compiler warnings. // #if _DEBUG private void DontTouchThis() { threadStart = null; m_Priority = 0; } #endif ////////////////////////////////////////////////////////////////////// // Volatile Read & Write and MemoryBarrier methods. // Provides the ability to read and write values ensuring that the values // are read/written each time they are accessed. // //| [Intrinsic] [NoHeapAllocation] public static extern byte VolatileRead(ref byte address); //| [Intrinsic] [NoHeapAllocation] public static extern short VolatileRead(ref short address); //| [Intrinsic] [NoHeapAllocation] public static extern int VolatileRead(ref int address); //| [Intrinsic] [NoHeapAllocation] public static extern long VolatileRead(ref long address); //| [CLSCompliant(false)] [Intrinsic] [NoHeapAllocation] public static extern sbyte VolatileRead(ref sbyte address); //| [CLSCompliant(false)] [Intrinsic] [NoHeapAllocation] public static extern ushort VolatileRead(ref ushort address); //| [CLSCompliant(false)] [Intrinsic] [NoHeapAllocation] public static extern uint VolatileRead(ref uint address); //| [Intrinsic] [NoHeapAllocation] public static extern IntPtr VolatileRead(ref IntPtr address); //| [CLSCompliant(false)] [Intrinsic] [NoHeapAllocation] public static extern UIntPtr VolatileRead(ref UIntPtr address); //| [CLSCompliant(false)] [Intrinsic] [NoHeapAllocation] public static extern ulong VolatileRead(ref ulong address); //| [Intrinsic] [NoHeapAllocation] public static extern float VolatileRead(ref float address); //| [Intrinsic] [NoHeapAllocation] public static extern double VolatileRead(ref double address); //| [Intrinsic] [NoHeapAllocation] public static extern Object VolatileRead(ref Object address); //| [Intrinsic] [NoHeapAllocation] public static extern void VolatileWrite(ref byte address, byte value); //| [Intrinsic] [NoHeapAllocation] public static extern void VolatileWrite(ref short address, short value); //| [Intrinsic] [NoHeapAllocation] public static extern void VolatileWrite(ref int address, int value); //| [Intrinsic] [NoHeapAllocation] public static extern void VolatileWrite(ref long address, long value); //| [CLSCompliant(false)] [Intrinsic] [NoHeapAllocation] public static extern void VolatileWrite(ref sbyte address, sbyte value); //| [CLSCompliant(false)] [Intrinsic] [NoHeapAllocation] public static extern void VolatileWrite(ref ushort address, ushort value); //| [CLSCompliant(false)] [Intrinsic] [NoHeapAllocation] public static extern void VolatileWrite(ref uint address, uint value); //| [Intrinsic] [NoHeapAllocation] public static extern void VolatileWrite(ref IntPtr address, IntPtr value); //| [CLSCompliant(false)] [Intrinsic] [NoHeapAllocation] public static extern void VolatileWrite(ref UIntPtr address, UIntPtr value); //| [CLSCompliant(false)] [Intrinsic] [NoHeapAllocation] public static extern void VolatileWrite(ref ulong address, ulong value); //| [Intrinsic] [NoHeapAllocation] public static extern void VolatileWrite(ref float address, float value); //| [Intrinsic] [NoHeapAllocation] public static extern void VolatileWrite(ref double address, double value); //| [Intrinsic] [NoHeapAllocation] public static extern void VolatileWrite(ref Object address, Object value); //| [Intrinsic] [NoHeapAllocation] public static extern void MemoryBarrier(); [NoHeapAllocation] internal static unsafe void DisplayAbbrev(ref ThreadContext context, String s) { fixed (ThreadContext * pContext = &context) { DebugStub.Print("{0}: ctx={1:x8} esp={2:x8} ebp={3:x8} eip={4:x8} " + "thr={5:x8} efl={6:x8} p={7:x8} n={8:x8}\n", __arglist( s, (UIntPtr)pContext, context.esp, context.ebp, context.eip, Kernel.AddressOf(context.thread), context.efl, (UIntPtr)context.prev, (UIntPtr)context.next)); } } [NoHeapAllocation] internal static unsafe void Display(ref ThreadContext context, String s) { fixed (ThreadContext * pContext = &context) { DebugStub.Print("{0}: ctx={1:x8} num={2:x2}\n", __arglist( s, (UIntPtr)pContext, context.num)); } DebugStub.Print(" thr={0:x8} prv={1:x8} nxt={2:x8}\n", __arglist( (UIntPtr)Kernel.AddressOf(context.thread), (UIntPtr)context.prev, (UIntPtr)context.next)); DebugStub.Print(" eax={0:x8} ebx={1:x8} ecx={2:x8} edx={3:x8}\n", __arglist( context.eax, context.ebx, context.ecx, context.edx)); DebugStub.Print(" esp={0:x8} ebp={1:x8} esi={2:x8} edi={3:x8}\n", __arglist( context.esp, context.ebp, context.esi, context.edi)); DebugStub.Print(" eip={0:x8} efl={1:x8} err={2:x8} cr2={3:x8}\n", __arglist( context.eip, context.efl, context.err, context.cr2)); } ///////////////////////////////////////////////// Blocking operations. // internal int WaitAny(WaitHandle[] waitHandles, int waitHandlesCount, TimeSpan timeout) { SchedulerTime stop = SchedulerTime.Now + timeout; return WaitAny(waitHandles, waitHandlesCount, stop); } internal int WaitAny(WaitHandle[] waitHandles, int waitHandlesCount, SchedulerTime stop) { VTable.Assert(threadState == ThreadState.Running); VTable.Assert(!blocked); Kernel.Waypoint(100); ThreadEntry[] entries = GetWaitEntries(waitHandlesCount); bool iflag = Processor.DisableInterrupts(); try { Thread target = null; Scheduler.DispatchLock(); try { Kernel.Waypoint(101); // Enqueue on all of the non-signaled handles. for (int marked = 0; marked < waitHandlesCount; marked++) { if (waitHandles[marked].AcquireOrEnqueue(entries[marked])) { // If one of the handles was signaled, then abort waits... #if DEBUG_DISPATCH DebugStub.Print("Thread {0:x8} WaitAny 004 [Presignal] "+ "on {1:x8}\n", __arglist( Kernel.AddressOf(this), Kernel.AddressOf(waitHandles[marked]))); #endif // DEBUG_DISPATCH for (int released = 0; released < marked; released++) { entries[released].RemoveFromQueue(); } Scheduler.DispatchRelease(); return marked; } } blocked = true; blockedOn = entries; blockedOnCount = waitHandlesCount; blockedUntil = stop; Monitoring.Log(Monitoring.Provider.Thread, (ushort)ThreadEvent.WaitAny, 0, (uint)stop.Ticks, (uint)(stop.Ticks >> 32), (uint)this.threadIndex, 0, 0); unblockedBy = WaitHandle.WaitTimeout; #if DEBUG_DISPATCH DebugStub.Print("Thread {0:x8} WaitAny 007 [Blocking] on\n", __arglist(Kernel.AddressOf(this))); for (int i = 0; i < waitHandlesCount; i++) { DebugStub.Print(" {0:d3}: {1:x8}\n", __arglist(i, Kernel.AddressOf(waitHandles[i]))); } #endif // DEBUG_DISPATCH target = Scheduler.OnThreadBlocked(this, stop); Scheduler.SelectingThread(target); } catch(Exception e) { Scheduler.DispatchRelease(); throw e; } if (target == null) { #if DEBUG_DISPATCH DebugStub.Print("Thread {0:x8} WaitAny 013 [Idle Thread]\n", __arglist(Kernel.AddressOf(this))); #endif // DEBUG_DISPATCH target = Processor.CurrentProcessor.IdleThread; VTable.Deny(Transitions.UnderGCControl(target.threadIndex)); } #if DEBUG_DISPATCH DebugStub.Print("Thread {0:x8} WaitAny 014 [SwitchTo] thread {1:x8}\n", __arglist( Kernel.AddressOf(this), Kernel.AddressOf(target))); #endif // DEBUG_DISPATCH target.SwitchTo(); // Execution will return here only after either the timeout // period has been passed or one of the handles has been // signalled. If we were unblocked, then WaitDone will have // been called and unblockedBy will not be WaitHandle.WaitTimeout. // If we timed out, then WaitFail will have been called. } finally { //Kernel.Waypoint(114); Processor.RestoreInterrupts(iflag); #if DEBUG_DISPATCH DebugStub.Print("Thread {0:x8} WaitAny 019\n", __arglist(Kernel.AddressOf(this))); #endif // DEBUG_DISPATCH } return unblockedBy; } // This method should only be called by low-level synchronization // primitives derived from WaitHandle. If you need to cause a // thread to sleep or wake up, you should use those primitives // instead. [NoHeapAllocation] internal void WaitDone(ThreadEntry entry) { Scheduler.AssertDispatchLockHeld(); // DebugStub.Break(); // for (int i = 0; i < blockedOnCount; i++) { if (blockedOn[i] == entry) { unblockedBy = i; } else { blockedOn[i].RemoveFromQueue(); } } // Tell the scheduler this thread is no longer blocked. Scheduler.OnThreadUnblocked(this); // Clear out the blocked information, after we know we won't want it. blocked = false; blockedOn = null; //No more beneficiaries of inherited time. blockedOnCount = 0; } // This method is called when the scheduler has timed-out the wait. [NoHeapAllocation] internal void WaitFail() { Scheduler.AssertDispatchLockHeld(); // DebugStub.Break(); // #if DEBUG_DISPATCH DebugStub.Print("Thread {0:x8} [WaitFail]\n", __arglist(Kernel.AddressOf(this))); #endif // DEBUG_DISPATCH VTable.Assert(unblockedBy == WaitHandle.WaitTimeout); for (int i = 0; i < blockedOnCount; i++) { blockedOn[i].RemoveFromQueue(); } Monitoring.Log(Monitoring.Provider.Thread, (ushort)ThreadEvent.WaitFail, 0, (uint)blockedUntil.Ticks, (uint)(blockedUntil.Ticks >> 32), (uint)this.threadIndex, 0, 0); // Clear out the blocked information, after we know we won't want it. blocked = false; blockedOn = null; //No more beneficiaries of inherited time. blockedOnCount = 0; } ///////////////////////////////////////////////// Context Switch Code. // // This method should only be called by the scheduler and by Yield(). // precondition: Scheduler.dispatchLock held // postcondition: Scheduler.dispatchLock released [NoHeapAllocation] private void SwitchTo() { Thread currentThread = CurrentThread; #if DONT Tracing.Log(Tracing.Audit, "Schedule(old={0:x}, new={1:x})", Kernel.AddressOf(currentThread), Kernel.AddressOf(this)); #endif #if DEBUG_SWITCH Display(ref this.context, "New Context"); #endif Scheduler.AssertDispatchLockHeld(); Processor.CurrentProcessor.NumContextSwitches++; //Tracing.LogContextSwitch(CurrentThread.GetThreadId(), this.GetThreadId()); #if DEBUG_SWITCH Thread.DisplayAbbrev(ref currentThread.context, "swi bef"); #endif Kernel.Waypoint(2); Monitoring.Log(Monitoring.Provider.Thread, (ushort)ThreadEvent.SwitchTo, 0, (uint)this.context.threadIndex, 0, 0, 0, 0); Processor.SwitchToThreadContext(ref currentThread.context, ref this.context); // Kernel.Waypoint(7); #if DEBUG_SWITCH Thread.DisplayAbbrev(ref Thread.CurrentThread.context, "swi aft"); #endif #if DONT Display(ref currentThread.context, "Old Context"); Display(ref threadTable[0].context, "[0] Context"); Display(ref threadTable[1].context, "[1] Context"); #endif } // ////////////////////////////////////////////////////////////////////// [NoHeapAllocation] public static void Yield() { bool iflag = Processor.DisableInterrupts(); try { Thread target = CurrentThread; Scheduler.DispatchLock(); try { Kernel.Waypoint(1); target = Scheduler.OnThreadYield(CurrentThread); #if DEBUG_DISPATCH DebugStub.Print("Yield.Selecting\n"); #endif // DEBUG_DISPATCH Scheduler.SelectingThread(target); } catch(Exception e) { Scheduler.DispatchRelease(); throw e; } if (target == null) { target = CurrentThread; } target.SwitchTo(); } finally { //Kernel.Waypoint(114); Processor.RestoreInterrupts(iflag); } } [NoHeapAllocation] public bool IsWaiting() { return blocked; } [NoHeapAllocation] public bool IsStopped() { return (threadState == ThreadState.Stopped); } [NoHeapAllocation] public bool IsStopping() { return (threadState == ThreadState.Stopping || threadState == ThreadState.Stopped); } [PreInitRefCounts] static unsafe Thread() { threadIndexGenerator = 1; DebugStub.Print("Thread()"); // Enable Thread.CurrentThread as soon as we can! initialThread = Magic.toThread(GCs.BootstrapMemory.Allocate(typeof(Thread))); initialThread.threadState = ThreadState.Running; initialThread.SetKernelMode(); initialThread.threadIndex = 0; // Allocate tables for thread management threadTable = (Thread[]) GCs.BootstrapMemory.Allocate(typeof(Thread[]), maxThreads); // Initialize the thread and event tables threadTable[initialThread.threadIndex] = initialThread; initialThread.context.threadIndex = unchecked((ushort)(initialThread.threadIndex)); Transitions.InitializeStatusWord(ref initialThread.context); initialThread.context.processId = unchecked((ushort)(1)); // Prevent stack linking. initialThread.context.stackBegin = 0; initialThread.context.stackLimit = 0; initialThread.context.UpdateAfterGC(initialThread); Processor.SetCurrentThreadContext(ref initialThread.context); #if DEBUG_THREAD_CONTEXT_ALIGNMENT Tracing.Log(Tracing.Debug, "Thread.alignment = {0}", (((RuntimeType)typeof(Thread)).classVtable).baseAlignment); Tracing.Log(Tracing.Debug, "ThreadContext.alignment = {0}", (((RuntimeType)typeof(ThreadContext)).classVtable).baseAlignment); Tracing.Log(Tracing.Debug, "MmxContext.alignment = {0}", (((RuntimeType)typeof(MmxContext)).classVtable).baseAlignment); Tracing.Log(Tracing.Debug, "&initialThread = {0:x8}", Kernel.AddressOf(initialThread)); fixed (void *v = &initialThread.context) { Tracing.Log(Tracing.Debug, "&initialThread.context = {0:x8}", (UIntPtr)v); } fixed (void *v = &initialThread.context.mmx) { Tracing.Log(Tracing.Debug, "&initialThread.context.mmx = {0:x8}", (UIntPtr)v); } fixed (void *v = &initialThread.context.mmx.st0) { Tracing.Log(Tracing.Debug, "&initialThread.context.mmx.st0 = {0:x8}", (UIntPtr)v); } #endif VTable.Assert((int)(((RuntimeType)typeof(Thread)).classVtable).baseAlignment == 16); VTable.Assert((int)(((RuntimeType)typeof(ThreadContext)).classVtable).baseAlignment == 16); VTable.Assert((int)(((RuntimeType)typeof(MmxContext)).classVtable).baseAlignment == 16); Tracing.Log(Tracing.Debug, "InitialThread={0:x8}", Kernel.AddressOf(initialThread)); Monitoring.Log(Monitoring.Provider.Thread, (ushort)ThreadEvent.ThreadPackageInit); initialThread.bumpAllocator.Dump(); Tracing.Log(Tracing.Debug, "Class constructor Thread() exiting\n"); } internal static unsafe void FinishInitializeThread() { // Set the fields of initialThread int stackVariable; initialThread.context.stackBegin = (new UIntPtr(&stackVariable) + 0xfff) & new UIntPtr(~0xfffU); initialThread.context.stackLimit = 0; initialThread.autoEvent = new AutoResetEvent(false); initialThread.joinEvent = new ManualResetEvent(false); initialThread.schedulerEntry = new ThreadEntry(initialThread); initialThread.GetWaitEntries(1); // Cache allows wait without alloc Transitions.RuntimeInitialized(); Transitions.ThreadStart(); // Instantiate the static variable that needs to be initialized m_LocalDataStoreMgr = new LocalDataStoreMgr(); processStopException = new ProcessStopException(); // Tell the scheduler to initialize the thread. Scheduler.OnThreadStateInitialize(initialThread, false); } /// Prepares a new Thread to take on role as kernel thread /// for upcoming processor. Called by Bootstrap processor. public static Thread PrepareKernelThread() { Thread kernelThread = new Thread(null); GC.NewThreadNotification(kernelThread, false); return kernelThread; } public static void BindKernelThread(Thread kernelThread, UIntPtr stackBegin, UIntPtr stackLimit) { kernelThread.context.processId = initialThread.context.processId; kernelThread.context.stackBegin = stackBegin; kernelThread.context.stackLimit = 0/* stackLimit */; kernelThread.context.UpdateAfterGC(kernelThread); Processor.SetCurrentThreadContext(ref kernelThread.context); kernelThread.threadState = ThreadState.Running; Transitions.ThreadStart(); } [NoHeapAllocation] public void DumpStackInfo() { Tracing.Log(Tracing.Debug, "<< thr={0:x8} beg={1:x8} lim={2:x8} ptr={3:x8} >>", Kernel.AddressOf(this), context.stackBegin, context.stackLimit, Processor.GetStackPointer()); } public Thread GetRunnableBeneficiary() { // this does a depth first search, not sure if that is a good idea. if (threadState == ThreadState.Running) { return this; } Thread tempThread; for (int i = 0; blockedOn != null && i < blockedOnCount; i++) { if (blockedOn[i] != null) { tempThread = blockedOn[i].GetBeneficiary(); if (tempThread != null) { tempThread = tempThread.GetRunnableBeneficiary(); } if (tempThread != null) { return tempThread; } } } return null; } #if THREAD_TIME_ACCOUNTING // timestamp of last update for ExecutionTime internal ulong LastUpdateTime { [NoHeapAllocation] get { return context.lastExecutionTimeUpdate; } [NoHeapAllocation] set { context.lastExecutionTimeUpdate = value; } } // fixme: where to init. this one ??? // FinishInitializeThread() seems to be called before // Processor.CyclesPerSecond is set up //static private ulong multiplier = Processor.CyclesPerSecond / // TimeSpan.TicksPerSecond #else protected TimeSpan executionTime; #endif public TimeSpan ExecutionTime { #if THREAD_TIME_ACCOUNTING //[NoHeapAllocation] get { ulong m = Processor.CyclesPerSecond / TimeSpan.TicksPerSecond; bool saved = Processor.DisableInterrupts(); try { if (Processor.GetCurrentThread() == this) { ulong now = Processor.CycleCount; context.executionTime += now - context.lastExecutionTimeUpdate; LastUpdateTime = now; } } finally { Processor.RestoreInterrupts(saved); } // fixme: this division is bad (slow), hot to get rid of it? return new TimeSpan((long)(context.executionTime / m)); } #else [NoHeapAllocation] get { return executionTime; } #endif } #if THREAD_TIME_ACCOUNTING // This provides access to the raw cycle counter, so access to it // should be fast, compared to ExecutionTime. This might be useful // for monitoring code which calls this often and can postprocess // these times otherwise public ulong RawExecutionTime { //[NoHeapAllocation] get { bool saved = Processor.DisableInterrupts(); try { if (Processor.GetCurrentThread() == this) { ulong now = Processor.CycleCount; context.executionTime += now - context.lastExecutionTimeUpdate; LastUpdateTime = now; } } finally { Processor.RestoreInterrupts(saved); } return context.executionTime; } } #endif internal static void VisitBootstrapData(NonNullReferenceVisitor visitor) { visitor.VisitReferenceFields(initialThread); visitor.VisitReferenceFields(threadTable); } internal static void UpdateAfterGC() { // Update all the thread pointers in the thread contexts for (int i = 0; i < threadTable.Length; i++) { Thread thread = threadTable[i]; if (thread != null) { thread.context.UpdateAfterGC(thread); } } } // Cache for ABI synchronization private WaitHandle[] syncHandles; internal WaitHandle[] GetSyncHandles(int num) { if (syncHandles == null || syncHandles.Length < num) { syncHandles = new WaitHandle[num + 8]; } return syncHandles; } // Cache for handle synchronization private ThreadEntry[] entries; internal ThreadEntry[] GetWaitEntries(int num) { if (entries == null || entries.Length < num) { num += 8; // So we don't have to do this too frequently. entries = new ThreadEntry[num]; for (int i = 0; i < num; i++) { entries[i] = new ThreadEntry(this); } } return entries; } // Caches for Select synchronization // We use stacks, because selectable abstractions might // internally implement HeadMatches using select receive // which is called from within an outer select. // NOTE however that internal selects should never block // (use timeout) private Stack selectBoolsStack; private Stack selectObjectsStack; private Stack selectSyncHandlesStack; public bool[] PopSelectBools(int size) { if (selectBoolsStack == null) { selectBoolsStack = new Stack(); } if (selectBoolsStack.Count == 0) { return new bool [size]; } bool[] selectBools = (bool[])selectBoolsStack.Pop(); if (selectBools.Length < size) { return new bool [size]; } return selectBools; } public void PushSelectBools(bool[] cache) { selectBoolsStack.Push(cache); } public ISelectable[] PopSelectObjects(int size) { if (selectObjectsStack == null) { selectObjectsStack = new Stack(); } if (selectObjectsStack.Count == 0) { return new ISelectable [size]; } ISelectable[] selectObjects = (ISelectable[])selectObjectsStack.Pop(); if (selectObjects.Length < size) { return new ISelectable [size]; } return selectObjects; } public void PushSelectObjects(ISelectable[] cache) { for (int i=0; iprocessMarkers; System.GCs.CallStack.TransitionRecord *secondMarker = context->stackMarkers; UIntPtr topMarkerPtr = (UIntPtr) topMarker; // If the top marker is in our frame, we've reached a boundary: if (esp < topMarkerPtr && topMarkerPtr <= ebp) { Thread.CurrentThread.lastUncaughtException = exn; // Is this a ProcessStopException? If not, it's a bug; log the bug. if (!(exn is ProcessStopException)) { // Log the bug, but don't do anything that could // throw another exception (e.g. memory allocation). // XXX: what if stack allocation throws an exception here? DebugStub.Print("Bug: kernel exception thrown to process (saved to Thread.LastUncaughtException)\n"); Tracing.Log(Tracing.Warning, "Bug: kernel exception thrown to process (saved to Thread.LastUncaughtException)\n"); } // atomic // { // do these together so we never enter process mode // remove top process->kernel marker from marker list // remove top kernel->process marker from marker list // } bool iflag = Processor.DisableInterrupts(); Scheduler.DispatchLock(); try { context->processMarkers = context->processMarkers->oldTransitionRecord; context->stackMarkers = context->stackMarkers->oldTransitionRecord; context->SetKernelMode(); } finally { Scheduler.DispatchRelease(); Processor.RestoreInterrupts(iflag); } //Return the kernel->process marker, in preparation for these operations: // edx := retAddr from *stackBottom from kernel->process marker // restore esp,ebp from kernel->process marker // while(top kernel->process marker not in stack segment) // pop (and free) stack segment // restore ebx,edi,esi from kernel->process marker return new UIntPtr(secondMarker); } else { return 0; } } // Discard any garbage stack segments that follow the segment // containing the marker. After this runs, the topmost stack // segment will contain the marker. [AccessedByRuntime("referenced from halasm.asm")] [NoStackLinkCheck] internal static unsafe void DiscardSkippedStackSegments(System.GCs.CallStack.TransitionRecord *marker) { ThreadContext *context = Processor.GetCurrentThreadContext(); UIntPtr markerPtr = new UIntPtr(marker); // while(top kernel->process marker not in stack segment) // pop (and free) stack segment // // HACKHACK think about what this is doing. The topmost // stack segment is the one *currently in use*. On a paging // system, freeing it unmaps the underlying physical page. // Needless to say, our ability to use esp after that is // severely compromised. // #if !PAGING while ((context->stackBegin != 0) && !(context->stackLimit <= markerPtr && markerPtr < context->stackBegin)) { Microsoft.Singularity.Memory.Stacks.ReturnStackSegment(ref *context); } #endif // Unlink marker: context->stackMarkers = marker->oldTransitionRecord; } // Most recently thrown exception object that the thread // did not catch at all (i.e. that propagated to the bottom // of the stack without encountering an appropriate catch clause). public Exception LastUncaughtException { [NoHeapAllocation] get { return lastUncaughtException; } } #if PAGING // Switch to a different protection domain. This is an advanced stunt // for use by kernel threads only internal static void SwitchToDomain(ProtectionDomain newDomain) { Thread currentThread = CurrentThread; currentThread.CheckAddressSpaceConsistency(); AddressSpace processorSpace = Processor.GetCurrentAddressSpace(); AddressSpace newSpace = newDomain.AddressSpace; if (newSpace != processorSpace) { Processor.ChangeAddressSpace(newSpace); currentThread.tempDomain = newDomain; } currentThread.CheckAddressSpaceConsistency(); } // Call this to snap back to our parent process' domain. internal static void RevertToParentDomain() { Thread currentThread = CurrentThread; currentThread.CheckAddressSpaceConsistency(); Processor.ChangeAddressSpace(currentThread.process.Domain.AddressSpace); currentThread.tempDomain = null; currentThread.CheckAddressSpaceConsistency(); } // This property provides the correct answer even when an // arbitrary protection domain is temporarily being used internal ProtectionDomain CurrentDomain { get { CheckAddressSpaceConsistency(); return (tempDomain != null) ? tempDomain : process.Domain; } } [Inline] private void CheckAddressSpaceConsistency() { ProtectionDomain currentDomain = (tempDomain != null) ? tempDomain : process.Domain; DebugStub.Assert(Processor.GetCurrentAddressSpace() == currentDomain.AddressSpace); } #endif } }