3065 lines
111 KiB
C#
3065 lines
111 KiB
C#
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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(): Decr 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.Memory;
|
|
using Microsoft.Singularity.Isal;
|
|
using Microsoft.Bartok.Runtime;
|
|
|
|
[CLSCompliant(false)]
|
|
public enum ThreadEvent : ushort
|
|
{
|
|
CreateIdle = 12,
|
|
Create = 13,
|
|
WaitAny = 30,
|
|
WaitFail = 31,
|
|
SwitchTo = 3,
|
|
ThreadPackageInit = 10
|
|
}
|
|
|
|
internal enum AbortRequestSource
|
|
{
|
|
ABIExit = 0,
|
|
ABINoGCExit = 1,
|
|
WaitHandle = 2,
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Class implements thread functionality in Singluarity
|
|
/// </summary>
|
|
///
|
|
public sealed partial class Thread
|
|
{
|
|
|
|
///
|
|
/// <summary>
|
|
/// Constructor to create a new thread
|
|
///</summary>
|
|
///
|
|
/// <param name="process">A process to which thread belongs to</param>
|
|
///
|
|
private Thread(Process process)
|
|
{
|
|
|
|
// Initialize thread fields to default values
|
|
this.processThreadIndex = -1;
|
|
this.threadIndex = -1;
|
|
this.schedulerInfo.State = ThreadState.Unstarted;
|
|
|
|
// Set up thread kernel mode information
|
|
this.SetKernelMode();
|
|
|
|
// Bind thread to a process
|
|
this.process = process;
|
|
|
|
// Bind thread to a proper scheduler
|
|
#if false
|
|
if (process != null) {
|
|
this.ScheduleData = process.DefaultScheduleData;
|
|
}
|
|
#endif
|
|
|
|
// Initialize context fields
|
|
this.context.threadIndex = unchecked((ushort)-1);
|
|
this.context.processThreadIndex = unchecked((ushort)-1);
|
|
this.context.processId = unchecked((ushort)-1);
|
|
Transitions.InitializeStatusWord(ref this.context);
|
|
|
|
// Allocate the kernel objects needed by the thread.
|
|
this.threadLock = new SpinLock(SpinLock.Types.Thread);
|
|
this.autoEvent = new AutoResetEvent(false);
|
|
this.joinEvent = new ManualResetEvent(false);
|
|
this.localServiceRequest = new ThreadLocalServiceRequest();
|
|
this.schedulerEntry = new ThreadEntry(this);
|
|
this.timerEntry= new ThreadEntry(this);
|
|
this.deferredEntry= new ThreadEntry(this);
|
|
|
|
// Initialize wait entries cache so that we don't have to perform allocation in most
|
|
// common case, when thread is waiting on single handle
|
|
this.GetWaitEntries(1);
|
|
|
|
// Try to put the thread in the thread table.
|
|
bool saved = Processor.DisableInterrupts();
|
|
threadTableLock.Acquire(CurrentThread);
|
|
try {
|
|
// Grab the first empty spot in thread table
|
|
for (int i = 0; i < threadTable.Length; i++) {
|
|
// Calculate possible thread index starting from the last time success
|
|
int index = (threadIndexGenerator + i) % threadTable.Length;
|
|
|
|
// If none of the existing thread occupies this particular index use it:
|
|
if (threadTable[index] == null) {
|
|
|
|
// Bind table to our thread and our thread to table
|
|
threadTable[index] = this;
|
|
this.threadIndex = index;
|
|
|
|
// Remember last we visited this place
|
|
threadIndexGenerator = index + 1;
|
|
|
|
// NB: We call this once, subsequently the GC visitor calls it.
|
|
context.UpdateAfterGC(this);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
finally {
|
|
threadTableLock.Release(CurrentThread);
|
|
Processor.RestoreInterrupts(saved);
|
|
}
|
|
|
|
// Assert that thread table is not full temporary before real fix
|
|
VTable.Assert(this.threadIndex != -1);
|
|
|
|
// Initialize context with proper thread index
|
|
context.threadIndex = unchecked((ushort)(threadIndex));
|
|
Transitions.InitializeStatusWord(ref context);
|
|
|
|
// Bind context to a process if one exists
|
|
if (process != null) {
|
|
context.processId = unchecked((ushort)(process.ProcessId));
|
|
}
|
|
|
|
// Initialize thread's stack.
|
|
context.stackBegin = 0;
|
|
context.stackLimit = 0;
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Idle thread constructor, deligates major initialization to main constructor by
|
|
/// using idle process
|
|
///</summary>
|
|
///
|
|
/// <param name="idle">Indicates if thread is idle or not</param>
|
|
///
|
|
protected Thread(bool idle)
|
|
: this(Process.idleProcess)
|
|
{
|
|
// Initialize stack
|
|
UIntPtr stackSegment = Stacks.GetInitialStackSegment(ref context);
|
|
|
|
// At this point we can't fail
|
|
DebugStub.Assert(stackSegment != UIntPtr.Zero);
|
|
|
|
#if PAGING
|
|
// Initialize idle context
|
|
context.InitializeIdle(threadIndex, stackSegment,
|
|
unchecked((uint)Process.kernelProcess.Domain.AddressSpace.PdptPage.Value));
|
|
#else
|
|
// Initialize idle context
|
|
context.InitializeIdle(threadIndex, stackSegment, 0);
|
|
#endif
|
|
// Record ilde thread creation event
|
|
Monitoring.Log(Monitoring.Provider.Thread,
|
|
(ushort)ThreadEvent.CreateIdle, 0,
|
|
(uint)threadIndex, 0, 0, 0, 0);
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// A thread constructor with a start routine, deligates major initialization to main constructor by
|
|
/// using idle process
|
|
///</summary>
|
|
///
|
|
/// <param name="process">A process to which thread belongs to</param>
|
|
/// <param name="start">Thread's start routine</param>
|
|
///
|
|
protected Thread(Process process, ThreadStart start)
|
|
: this(process)
|
|
{
|
|
// If no start routine specified throw exception
|
|
if (start == null) {
|
|
throw new ArgumentNullException("start");
|
|
}
|
|
|
|
// Initialize thread's start routine
|
|
this.threadStart = start;
|
|
|
|
#if PAGING
|
|
// In case of paging initialize abi stack
|
|
this.context.abiStackHead = Stacks.GetStackSegment(0, ref context, true, false);
|
|
this.context.abiStackBegin = this.context.stackBegin;
|
|
this.context.abiStackLimit = this.context.stackLimit;
|
|
this.context.stackBegin = 0;
|
|
this.context.stackLimit = 0;
|
|
#endif
|
|
// Allocation initial stack segment for the thread
|
|
UIntPtr stackSegment = Stacks.GetInitialStackSegment(ref context);
|
|
// Assert condition - no stack, no thread - is this really true?
|
|
DebugStub.Assert(stackSegment != UIntPtr.Zero);
|
|
#if PAGING
|
|
// Initialize context
|
|
context.Initialize(threadIndex, stackSegment,
|
|
unchecked((uint)(Process.Domain.AddressSpace.PdptPage.Value)));
|
|
#else
|
|
// Initialize context
|
|
context.Initialize(threadIndex, stackSegment, 0);
|
|
#endif
|
|
Monitoring.Log(Monitoring.Provider.Thread,
|
|
(ushort)ThreadEvent.Create, 0,
|
|
(uint)threadIndex, 0, 0, 0, 0);
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// An API to create a thread
|
|
///</summary>
|
|
///
|
|
/// <param name="processor">An idle thread for a given processor </param>
|
|
///
|
|
public static Thread CreateIdleThread(Processor processor)
|
|
{
|
|
// Allocate new idle thread.
|
|
Thread idle = new Thread(true);
|
|
idle.Affinity = processor.Id;
|
|
|
|
// Check if new thread is valid
|
|
if (idle.threadIndex < 0) {
|
|
Tracing.Log(Tracing.Warning, "Thread table is full.");
|
|
DebugStub.Break();
|
|
return null;
|
|
}
|
|
|
|
return idle;
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// An API to create a thread
|
|
///</summary>
|
|
///
|
|
/// <param name="process">A process to which thread belongs to</param>
|
|
/// <param name="start">Thread's start routine</param>
|
|
///
|
|
[Inline]
|
|
public static Thread CreateThread(Process process, ThreadStart start)
|
|
{
|
|
return CreateThread(process, start, false);
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// An API to create a thread
|
|
///</summary>
|
|
///
|
|
/// <param name="process">A process to which thread belongs to</param>
|
|
/// <param name="start">Thread's start routine</param>
|
|
/// <param name="needThreadHandle">Whether a thread handle should be created</param>
|
|
///
|
|
public static Thread CreateThread(Process process, ThreadStart start, bool needThreadHandle)
|
|
{
|
|
ThreadHandle handle;
|
|
|
|
// If no process specified use current one
|
|
if (process == null) {
|
|
process = CurrentProcess;
|
|
}
|
|
|
|
//Process can't be null from this point on
|
|
VTable.Assert(process != null);
|
|
|
|
// Allocate the thread.
|
|
Thread thread = new Thread(process, start);
|
|
|
|
// Check if thread is valid, return right a way if it is not
|
|
if (thread.threadIndex < 0) {
|
|
Tracing.Log(Tracing.Warning, "Thread table is full.");
|
|
DebugStub.Break();
|
|
return null;
|
|
}
|
|
|
|
// Add newly created thread to the process
|
|
process.AddThread(thread, needThreadHandle);
|
|
|
|
//MemoryBarrier();?
|
|
|
|
// Notifiy perfcounters about thread creation
|
|
PerfCounters.IncrementThreadsCreated();
|
|
DebugStub.AddToPerfCounter(4, 1);
|
|
|
|
return thread;
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Retrieve thread's scheduler for a thread
|
|
/// </summary>
|
|
///
|
|
public ProcessorDispatcher Dispatcher
|
|
{
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
get
|
|
{
|
|
return this.dispatcher;
|
|
}
|
|
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
set
|
|
{
|
|
this.dispatcher = value;
|
|
}
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Retrieve thread's scheduler info
|
|
/// </summary>
|
|
///
|
|
public SchedulerInfo ThreadSchedulerInfo
|
|
{
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
get
|
|
{
|
|
return this.schedulerInfo;
|
|
}
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Find out if thread is suspended
|
|
/// </summary>
|
|
///
|
|
public bool IsSuspended
|
|
{
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
get
|
|
{
|
|
return (this.schedulerInfo.State == ThreadState.Suspended);
|
|
}
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Find out if thread is blocked
|
|
/// </summary>
|
|
///
|
|
public bool IsBlocked
|
|
{
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
get
|
|
{
|
|
return this.schedulerInfo.State == ThreadState.Blocked;
|
|
}
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Check if thread is idle
|
|
/// </summary>
|
|
///
|
|
public bool IsIdle
|
|
{
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
get
|
|
{
|
|
return this.type == ThreadType.Idle;
|
|
}
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Check if thread is idle
|
|
/// </summary>
|
|
///
|
|
public bool IsScavenger
|
|
{
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
get
|
|
{
|
|
return this.type == ThreadType.Scavenger;
|
|
}
|
|
}
|
|
|
|
|
|
///
|
|
/// <summary>
|
|
/// Check if thread is idle
|
|
/// </summary>
|
|
///
|
|
public ThreadType Type
|
|
{
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
get
|
|
{
|
|
return this.type;
|
|
}
|
|
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
set
|
|
{
|
|
this.type = value;;
|
|
}
|
|
}
|
|
|
|
|
|
///
|
|
/// <summary>
|
|
/// Find out if thread is runnable
|
|
/// </summary>
|
|
///
|
|
public bool IsRunnable
|
|
{
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
get
|
|
{
|
|
return this.schedulerInfo.State == ThreadState.Runnable;
|
|
}
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Find out if thread is running
|
|
/// </summary>
|
|
///
|
|
public bool IsRunning
|
|
{
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
get
|
|
{
|
|
return this.schedulerInfo.State == ThreadState.Running;
|
|
}
|
|
}
|
|
|
|
|
|
///
|
|
/// <summary>
|
|
/// Find out who unblocked the thread
|
|
/// </summary>
|
|
///
|
|
public int UnblockedBy
|
|
{
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
get
|
|
{
|
|
return this.schedulerInfo.UnblockedBy;
|
|
}
|
|
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
set
|
|
{
|
|
this.schedulerInfo.UnblockedBy = value;
|
|
}
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Find out thread freeze count
|
|
/// </summary>
|
|
///
|
|
public int FreezeCount
|
|
{
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
get
|
|
{
|
|
return this.schedulerInfo.FreezeCount;
|
|
}
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
///
|
|
public void Start()
|
|
{
|
|
// Notify process that thread is about to start
|
|
process.StartThread();
|
|
|
|
// Go ahead and start thread - attach it scheduler and start running
|
|
StartRunningThread();
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Start running main thread inside of process
|
|
/// </summary>
|
|
///
|
|
///<remark> precondition: process.processLock held </remark>
|
|
internal void SetMainThreadRunning()
|
|
{
|
|
VTable.Assert(this.ThreadState == ThreadState.Unstarted);
|
|
process.StartMainThread();
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Start thread running: Attach to GC and process's scheduler
|
|
/// </summary>
|
|
///
|
|
internal void StartRunningThread()
|
|
{
|
|
VTable.Assert(this.ThreadState == ThreadState.Unstarted);
|
|
|
|
// Tell the GC that we have created the thread
|
|
GC.NewThreadNotification(this, false);
|
|
|
|
// Tell the scheduler to initialize the thread.
|
|
Kernel.TheScheduler.OnThreadStateInitialize(this, true);
|
|
|
|
// Let scheduler start thread: Scheduler is responsible for changing thread state
|
|
Kernel.TheScheduler.OnThreadStart(this);
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// An entry point for an idle thread. ThreadContext.InitializeIdle sets
|
|
/// DispatcherThreadStub as the first instruction to be executed in a new thread context.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// There are currently only two dispatcher threads: Idle and Scavenger. Both
|
|
/// of them are created during processor startup
|
|
/// </remarks>
|
|
///
|
|
/// <param name="index">An index represents a thread we are starting</param>
|
|
///
|
|
[AccessedByRuntime("referenced from c++")]
|
|
private static unsafe void DispatcherThreadStub(int index)
|
|
{
|
|
Thread currentThread = Thread.CurrentThread;
|
|
|
|
// Assert preconditions: Thread should be really one of dispatcher threads
|
|
VTable.Assert(threadTable[index] == currentThread);
|
|
VTable.Assert(
|
|
Processor.CurrentProcessor.Dispatcher.IsOneOfMyDispatcherThreads(currentThread));;
|
|
|
|
// If processor idle use idle loop otherwise use scavanager loop
|
|
if (Processor.CurrentProcessor.Dispatcher.IsMyIdleThread(currentThread)) {
|
|
// Mark thread idle before running
|
|
Thread.CurrentThread.Type = ThreadType.Idle;
|
|
ProcessorDispatcher.IdleThreadLoop();
|
|
}
|
|
else {
|
|
Thread.CurrentThread.Type = ThreadType.Scavenger;
|
|
// This is a scavanger thread so use the appropriate loop
|
|
ProcessorDispatcher.ScavengerThreadLoop();
|
|
}
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// An entry point for any thread. ThreadContext.Initialize sets ThreadStub as
|
|
/// the first instruction to be executed in a new thread context.
|
|
/// </summary>
|
|
///
|
|
/// <param name="threadIndex">An index represents a thread we are starting</param>
|
|
///
|
|
[AccessedByRuntime("referenced from c++")]
|
|
private static unsafe void ThreadStub(int threadIndex)
|
|
{
|
|
Thread currentThread = threadTable[threadIndex];
|
|
|
|
// Give our Protection Domain a chance to set up
|
|
// if we're first in here. Run this before anything
|
|
// else!
|
|
currentThread.process.Domain.InitHook();
|
|
|
|
// Attach thread to GC
|
|
Transitions.ThreadStart();
|
|
GC.ThreadStartNotification(threadIndex);
|
|
|
|
Tracing.Log(Tracing.Trace, "ThreadStub() entered");
|
|
ThreadStart startFun = currentThread.threadStart;
|
|
|
|
// Initialize procesor FPU
|
|
Isa.InitFpu();
|
|
|
|
// Start an entry point of actual thread
|
|
try {
|
|
startFun();
|
|
}
|
|
catch (ProcessStopException) {
|
|
// Stop exception is the only "good" exception expect at this point
|
|
// Ok, exit thread without failure.
|
|
}
|
|
catch (Exception e) {
|
|
// Not ok, fail: This is pretty much unhandled exception
|
|
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));
|
|
|
|
// We are done: stop thread and disassociate it from scheduler
|
|
Kernel.TheScheduler.OnThreadStop(currentThread);
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Service a stop thread - Method perfoms final tearing down of thread it is called
|
|
/// on special service thread.
|
|
/// </summary>
|
|
///
|
|
internal void ServiceStopped()
|
|
{
|
|
// Make sure ThreadStub has gotten a chance to exit before
|
|
// we deallocate its stack:
|
|
bool wasUnstarted;
|
|
|
|
// Indicate all waiters on this thread that we are done
|
|
joinEvent.Set();
|
|
|
|
// Make sure nobody else has access to the thread
|
|
bool saved = Processor.DisableInterrupts();
|
|
this.threadLock.Acquire();
|
|
try {
|
|
// Thread might have never started
|
|
wasUnstarted = (this.ThreadState == ThreadState.Unstarted);
|
|
|
|
// If thread has never started set state by hand to stopped
|
|
if (wasUnstarted) {
|
|
this.schedulerInfo.State = ThreadState.Stopped;
|
|
}
|
|
|
|
}
|
|
finally {
|
|
this.threadLock.Release();
|
|
Processor.RestoreInterrupts(saved);
|
|
}
|
|
|
|
|
|
// Asssert preconditions
|
|
VTable.Assert(this.ThreadState == ThreadState.Stopped);
|
|
VTable.Assert(threadIndex > 0);
|
|
|
|
// Notify GC that thread is tearing down
|
|
if (!wasUnstarted) {
|
|
if (Transitions.InMutatorState(GetThreadId())){
|
|
Transitions.ThreadEnd(GetThreadId());
|
|
}
|
|
GC.DeadThreadNotification(this);
|
|
}
|
|
|
|
int count = 0;
|
|
#if !PAGING
|
|
while (context.stackBegin != 0) {
|
|
|
|
if (count == 0) {
|
|
Stacks.ReturnInitialStackSegment(ref context);
|
|
}
|
|
else {
|
|
Stacks.ReturnStackSegment(ref context);
|
|
}
|
|
|
|
count++;
|
|
}
|
|
#else
|
|
// Free up stack
|
|
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;
|
|
}
|
|
|
|
if (count == 0) {
|
|
// First segment is reported to balance AllocateInitialStackSegment
|
|
Stacks.ReturnInitialStackSegment(ref context);
|
|
}
|
|
else {
|
|
Stacks.ReturnStackSegment(ref context);
|
|
}
|
|
count++;
|
|
}
|
|
#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;
|
|
|
|
// Free up table index
|
|
saved = Processor.DisableInterrupts();
|
|
threadTableLock.Acquire(currentThread);
|
|
|
|
try {
|
|
threadTable[threadIndex] = null;
|
|
Transitions.DeadThreadNotification(threadIndex);
|
|
}
|
|
finally {
|
|
threadTableLock.Release(currentThread);
|
|
Processor.RestoreInterrupts(saved);
|
|
}
|
|
|
|
if (process != null) {
|
|
process.ServiceOnThreadStop(this);
|
|
}
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Thread is alive only if it has been started and it is not stopped. There is no
|
|
/// gurantee as this call returns thread will stay alive. You will need to provide extra
|
|
/// synchronization
|
|
/// </summary>
|
|
///
|
|
public bool IsAlive
|
|
{
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
get
|
|
{
|
|
return (this.ThreadState != ThreadState.Unstarted &&
|
|
this.ThreadState != ThreadState.Stopped);
|
|
}
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Wait for thread to stop
|
|
/// </summary>
|
|
///
|
|
/// <remark>
|
|
/// Exceptions: ThreadStateException if the thread has not been started yet.
|
|
/// </remark>
|
|
public void Join()
|
|
{
|
|
Join(SchedulerTime.MaxValue);
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Wait for thread to stop or specified timeout expires
|
|
/// </summary>
|
|
///
|
|
/// <remark>
|
|
/// Exceptions: ThreadStateException if the thread has not been started yet.
|
|
/// </remark>
|
|
public bool Join(TimeSpan timeout)
|
|
{
|
|
if (this.ThreadState == ThreadState.Unstarted) {
|
|
throw new ThreadStateException();
|
|
}
|
|
else if (this.ThreadState == ThreadState.Stopped) {
|
|
return true;
|
|
}
|
|
return joinEvent.WaitOne(timeout);
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Wait for thread to stop or specified timeout expires
|
|
/// </summary>
|
|
///
|
|
/// <remark>
|
|
/// Exceptions: ThreadStateException if the thread has not been started yet.
|
|
/// </remark>
|
|
public bool Join(SchedulerTime timeout)
|
|
{
|
|
if (this.ThreadState == ThreadState.Unstarted) {
|
|
throw new ThreadStateException();
|
|
}
|
|
else if (this.ThreadState == ThreadState.Stopped) {
|
|
return true;
|
|
}
|
|
return joinEvent.WaitOne(timeout);
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Set thread's affinity
|
|
/// </summary>
|
|
///
|
|
public void SetAffinity(int affinity)
|
|
{
|
|
// Affinity can only be changed when it has not been set
|
|
if (Affinity == NoAffinity) {
|
|
Affinity = affinity;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// 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));
|
|
}
|
|
|
|
//| <include path='docs/doc[@for="Thread.Sleep"]/*' />
|
|
public static void Sleep(SchedulerTime stop)
|
|
{
|
|
Tracing.Log(Tracing.Audit, "Sleep until stop");
|
|
WaitHandle.WaitAny(null, 0, stop);
|
|
Tracing.Log(Tracing.Audit, "Sleep until stop finished");
|
|
}
|
|
|
|
//| <include path='docs/doc[@for="Thread.Sleep"]/*' />
|
|
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 a explicit busy loop because the
|
|
// hardware can be informed that it is busy waiting.
|
|
//
|
|
//| <include path='docs/doc[@for="Thread.SpinWait"]/*' />
|
|
[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)
|
|
{
|
|
Thread.WaitForGCEvent(currentThreadIndex, SchedulerTime.MaxValue);
|
|
}
|
|
|
|
internal static void WaitForGCEvent(int currentThreadIndex, int millis)
|
|
{
|
|
SchedulerTime stop = SchedulerTime.Now.AddMilliseconds(millis);
|
|
Thread.WaitForGCEvent(currentThreadIndex, stop);
|
|
}
|
|
|
|
|
|
///
|
|
/// <summary>
|
|
/// Signal thread's GC event, we can't use actual event object as it might allow
|
|
/// unneccesasry reentrancy.
|
|
/// </summary>
|
|
///
|
|
[Inline]
|
|
internal static void SignalGCEvent(int threadIndex) {
|
|
SignalGCEvent(Thread.GetCurrentThreadIndex(), threadIndex);
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Wait for GC - method is called by GC to park thread while GC is up and running
|
|
/// At this point we chose not to use autoresetevent as it might introduce reentrancy.
|
|
/// In this method we closely mimic to what we are doing in WaitAny when waiting on handles
|
|
/// </summary>
|
|
///
|
|
private static void WaitForGCEvent(
|
|
int currentThreadIndex,
|
|
SchedulerTime stop)
|
|
{
|
|
Thread currentThread = threadTable[currentThreadIndex];
|
|
|
|
Tracing.Log(Tracing.Audit, "WaitForGCEvent({0})",
|
|
(UIntPtr) currentThreadIndex);
|
|
VTable.Deny(ProcessorDispatcher.IsIdleThread(currentThreadIndex));
|
|
|
|
while (true) {
|
|
// Attempt to reset the one-shot from true to false
|
|
if (Interlocked.CompareExchange(ref currentThread.gcEventSignaled,
|
|
0,
|
|
1) == 1) {
|
|
break;
|
|
}
|
|
// If we failed, yield the processor. Eventually we will block, but the
|
|
// dispatcher doesn't yet provide a suitable blocking primitive below the
|
|
// scheduler -- beyond perhaps Suspend.
|
|
Yield();
|
|
}
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Signal thread's GC event. For more information see WaitForGCEvent
|
|
/// </summary>
|
|
///
|
|
internal static void SignalGCEvent(int currentThreadIndex,int threadIndex)
|
|
{
|
|
Thread currentThread = threadTable[threadIndex];
|
|
|
|
Tracing.Log(Tracing.Audit, "SignalGCEvent({0})",
|
|
(UIntPtr) threadIndex);
|
|
VTable.Deny(ProcessorDispatcher.IsIdleThread(threadIndex));
|
|
|
|
if (currentThread != null) {
|
|
Interlocked.Exchange(ref currentThread.gcEventSignaled, 1);
|
|
}
|
|
}
|
|
|
|
internal void SignalEvent()
|
|
{
|
|
this.autoEvent.Set();
|
|
}
|
|
|
|
internal void SignalMonitor()
|
|
{
|
|
this.autoEvent.Set();
|
|
}
|
|
|
|
//| <include path='docs/doc[@for="Thread.CurrentThread"]/*' />
|
|
public static extern Thread CurrentThread
|
|
{
|
|
[NoStackLinkCheck]
|
|
[NoHeapAllocation]
|
|
[Intrinsic]
|
|
get;
|
|
}
|
|
|
|
public static Process CurrentProcess
|
|
{
|
|
[NoStackLinkCheckTrans]
|
|
[NoHeapAllocation]
|
|
get {
|
|
return Processor.GetCurrentThread().process;
|
|
}
|
|
}
|
|
|
|
public Process Process
|
|
{
|
|
[NoHeapAllocation]
|
|
get { return process; }
|
|
}
|
|
|
|
public ThreadHandle Handle
|
|
{
|
|
[NoHeapAllocation]
|
|
get { return threadHandle; }
|
|
}
|
|
|
|
[NoStackLinkCheckTrans]
|
|
[RequiredByBartok]
|
|
[NoHeapAllocation]
|
|
public static int GetCurrentThreadIndex()
|
|
{
|
|
return Processor.GetCurrentThread().threadIndex;
|
|
}
|
|
|
|
[NoStackLinkCheckTrans]
|
|
[RequiredByBartok]
|
|
[NoHeapAllocation]
|
|
private static Thread GetCurrentThreadNative()
|
|
{
|
|
return Processor.GetCurrentThread();
|
|
}
|
|
|
|
[NoHeapAllocation]
|
|
public static UIntPtr GetThreadLocalValue()
|
|
{
|
|
return Processor.GetCurrentThread().threadLocalValue;
|
|
}
|
|
|
|
[NoHeapAllocation]
|
|
public static void SetThreadLocalValue(UIntPtr value)
|
|
{
|
|
Processor.GetCurrentThread().threadLocalValue = value;
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Retrieve thread's state
|
|
/// </summary>
|
|
///
|
|
public ThreadState ThreadState
|
|
{
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
get
|
|
{
|
|
return schedulerInfo.State;
|
|
}
|
|
}
|
|
|
|
[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();
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Suspend thread and wait till it is suspended
|
|
/// </summary>
|
|
///
|
|
internal void Suspend(bool aboutToStop)
|
|
{
|
|
Kernel.TheScheduler.Suspend(this);
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Resume thread
|
|
/// </summary>
|
|
///
|
|
internal void Resume()
|
|
{
|
|
Kernel.TheScheduler.Resume(this);
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Handle abort - if abort is requested for the current thread, calling this
|
|
/// method stops the thread immediately. Note this method can only be called
|
|
/// by the current thread
|
|
/// </summary>
|
|
///
|
|
/// <param name="source"> Where the request come from, for debugging purpose</param>
|
|
///
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
internal void ProcessAbortIfRequested(AbortRequestSource source)
|
|
{
|
|
// TODO: This exists with no
|
|
// code as a means of getting an updated Bartok in
|
|
// the tree - the added files in the process call
|
|
// this method and the files can't be edited until
|
|
// checked in.
|
|
#if false
|
|
// Assert preconditions: This method can only be called by the current thread
|
|
VTable.Assert(this == Thread.CurrentThread);
|
|
|
|
// If the thread is requested to abort, stop it now
|
|
if (IsAbortRequested) {
|
|
|
|
// We are done: stop thread and disassociate it from scheduler
|
|
Scheduler.OnThreadStop(this);
|
|
}
|
|
#endif // false
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Abort thread - thread will be aborted right a way unless dealy abort bit is set
|
|
/// </summary>
|
|
///
|
|
internal void Stop()
|
|
{
|
|
// First make sure that thread is stable - suspended
|
|
Suspend(false);
|
|
|
|
// Now we can stop thread
|
|
StopSuspended();
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Abort suspended thread - thread will be aborted right a way unless dealy abort bit is set
|
|
/// Once stop call is processed, thread will be resumed to finish stop
|
|
/// </summary>
|
|
///
|
|
internal void StopSuspended()
|
|
{
|
|
bool shouldResumeAtTheEnd = true;
|
|
// Once thread is supsended - turn on abort
|
|
Abort();
|
|
|
|
// If thread is not stopped already and is not blocked and not in delay abort scope unblock it
|
|
if (!IsStopped() && !IsAbortDelayed()) {
|
|
// Try to unblock it if it is blocked
|
|
if (this.UnblockedBy == WaitHandle.UninitWait) {
|
|
// Just go ahead and try to unblock it if we succesfuly thread will be
|
|
// aborted after resume
|
|
if (Unblock(WaitHandle.UnblockedByAbort) == WaitHandle.UnblockedByAbort &&
|
|
ShouldCallSchedulerUnBlock(WaitHandle.UnblockedByAbort)) {
|
|
|
|
// Before we can unblock thread we have to resume it -currently
|
|
// we don't have a way of going from block and suspend state back
|
|
// and forward:
|
|
Resume();
|
|
|
|
// Now are ready to unblock it
|
|
Kernel.TheScheduler.OnThreadUnblocked(this);
|
|
|
|
shouldResumeAtTheEnd = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// We are done with thread just resume it
|
|
if (shouldResumeAtTheEnd) {
|
|
Resume();
|
|
}
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Move thread abort status to abort state
|
|
/// </summary>
|
|
///
|
|
private void Abort()
|
|
{
|
|
SchedulerInfo newInfo;
|
|
SchedulerInfo oldInfo;
|
|
|
|
do {
|
|
// Copy scheduler state to memory
|
|
newInfo = this.schedulerInfo;
|
|
oldInfo = newInfo;
|
|
|
|
// Mark thread as aborted
|
|
newInfo.IsAborted = true;
|
|
|
|
// Attempt to update atomicatlly the state.
|
|
} while (oldInfo.Data != Interlocked.CompareExchange(ref this.schedulerInfo.Data,
|
|
newInfo.Data,
|
|
oldInfo.Data));
|
|
return;
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Handle Abort - if abort is set and we are not in dealy abort scope throw
|
|
/// abort exception
|
|
/// </summary>
|
|
///
|
|
internal void ProcessAbortIfRequired()
|
|
{
|
|
// Assert preconditions: This method can only be called by the current thread
|
|
VTable.Assert(this == Thread.CurrentThread);
|
|
|
|
// If thread is aborted and abort is not delayed handle it!
|
|
if (ShouldStop()) {
|
|
// Stop if we have to
|
|
|
|
// We are done: stop thread and disassociate it from scheduler
|
|
Kernel.TheScheduler.OnThreadStop(this);
|
|
}
|
|
return;
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Enable/Disable delay abort on the thread depending on the passed in flag
|
|
/// </summary>
|
|
///
|
|
/// <param name="shouldDelayAbort">Flag indicating if abort has to be enabled or disabled </param>
|
|
///
|
|
[NoHeapAllocation]
|
|
internal void DelayStop(bool shouldDelayAbort)
|
|
{
|
|
SchedulerInfo newInfo;
|
|
SchedulerInfo oldInfo;
|
|
|
|
do {
|
|
// Copy scheduler state to memory
|
|
newInfo = this.schedulerInfo;
|
|
oldInfo = newInfo;
|
|
|
|
// Check the abort direction - enabling or disabling
|
|
if (shouldDelayAbort) {
|
|
// We are enabling abort
|
|
newInfo.IncrementDelayAbortCount();
|
|
}
|
|
else {
|
|
// We are disabling abort
|
|
newInfo.DecrementDelayAbortCount();
|
|
}
|
|
|
|
// Attempt to update atomicatlly the state.
|
|
} while (oldInfo.Data != Interlocked.CompareExchange(ref this.schedulerInfo.Data,
|
|
newInfo.Data,
|
|
oldInfo.Data));
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Handle Abort - if abort is set and we are not in dealy abort scope throw
|
|
/// abort exception
|
|
/// </summary>
|
|
///
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
internal bool ShouldStop()
|
|
{
|
|
// Check if thread has to be stopped
|
|
return (IsAborted() && !IsAbortDelayed());
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Check if we can abort thread
|
|
/// </summary>
|
|
///
|
|
[NoHeapAllocation]
|
|
internal bool IsAbortDelayed()
|
|
{
|
|
return this.schedulerInfo.DelayAbortCount > 0;
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Check if thread has abort bit set
|
|
/// </summary>
|
|
///
|
|
[NoHeapAllocation]
|
|
internal bool IsAborted()
|
|
{
|
|
return this.schedulerInfo.IsAborted;
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Notify thread that it acquired spinlock of specific rank
|
|
/// </summary>
|
|
///
|
|
/// <param name="type">Type of a spinlock </param>
|
|
///
|
|
[NoHeapAllocation]
|
|
internal void NotifySpinLockAboutToAcquire(int type)
|
|
{
|
|
int rank = SpinLock.DeriveRank(type);
|
|
|
|
// Assert preconditions - we can't acquire spinlocks of the lower rank than we already
|
|
// acquired
|
|
VTable.Assert(rank == (int) SpinLock.Ranks.NoRank ||
|
|
this.spinLockRankMask == (int) SpinLock.Ranks.NoRank ||
|
|
this.spinLockRankMask > rank);
|
|
VTable.Assert(Processor.InterruptsDisabled());
|
|
|
|
// Record rank
|
|
this.spinLockRankMask |= rank;
|
|
|
|
// Update number of spinlocks held - we have to do until we remove NoRank...
|
|
this.numberOfSpinlocksHeld++;
|
|
|
|
// If spinlock has a rank higher than dispatcher, we need to mark thread for delay
|
|
// abortion
|
|
if (rank == (int)SpinLock.Ranks.NoRank || rank > (int)SpinLock.Ranks.Dispatcher) {
|
|
DelayStop(true);
|
|
}
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Notify thread that released a spinlock of specific rank
|
|
/// </summary>
|
|
///
|
|
/// <param name="type">Type of a spinlock </param>
|
|
///
|
|
[NoHeapAllocation]
|
|
internal void NotifySpinLockReleased(int type)
|
|
{
|
|
VTable.Assert(Processor.InterruptsDisabled());
|
|
|
|
int rank = SpinLock.DeriveRank(type);
|
|
|
|
// Assert preconditions: We can't release multiple ranks at the same time: There should
|
|
// be only a single bit set for the rank. The following calculation turns off a least
|
|
// significant bit: x & (x-1) and hence the assert:
|
|
VTable.Assert((rank & (rank-1)) == 0);
|
|
|
|
// We can't release spinlocks out of order. The calculation below
|
|
// relies on the following property of bitmask operations:If we turn off least significant bit
|
|
// and then or it with the rank we are releasing, we should get the same mask as we head
|
|
// before. To turn off least significant bit we use operation such as x = x&(x-1). If
|
|
// spinlocks released out of order we will turn off least significant bit other than
|
|
// rank we about to release so when we apply or with rank to the result we won't get
|
|
// the same mask back.
|
|
|
|
VTable.Assert(rank == (int)SpinLock.Ranks.NoRank ||
|
|
this.spinLockRankMask == (
|
|
(this.spinLockRankMask & (this.spinLockRankMask-1)) | rank));
|
|
|
|
// We should have acquired spinlock of such rank before
|
|
VTable.Assert(rank == (int)SpinLock.Ranks.NoRank ||
|
|
(this.spinLockRankMask & rank) != 0);
|
|
|
|
// Turn off specified rank
|
|
this.spinLockRankMask ^= rank;
|
|
|
|
// Update number of spinlocks held - we have to do until we remove NoRank...
|
|
this.numberOfSpinlocksHeld--;
|
|
|
|
// If spinlock has a rank higher than dispatcher , we need to mark thread that
|
|
// it can be stopped
|
|
if (rank == (int)SpinLock.Ranks.NoRank || rank > (int)SpinLock.Ranks.Dispatcher) {
|
|
DelayStop(false);
|
|
}
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Given a thread id return thread
|
|
/// </summary>
|
|
///
|
|
/// <param name="threadId">Thread Id</param>
|
|
///
|
|
[NoHeapAllocation]
|
|
internal static Thread GetThreadFromThreadId(int threadId)
|
|
{
|
|
// Assert preconditions: threadId has to be in range and thread can't be null
|
|
VTable.Assert(threadId < threadTable.Length);
|
|
VTable.Assert(threadTable[threadId] != null);
|
|
|
|
return threadTable[threadId];
|
|
}
|
|
|
|
|
|
[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.
|
|
//| <include path='docs/doc[@for="Thread.AllocateDataSlot"]/*' />
|
|
public static LocalDataStoreSlot AllocateDataSlot()
|
|
{
|
|
return 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.
|
|
//| <include path='docs/doc[@for="Thread.AllocateNamedDataSlot"]/*' />
|
|
public static LocalDataStoreSlot AllocateNamedDataSlot(String name)
|
|
{
|
|
return 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.
|
|
//| <include path='docs/doc[@for="Thread.GetNamedDataSlot"]/*' />
|
|
public static LocalDataStoreSlot GetNamedDataSlot(String name)
|
|
{
|
|
return 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.
|
|
//| <include path='docs/doc[@for="Thread.FreeNamedDataSlot"]/*' />
|
|
public static void FreeNamedDataSlot(String name)
|
|
{
|
|
localDataStoreMgr.FreeNamedDataSlot(name);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Retrieves the value from the specified slot on the current thread.
|
|
//| <include path='docs/doc[@for="Thread.GetData"]/*' />
|
|
public static Object GetData(LocalDataStoreSlot slot)
|
|
{
|
|
localDataStoreMgr.ValidateSlot(slot);
|
|
|
|
if (localDataStore != null) {
|
|
return localDataStore.GetData(slot);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Sets the data in the specified slot on the currently running thread.
|
|
//| <include path='docs/doc[@for="Thread.SetData"]/*' />
|
|
public static void SetData(LocalDataStoreSlot slot, Object data)
|
|
{
|
|
// Create new DLS if one hasn't been created for this thread.
|
|
if (localDataStore == null) {
|
|
localDataStore = 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.
|
|
//
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileRead"]/*' />
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern byte VolatileRead(ref byte address);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileRead1"]/*' />
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern short VolatileRead(ref short address);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileRead2"]/*' />
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern int VolatileRead(ref int address);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileRead3"]/*' />
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern long VolatileRead(ref long address);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileRead4"]/*' />
|
|
[CLSCompliant(false)]
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern sbyte VolatileRead(ref sbyte address);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileRead5"]/*' />
|
|
[CLSCompliant(false)]
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern ushort VolatileRead(ref ushort address);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileRead6"]/*' />
|
|
[CLSCompliant(false)]
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern uint VolatileRead(ref uint address);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileRead7"]/*' />
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern IntPtr VolatileRead(ref IntPtr address);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileRead8"]/*' />
|
|
[CLSCompliant(false)]
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern UIntPtr VolatileRead(ref UIntPtr address);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileRead9"]/*' />
|
|
[CLSCompliant(false)]
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern ulong VolatileRead(ref ulong address);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileRead10"]/*' />
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern float VolatileRead(ref float address);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileRead11"]/*' />
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern double VolatileRead(ref double address);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileRead12"]/*' />
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern Object VolatileRead(ref Object address);
|
|
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
internal static unsafe extern byte VolatileReadUnsafe(byte* address);
|
|
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
internal static unsafe extern short VolatileReadUnsafe(short* address);
|
|
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
internal static unsafe extern int VolatileReadUnsafe(int* address);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileWrite"]/*' />
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern void VolatileWrite(ref byte address, byte value);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileWrite1"]/*' />
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern void VolatileWrite(ref short address, short value);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileWrite2"]/*' />
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern void VolatileWrite(ref int address, int value);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileWrite3"]/*' />
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern void VolatileWrite(ref long address, long value);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileWrite4"]/*' />
|
|
[CLSCompliant(false)]
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern void VolatileWrite(ref sbyte address, sbyte value);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileWrite5"]/*' />
|
|
[CLSCompliant(false)]
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern void VolatileWrite(ref ushort address, ushort value);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileWrite6"]/*' />
|
|
[CLSCompliant(false)]
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern void VolatileWrite(ref uint address, uint value);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileWrite7"]/*' />
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern void VolatileWrite(ref IntPtr address, IntPtr value);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileWrite8"]/*' />
|
|
[CLSCompliant(false)]
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern void VolatileWrite(ref UIntPtr address, UIntPtr value);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileWrite9"]/*' />
|
|
[CLSCompliant(false)]
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern void VolatileWrite(ref ulong address, ulong value);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileWrite10"]/*' />
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern void VolatileWrite(ref float address, float value);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileWrite11"]/*' />
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern void VolatileWrite(ref double address, double value);
|
|
|
|
//| <include path='docs/doc[@for="Thread.VolatileWrite12"]/*' />
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
public static extern void VolatileWrite(ref Object address, Object value);
|
|
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
internal static unsafe extern void VolatileWriteUnsafe(int* address,
|
|
int value);
|
|
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
internal static unsafe extern void VolatileWriteUnsafe(short* address,
|
|
short value);
|
|
|
|
[Intrinsic]
|
|
[NoHeapAllocation]
|
|
internal static unsafe extern void VolatileWriteUnsafe(byte* address,
|
|
byte value);
|
|
|
|
//| <include path='docs/doc[@for="Thread.MemoryBarrier"]/*' />
|
|
[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.threadRecord.spill.StackPointer,
|
|
context.threadRecord.spill.FramePointer,
|
|
context.threadRecord.spill.InstructionPointer,
|
|
Kernel.AddressOf(context.thread),
|
|
context.threadRecord.spill.Flags,
|
|
(UIntPtr)context.prev,
|
|
(UIntPtr)context.next));
|
|
}
|
|
}
|
|
|
|
[NoHeapAllocation]
|
|
internal static unsafe void TraceAbbrev(ref ThreadContext context, String s)
|
|
{
|
|
fixed (ThreadContext* pContext = &context)
|
|
{
|
|
Tracing.Log(Tracing.Debug, s);
|
|
context.threadRecord.spill.Trace();
|
|
}
|
|
}
|
|
|
|
[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));
|
|
|
|
context.threadRecord.spill.Display();
|
|
|
|
}
|
|
|
|
///////////////////////////////////////////////// Blocking operatings.
|
|
//
|
|
internal int WaitAny(WaitHandle[] waitHandles,
|
|
int waitHandlesCount,
|
|
TimeSpan timeout)
|
|
{
|
|
|
|
SchedulerTime stop;
|
|
|
|
if (timeout != TimeSpan.MaxValue) {
|
|
stop = SchedulerTime.Now + timeout;
|
|
}
|
|
else {
|
|
stop = SchedulerTime.MaxValue;
|
|
}
|
|
return WaitHandle.WaitAny(waitHandles, waitHandlesCount, stop);
|
|
}
|
|
|
|
internal int WaitAny(WaitHandle[] waitHandles,
|
|
int waitHandlesCount,
|
|
SchedulerTime stop)
|
|
{
|
|
return WaitHandle.WaitAny(waitHandles, waitHandlesCount, stop);
|
|
}
|
|
|
|
[NoHeapAllocation]
|
|
public static void Yield()
|
|
{
|
|
Kernel.TheScheduler.OnThreadYield(CurrentThread);
|
|
}
|
|
|
|
[NoHeapAllocation]
|
|
public bool IsWaiting()
|
|
{
|
|
return IsBlocked;
|
|
}
|
|
|
|
[NoHeapAllocation]
|
|
public bool IsStopped()
|
|
{
|
|
return (this.schedulerInfo.State == ThreadState.Stopped);
|
|
}
|
|
|
|
[NoHeapAllocation]
|
|
public bool IsStopping()
|
|
{
|
|
return IsStopped();
|
|
}
|
|
|
|
[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.schedulerInfo.State = ThreadState.Running;
|
|
initialThread.SetKernelMode();
|
|
initialThread.threadIndex = 0;
|
|
|
|
// Allocate tables for thread management
|
|
threadTableLock = new SpinLock(SpinLock.Types.ThreadTable);
|
|
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);
|
|
|
|
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.timerEntry = new ThreadEntry(initialThread);
|
|
initialThread.deferredEntry= new ThreadEntry(initialThread);
|
|
initialThread.GetWaitEntries(1); // Cache allows wait without alloc
|
|
Transitions.RuntimeInitialized();
|
|
Transitions.ThreadStart();
|
|
|
|
// Instantiate the static variable that needs to be initialized
|
|
localDataStoreMgr = new LocalDataStoreMgr();
|
|
|
|
processStopException = new ProcessStopException();
|
|
}
|
|
|
|
/// <summary> Prepares a new Thread to take on role as kernel thread
|
|
/// for upcoming processor. Called by Bootstrap processor. </summary>
|
|
public static Thread PrepareKernelThread(Processor p)
|
|
{
|
|
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.schedulerInfo.State = 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,
|
|
Isa.GetStackPointer());
|
|
}
|
|
|
|
#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 multiplyer = 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(ReferenceVisitor 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; i < cache.Length; i++) {
|
|
cache[i] = null;
|
|
}
|
|
selectObjectsStack.Push(cache);
|
|
}
|
|
|
|
public SyncHandle[] PopSelectSyncHandles(int size)
|
|
{
|
|
if (selectSyncHandlesStack == null) {
|
|
selectSyncHandlesStack = new Stack();
|
|
}
|
|
if (selectSyncHandlesStack.Count == 0) {
|
|
return new SyncHandle [size];
|
|
}
|
|
SyncHandle[] selectSyncHandles = (SyncHandle[])selectSyncHandlesStack.Pop();
|
|
if (selectSyncHandles.Length < size) {
|
|
return new SyncHandle [size];
|
|
}
|
|
return selectSyncHandles;
|
|
}
|
|
public void PushSelectSyncHandles(SyncHandle[] cache) {
|
|
for (int i = 0; i < cache.Length; i++) {
|
|
cache[i] = new SyncHandle();
|
|
}
|
|
selectSyncHandlesStack.Push(cache);
|
|
}
|
|
|
|
/// <summary> </summary>
|
|
public SchedulerTime BlockedUntil
|
|
{
|
|
[NoHeapAllocation]
|
|
get { return blockedUntil; }
|
|
}
|
|
|
|
// Given a frame's range in memory (its esp/ebp), check whether
|
|
// the frame contains the top transition record. If so,
|
|
// prepare to skip over a process's frames.
|
|
[AccessedByRuntime("referenced from halasm.asm")]
|
|
[NoStackLinkCheckTrans] // We don't want to throw an exception here;
|
|
// Therefore, we cannot risk allocating stack segments,
|
|
// and we should only call other NoStackLinkCheck functions (XXX).
|
|
internal static unsafe UIntPtr CheckKernelProcessBoundary(UIntPtr esp, UIntPtr ebp, Exception exn)
|
|
{
|
|
ThreadContext *context = Processor.GetCurrentThreadContext();
|
|
System.GCs.CallStack.TransitionRecord *topMarker = context->processMarkers;
|
|
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.
|
|
|
|
// NOTE: Removing the 'if'. This
|
|
// will be removed soon, and the 'is' here
|
|
// results in an unbreakable cycle.
|
|
//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();
|
|
|
|
try {
|
|
context->processMarkers = context->processMarkers->oldTransitionRecord;
|
|
context->stackMarkers = context->stackMarkers->oldTransitionRecord;
|
|
context->SetKernelMode();
|
|
}
|
|
finally {
|
|
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")]
|
|
[NoStackLinkCheckTrans]
|
|
internal static unsafe void DiscardSkippedStackSegments(
|
|
System.GCs.CallStack.TransitionRecord *marker,
|
|
System.GCs.CallStack.TransitionRecord *oldMarker)
|
|
{
|
|
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 = oldMarker;
|
|
|
|
// Update stack limit.
|
|
context->threadRecord.activeStackLimit = context->stackLimit;
|
|
}
|
|
|
|
// 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
|
|
|
|
///
|
|
/// <summary>
|
|
/// Prepare thread for blocking - initialize UnblockedBy state
|
|
/// </summary>
|
|
///
|
|
[NoHeapAllocation]
|
|
public int PrepareForBlocking()
|
|
{
|
|
SchedulerInfo newInfo;
|
|
SchedulerInfo oldInfo;
|
|
int prevUnblockedBy;
|
|
|
|
do {
|
|
// Copy scheduler state to memory
|
|
newInfo = this.schedulerInfo;
|
|
oldInfo = newInfo;
|
|
|
|
// Initialize unblocked by - this is all we pretty much going to update
|
|
newInfo.UnblockedBy = WaitHandle.UninitWait;
|
|
|
|
// Remember who unblocked us before
|
|
prevUnblockedBy = oldInfo.UnblockedBy;
|
|
|
|
// Attempt to update atomicatlly the state.
|
|
} while (oldInfo.Data != Interlocked.CompareExchange(ref this.schedulerInfo.Data,
|
|
newInfo.Data,
|
|
oldInfo.Data));
|
|
return prevUnblockedBy;
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Finish blocking - only changes blocking state if UnblockedBy is uninitialized
|
|
/// </summary>
|
|
///
|
|
/// <param name="unblockedBy">
|
|
/// Id of waithandle that is attempting to unblock thread
|
|
/// </param>
|
|
///
|
|
[NoHeapAllocation]
|
|
public int Unblock(int unblockedBy)
|
|
{
|
|
SchedulerInfo newInfo;
|
|
SchedulerInfo oldInfo;
|
|
int result;
|
|
|
|
do {
|
|
// Copy scheduler state to memory
|
|
newInfo = this.schedulerInfo;
|
|
oldInfo = newInfo;
|
|
|
|
// We can't unblock stopped and unstarted thread
|
|
if ((oldInfo.State & (ThreadState.Stopped | ThreadState.Unstarted)) != 0) {
|
|
result = WaitHandle.UninitWait;
|
|
break;
|
|
}
|
|
|
|
// If nobody unblocked thread yet proceed otherwise we can return right away
|
|
if (oldInfo.UnblockedBy == WaitHandle.UninitWait) {
|
|
|
|
// Remember unblocked information
|
|
newInfo.UnblockedBy = unblockedBy;
|
|
|
|
// Return unblcoked information
|
|
result = unblockedBy;
|
|
}
|
|
else {
|
|
// Remember who unblocked thread
|
|
result = oldInfo.UnblockedBy;
|
|
|
|
// Now we are ready to return;
|
|
break;
|
|
}
|
|
// Attempt to update atomicatlly the state.
|
|
} while (oldInfo.Data != Interlocked.CompareExchange(ref this.schedulerInfo.Data,
|
|
newInfo.Data,
|
|
oldInfo.Data));
|
|
return result;
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Block thread - only changes blocking state if UnblockedBy is uninitialized
|
|
/// </summary>
|
|
///
|
|
[NoHeapAllocation]
|
|
public int BlockThread()
|
|
{
|
|
SchedulerInfo newInfo;
|
|
SchedulerInfo oldInfo;
|
|
int result;
|
|
|
|
do {
|
|
// Copy scheduler state to memory
|
|
newInfo = this.schedulerInfo;
|
|
oldInfo = newInfo;
|
|
|
|
// If nobody unblocked thread yet proceed otherwise we can return right a way
|
|
if (oldInfo.UnblockedBy == WaitHandle.UninitWait) {
|
|
|
|
// Mark thread as blocked
|
|
newInfo.State = ThreadState.Blocked;
|
|
result = WaitHandle.UninitWait;
|
|
}
|
|
else {
|
|
// Remember who unblocked thread
|
|
result = oldInfo.UnblockedBy;
|
|
|
|
// Now we are ready to return;
|
|
break;
|
|
}
|
|
// Attempt to update atomicatlly the state.
|
|
} while (oldInfo.Data != Interlocked.CompareExchange(ref this.schedulerInfo.Data,
|
|
newInfo.Data,
|
|
oldInfo.Data));
|
|
return result;
|
|
}
|
|
|
|
|
|
///
|
|
/// <summary>
|
|
/// Increment thread's freeze counter
|
|
/// </summary>
|
|
///
|
|
[NoHeapAllocation]
|
|
public void IncrementFreezeCounter()
|
|
{
|
|
SchedulerInfo newInfo;
|
|
SchedulerInfo oldInfo;
|
|
int result;
|
|
|
|
do {
|
|
// Copy scheduler state to memory
|
|
newInfo = this.schedulerInfo;
|
|
oldInfo = newInfo;
|
|
|
|
// Freeze counter can't be negative
|
|
VTable.Assert(newInfo.FreezeCount >= 0);
|
|
|
|
// Increment freeze counter
|
|
newInfo.FreezeCount++;
|
|
|
|
} while (oldInfo.Data != Interlocked.CompareExchange(ref this.schedulerInfo.Data,
|
|
newInfo.Data,
|
|
oldInfo.Data));
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Decrement thread's freeze counter, if thread was really suspended meaing
|
|
/// its state was marked suspended by scheduler, return this information so that
|
|
/// </summary>
|
|
///
|
|
/// <param name="shouldPutOnRunnableQueue">
|
|
/// Indicates if caller has to put thread on a runnable queue
|
|
/// </param>
|
|
///
|
|
[NoHeapAllocation]
|
|
public int DecrementFreezeCounter(ref bool shouldPutOnRunnableQueue)
|
|
{
|
|
SchedulerInfo newInfo;
|
|
SchedulerInfo oldInfo;
|
|
int result;
|
|
|
|
do {
|
|
// By default caller shouldn't be putting this thread on runnable queue
|
|
shouldPutOnRunnableQueue = false;
|
|
|
|
// Copy scheduler state to memory
|
|
newInfo = this.schedulerInfo;
|
|
oldInfo = newInfo;
|
|
|
|
// Decrement freeze counter
|
|
newInfo.FreezeCount--;
|
|
|
|
// If thread is really suspended mark it as runnable - caller will put it on runnable
|
|
// queue where it will change its state to ether runnable, blocked or suspended
|
|
if (newInfo.FreezeCount == 0 &&
|
|
oldInfo.State == ThreadState.Suspended) {
|
|
|
|
// If we succeede with state change, caller will need to put thread on a
|
|
// runnable queue
|
|
shouldPutOnRunnableQueue = true;
|
|
}
|
|
|
|
// Freeze counter can't be negative
|
|
VTable.Assert(newInfo.FreezeCount >= 0);
|
|
|
|
} while (oldInfo.Data != Interlocked.CompareExchange(ref this.schedulerInfo.Data,
|
|
newInfo.Data,
|
|
oldInfo.Data));
|
|
|
|
// Assert post conditions: If caller has to put thread on runnable queue then counter
|
|
// had to be 1
|
|
VTable.Assert(!shouldPutOnRunnableQueue || oldInfo.FreezeCount == 1);
|
|
|
|
return oldInfo.FreezeCount -1;
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Verifies if caller has to call scheduler unblock - this will only required if thread is
|
|
/// in blocked state, was unblocked by caller and blocking version is off by one
|
|
/// </summary>
|
|
///
|
|
/// <param name="unblocker">Id of handle unblocker that is attempting to unblock thread</param>
|
|
///
|
|
[NoHeapAllocation]
|
|
public bool ShouldCallSchedulerUnBlock(int unblocker)
|
|
{
|
|
return (this.schedulerInfo.State == ThreadState.Blocked &&
|
|
this.schedulerInfo.UnblockedBy == unblocker);
|
|
}
|
|
|
|
|
|
///
|
|
/// <summary>
|
|
/// Given complete thread's scheduler information derive scheduler state
|
|
/// </summary>
|
|
///
|
|
/// <param name="threadSchedulerInfo">Given scheduler atomic info to use for derivation</param>
|
|
///
|
|
/// <remark> Scheduler info is complex state that consist of three states: Sceduler state
|
|
/// unblocked by information and freeze count. We need to examine all three peices
|
|
/// of information as well current thread state to derive real scheduelr state
|
|
///</remark>
|
|
///
|
|
[NoHeapAllocation]
|
|
public static ThreadState DeriveCurrentSchedulerState(SchedulerInfo threadSchedulerInfo)
|
|
{
|
|
// Retrieve state
|
|
ThreadState actualState = threadSchedulerInfo.State;
|
|
|
|
// If thread is runnable and it is frozen use suspend state otherwise use actual state
|
|
if (actualState == ThreadState.Runnable &&
|
|
threadSchedulerInfo.FreezeCount > 0) {
|
|
|
|
actualState = ThreadState.Suspended;
|
|
}
|
|
|
|
// If thread was unblocked but is marked as blocked it is no longer blocked - it is runnable
|
|
if (threadSchedulerInfo.UnblockedBy != WaitHandle.UninitWait &&
|
|
actualState == ThreadState.Blocked) {
|
|
|
|
actualState = ThreadState.Runnable;
|
|
}
|
|
|
|
return actualState;
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Derive new scheduler state
|
|
/// </summary>
|
|
///
|
|
/// <param name="threadSchedulerInfo">Given scheduler atomic info to use for derivation</param>
|
|
/// <param name="schedulingAction">scheduling action thread is performing: for now maps to states</param>
|
|
///
|
|
/// <remark> Scheduler info is complex state that consist of three states: Scheduler state
|
|
/// unblocked by information and freeze count. We need to examine all three peices
|
|
/// of information as well current thread state to derive real scheduler state
|
|
///</remark>
|
|
///
|
|
[NoHeapAllocation]
|
|
public static ThreadState DeriveNewSchedulerState(
|
|
SchedulerInfo threadSchedulerInfo,
|
|
ThreadState schedulingAction)
|
|
{
|
|
ThreadState oldState;
|
|
ThreadState newState;
|
|
|
|
// First derive old schduler state
|
|
oldState = DeriveCurrentSchedulerState(threadSchedulerInfo);
|
|
|
|
// Base on the current state and scheduling action calculate new state
|
|
switch (oldState) {
|
|
case ThreadState.Stopped: {
|
|
// When thread is stopped any of scheduling action on it is forbidden
|
|
VTable.Assert(false);
|
|
break;
|
|
}
|
|
case ThreadState.Unstarted: {
|
|
// Only two actions are allowed in this situation eiterh make thread runnable or
|
|
// stop
|
|
VTable.Assert(schedulingAction == ThreadState.Runnable ||
|
|
schedulingAction == ThreadState.Stopped);
|
|
|
|
threadSchedulerInfo.State =schedulingAction;
|
|
break;
|
|
}
|
|
case ThreadState.Runnable: {
|
|
// When thread is runnable it is normal for scheduler to attempt it to mark
|
|
// runnable again - it could happen due to complex runnable state (unitwait + blocked)
|
|
// Two other actions can be apllied suspended and running
|
|
// any other action should be assumed incorrect
|
|
VTable.Assert(schedulingAction == ThreadState.Suspended ||
|
|
schedulingAction == ThreadState.Running ||
|
|
schedulingAction == ThreadState.Runnable);
|
|
|
|
threadSchedulerInfo.State = schedulingAction;
|
|
|
|
break;
|
|
}
|
|
case ThreadState.Running: {
|
|
// From this state we can go to blocked, runnable, stopped no other transition is
|
|
// possible
|
|
VTable.Assert(schedulingAction == ThreadState.Blocked ||
|
|
schedulingAction == ThreadState.Runnable ||
|
|
schedulingAction == ThreadState.Stopped);
|
|
|
|
threadSchedulerInfo.State = schedulingAction;
|
|
break;
|
|
}
|
|
case ThreadState.Blocked: {
|
|
// From this state we can go to runnable only no other transition is
|
|
// possible
|
|
VTable.Assert(schedulingAction == ThreadState.Runnable);
|
|
|
|
threadSchedulerInfo.State = schedulingAction;
|
|
break;
|
|
}
|
|
case ThreadState.Suspended: {
|
|
// From this state we can go to runnable however someone might attempt to
|
|
// make us rinnning - we need to ignore it!
|
|
VTable.Assert(schedulingAction == ThreadState.Runnable ||
|
|
schedulingAction == ThreadState.Running);
|
|
|
|
// Though we can recieve runnng signal we can't go to running by passing
|
|
// runnable
|
|
if (schedulingAction == ThreadState.Runnable) {
|
|
threadSchedulerInfo.State = schedulingAction;
|
|
}
|
|
else {
|
|
// Otherwise we have to stay suspendedcd .
|
|
threadSchedulerInfo.State = ThreadState.Suspended;
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
// We shouldn't get here ever:
|
|
VTable.Assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Now lets derive our new state
|
|
return DeriveCurrentSchedulerState(threadSchedulerInfo);
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Set new scheduler state on thread
|
|
/// </summary>
|
|
///
|
|
/// <param name="schedulerAction">Scheduler action</param>
|
|
///
|
|
[NoHeapAllocation]
|
|
internal ThreadState ChangeSchedulerState(ThreadState schedulerAction)
|
|
{
|
|
Thread.SchedulerInfo oldSchedulerInfo;
|
|
Thread.SchedulerInfo newSchedulerInfo;
|
|
|
|
do {
|
|
// Retrieve thread's complex scheduling state
|
|
oldSchedulerInfo = this.ThreadSchedulerInfo;
|
|
newSchedulerInfo = this.ThreadSchedulerInfo;
|
|
|
|
// Map thread's state to Scheduler state and derive thread's new state
|
|
newSchedulerInfo.State = DeriveNewSchedulerState(oldSchedulerInfo, schedulerAction);
|
|
|
|
// We calculated new state try change thread's new state.
|
|
} while (!TryChangeSchedulerState(newSchedulerInfo.State, oldSchedulerInfo));
|
|
|
|
return newSchedulerInfo.State;
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Try to change scheduler state to a new state. New state usually is derived from
|
|
/// calling DeriveSchedulerState method. For more info see comments to that method
|
|
/// </summary>
|
|
///
|
|
/// <param name="newState">New scheduling state</param>
|
|
/// <param name="previousInfo">Scheduling information base on which we derived new state</param>
|
|
///
|
|
[NoHeapAllocation]
|
|
private bool TryChangeSchedulerState(
|
|
ThreadState newState,
|
|
SchedulerInfo previousInfo)
|
|
{
|
|
bool didStateChange = true;
|
|
SchedulerInfo newInfo = previousInfo;
|
|
|
|
// Update scheduler state in a new atomic info
|
|
newInfo.State = newState;
|
|
|
|
//Try to update the state and check if we were succesful
|
|
didStateChange = previousInfo.Data == Interlocked.CompareExchange(ref this.schedulerInfo.Data,
|
|
newInfo.Data,
|
|
previousInfo.Data);
|
|
return didStateChange;
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Wait for reschedule - wait until thread is allowed to be running. This method
|
|
/// should be exclusively used by processor dispatcher
|
|
/// </summary>
|
|
///
|
|
[NoHeapAllocation]
|
|
internal void WaitUntilReadyForContextSwitch()
|
|
{
|
|
while (this.insideOfContextSwitchDepth > 0) {
|
|
// Loop until thread is inside of context switch
|
|
// use SpinWait intrinsic to optimize for hyperthreaded processors.
|
|
Thread.SpinWait(1);
|
|
}
|
|
return;
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Turn on thread's state inside of context switch
|
|
/// </summary>
|
|
///
|
|
[NoHeapAllocation]
|
|
internal void TurnOnInsideOfContextSwitch()
|
|
{
|
|
// Assert preconditions: Contex Switch Depth can't be negative
|
|
VTable.Assert(this.insideOfContextSwitchDepth ==0);
|
|
|
|
this.insideOfContextSwitchDepth++;
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Turn off thread's state inside of context switch
|
|
/// </summary>
|
|
///
|
|
[NoHeapAllocation]
|
|
internal void TurnOffInsideOfContextSwitch()
|
|
{
|
|
// Assert preconditions: Contex Switch Depth can't be 0
|
|
VTable.Assert(this.insideOfContextSwitchDepth == 1);
|
|
|
|
this.insideOfContextSwitchDepth--;
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Find out if we inside of context switch
|
|
/// </summary>
|
|
///
|
|
[NoHeapAllocation]
|
|
internal bool IsInsideOfContextSwitch()
|
|
{
|
|
return this.insideOfContextSwitchDepth > 0;
|
|
}
|
|
|
|
|
|
///
|
|
/// <summary>
|
|
/// Scheduler specific information. Includes scheduler state, wait version and
|
|
/// unblocked by information
|
|
/// </summary>
|
|
///
|
|
[StructAlign(8)]
|
|
public struct SchedulerInfo
|
|
{
|
|
// NOTE:
|
|
// 1. Ideally we can use Interlocked operations on a 64-bit struct, then we
|
|
// don't need to use bit masks and shifts. But Bartok doesn't support
|
|
// it at the moment.
|
|
// 2. Another way is to use StructLayout to emulate union, but we have both
|
|
// performance issues in Bartok generated StructCopy and a Bartok bug
|
|
// that sometimes optimizes away useful code.
|
|
// 3. There are different opinions about whether it's better to define
|
|
// constants for the masks and shifts, we'll re-evaluate.
|
|
|
|
///
|
|
/// <summary>
|
|
/// This separated into 5 different fields. We use a single 64-bit integer so that
|
|
/// we can use Interlocked operations on this struct. The layout is:
|
|
///
|
|
/// State : byte : byte 0 : mask 00000000000000FF
|
|
/// DelayAbortCount : byte : bit 0-6 of byte 1 : mask 0000000000007F00
|
|
/// IsAborted : bool : bit 7 of byte 1 : mask 0000000000008000
|
|
/// FreezeCount : UInt16: byte 2-3 : mask 00000000FFFF0000
|
|
/// UnblockedBy : Int32 : byte 4-7 : mask FFFFFFFF00000000
|
|
/// </summary>
|
|
///
|
|
public UInt64 Data;
|
|
|
|
///
|
|
/// <summary>
|
|
/// State of a thread with respect to scheduler: Runable, Running and etc...
|
|
/// </summary>
|
|
///
|
|
public ThreadState State
|
|
{
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
get
|
|
{
|
|
// State is byte 0 of the data
|
|
return (ThreadState)(byte)Data;
|
|
}
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
set
|
|
{
|
|
// State is byte 0 of the data
|
|
Data &= 0xFFFFFFFFFFFFFF00UL;
|
|
Data |= (byte)value;
|
|
}
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Reference counter of delay abort.
|
|
/// Thread is not allowed to die with non zero DelayAbortCount.
|
|
/// </summary>
|
|
///
|
|
public byte DelayAbortCount
|
|
{
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
get
|
|
{
|
|
// AbortState is bit 0-6 of byte 1 of the data
|
|
return (byte)(((UInt32)Data >> 8) & 0x7F);
|
|
}
|
|
}
|
|
|
|
|
|
///
|
|
/// <summary>
|
|
/// Increment the delay abort count.
|
|
/// Thread is not allowed to die with non zero DelayAbortCount.
|
|
/// </summary>
|
|
///
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
public void IncrementDelayAbortCount()
|
|
{
|
|
// AbortState is bit 0-6 of byte 1 of the data
|
|
// It must not overflow above 7 bits (0x7F)
|
|
VTable.Assert(((UInt32)Data & 0x7F00) != 0x7F00);
|
|
Data += 0x100;
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Decrement the delay abort count.
|
|
/// Thread is not allowed to die with non zero DelayAbortCount.
|
|
/// </summary>
|
|
///
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
public void DecrementDelayAbortCount()
|
|
{
|
|
// AbortState is bit 0-6 of byte 1 of the data
|
|
// It must not underflow below 0
|
|
VTable.Assert(((UInt32)Data & 0x7F00) != 0);
|
|
Data -= 0x100;
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Get or set whether the thread is aborted
|
|
/// </summary>
|
|
///
|
|
public bool IsAborted
|
|
{
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
get
|
|
{
|
|
// IsAborted is bit 7 of byte 1 of the data
|
|
return ((UInt32)Data & 0x8000) != 0;
|
|
}
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
set
|
|
{
|
|
// IsAborted is bit 7 of byte 1 of the data
|
|
if (value) {
|
|
Data |= 0x0000000000008000;
|
|
}
|
|
else {
|
|
Data &= 0xFFFFFFFFFFFF7FFFUL;
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// A reference counter of how many times the thread is suspended. Threads
|
|
/// with non zero FreezeCount are not allowed to run.
|
|
/// </summary>
|
|
///
|
|
public UInt16 FreezeCount
|
|
{
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
get
|
|
{
|
|
// AbortState is byte 2 and 3 of the data
|
|
return (UInt16)(Data >> 16);
|
|
}
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
set
|
|
{
|
|
// AbortState is byte 2 and 3 of the data
|
|
Data &= 0xFFFFFFFF0000FFFFUL;
|
|
Data |= (((UInt32)value) << 16);
|
|
}
|
|
}
|
|
|
|
///
|
|
/// <summary>
|
|
/// Filed represents an id of WaitHandle that performed unblock operation
|
|
/// </summary>
|
|
///
|
|
public int UnblockedBy
|
|
{
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
get
|
|
{
|
|
// UnblockedBy is byte 4 to 7 of the data
|
|
return (int)(UInt32)(Data >> 32);
|
|
}
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
set
|
|
{
|
|
// UnblockedBy is byte 4 to 7 of the data
|
|
Data &= 0x00000000FFFFFFFFUL;
|
|
Data |= (((UInt64)(UInt32)value) << 32);
|
|
}
|
|
}
|
|
};
|
|
|
|
///
|
|
/// <summary>
|
|
/// Thread type
|
|
/// </summary>
|
|
///
|
|
public enum ThreadType
|
|
{
|
|
Unknown,
|
|
Idle,
|
|
Scavenger
|
|
};
|
|
|
|
#if PAGING
|
|
/// <summary> For use when we temporarily switch to a different domain </summary>
|
|
private ProtectionDomain tempDomain;
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// This manager is responsible for storing the global data that is shared amongst all
|
|
/// the thread local stores.
|
|
/// </summary>
|
|
static private LocalDataStoreMgr localDataStoreMgr;
|
|
|
|
/// <summary> A maximum number of threads , must be power of 2 >=64</summary>
|
|
internal const int maxThreads = 1024;
|
|
|
|
internal const int NoAffinity = -1;
|
|
|
|
/// <summary> A global counter to generate next thread index </summary>
|
|
private static int threadIndexGenerator;
|
|
|
|
/// <summary> Thread is inside of context switch </summary>
|
|
[AccessedByRuntime("referenced from halidt.asm")]
|
|
internal int insideOfContextSwitchDepth = 0;
|
|
|
|
/// <summary> MultiUseWord (object header) head</summary>
|
|
internal UIntPtr externalMultiUseObjAllocListHead;
|
|
|
|
/// <summary>MultiUseWord (object header) tail </summary>
|
|
internal UIntPtr externalMultiUseObjAllocListTail;
|
|
|
|
/// <summary> Thread index inside of a prcess </summary>
|
|
internal int processThreadIndex;
|
|
|
|
/// <summary> Global thread index </summary>
|
|
internal int threadIndex;
|
|
|
|
/// <summary> A method to start a thread</summary>
|
|
private ThreadStart threadStart;
|
|
|
|
/// <summary> A state of a thread from scheduler point of view </summary>
|
|
private SchedulerInfo schedulerInfo;
|
|
|
|
/// <summary> Previous scheduler info as seen by record event, used for debugging purposes</summary>
|
|
private SchedulerInfo prevSchedulerInfo;
|
|
|
|
/// <summary> Thread state from dispatcher point of view </summary>
|
|
private ProcessorDispatcher dispatcher;
|
|
|
|
/// <summary> Indicates if a thread is executing in a nonPreemptible region </summary>
|
|
private int nonPreemptibleRegionCount;
|
|
|
|
/// <summary> Thread's event used by monitor </summary>
|
|
private AutoResetEvent autoEvent;
|
|
|
|
/// <summary> Indicates if thread's gc event has been signaled </summary>
|
|
private int gcEventSignaled;
|
|
|
|
/// <summary>Thread's join event</summary>
|
|
private ManualResetEvent joinEvent;
|
|
|
|
/// <summary> This is needed for Bartok </summary>
|
|
internal Thread blockingCctorThread;
|
|
|
|
/// <summary>Preallocated services request object</summary>
|
|
internal ThreadLocalServiceRequest localServiceRequest;
|
|
|
|
/// <summary>A timer indicating till when thread is blocked</summary>
|
|
[AccessedByRuntime("referenced from c++")]
|
|
internal SchedulerTime blockedUntil;
|
|
|
|
/// <summary>An entry used by scheduler queues to manipulate wit thread</summary>
|
|
[AccessedByRuntime("referenced from c++")]
|
|
internal ThreadEntry schedulerEntry;
|
|
|
|
/// <summary>An entry used by timer quieue to manipulate with thread</summary>
|
|
internal ThreadEntry timerEntry;
|
|
|
|
/// <summary>An entry used by wait handle to put thread on deferred queue during wakeup</summary>
|
|
internal ThreadEntry deferredEntry;
|
|
|
|
/// <summary>Thread context</summary>
|
|
[AccessedByRuntime("referenced from c++")]
|
|
internal ThreadContext context;
|
|
|
|
/// <summary>Thread's process</summary>
|
|
[AccessedByRuntime("referenced from c++")]
|
|
internal Process process;
|
|
|
|
/// <summary>Thread's handle</summary>
|
|
internal ThreadHandle threadHandle;
|
|
|
|
/// <summary>Thread local value - single place for local storage</summary>
|
|
internal UIntPtr threadLocalValue;
|
|
|
|
/// <summary>
|
|
/// 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).
|
|
/// </summary>
|
|
internal Exception lastUncaughtException;
|
|
|
|
/// <summary>
|
|
/// Monitor link list of threads. Remove these and Monitor as soon as
|
|
/// stack is out of kernel.
|
|
/// </summary>
|
|
internal Thread nextThread;
|
|
|
|
/// <summary> </summary>
|
|
private Object exceptionStateInfo;
|
|
|
|
/// <summary>Global thread table</summary>
|
|
internal static Thread[] threadTable;
|
|
|
|
/// <summary>A lock protecting access to global trhead table</summary>
|
|
private static SpinLock threadTableLock;
|
|
|
|
/// <summary>Thread local storage</summary>
|
|
private static LocalDataStore localDataStore;
|
|
|
|
/// <summary>First thread in Singularity</summary>
|
|
internal static Thread initialThread;
|
|
|
|
/// <summary>Spinlock protecting thread state</summary>
|
|
private SpinLock threadLock;
|
|
|
|
/// <summary>An exception object to stop process </summary>
|
|
private static ProcessStopException
|
|
processStopException;
|
|
|
|
/// <summary> The processor ID this thread is running on or ran on last time </summary>
|
|
internal int Affinity = NoAffinity;
|
|
|
|
#if false
|
|
/// <summary> scheduler specific data </summary>
|
|
internal ThreadScheduleData ScheduleData;
|
|
#endif
|
|
|
|
/// <summary> Thread type </summary>
|
|
internal ThreadType type = ThreadType.Unknown;
|
|
|
|
/// <summary> SpinLock ranking masks </summary>
|
|
internal int spinLockRankMask;
|
|
|
|
/// <summary>A number of spinlocks held by a thread</summary>
|
|
internal int numberOfSpinlocksHeld = 0;
|
|
}
|
|
}
|
|
|