singrdk/base/Kernel/System/Threading/Thread.cs

3065 lines
111 KiB
C#
Raw Permalink Normal View History

2008-03-05 09:52:00 -05:00
////////////////////////////////////////////////////////////////////////////////
//
// 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.
2008-11-17 18:29:00 -05:00
// Scheduler.OnThreadFreezeDecrement(): Decr count, if 0 then unfreeze.
2008-03-05 09:52:00 -05:00
//
// Third, the Scheduler calls Thread.Stopped() when it has finish with a
// thread that is no longer runnable.
//
2008-11-17 18:29:00 -05:00
//#define DEBUG_SWITCH
2008-03-05 09:52:00 -05:00
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;
2008-11-17 18:29:00 -05:00
using Microsoft.Singularity.Isal;
2008-03-05 09:52:00 -05:00
using Microsoft.Bartok.Runtime;
[CLSCompliant(false)]
public enum ThreadEvent : ushort
{
CreateIdle = 12,
Create = 13,
WaitAny = 30,
WaitFail = 31,
SwitchTo = 3,
ThreadPackageInit = 10
}
2008-11-17 18:29:00 -05:00
internal enum AbortRequestSource
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
ABIExit = 0,
ABINoGCExit = 1,
WaitHandle = 2,
}
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
///
/// <summary>
/// Class implements thread functionality in Singluarity
/// </summary>
///
public sealed partial class Thread
{
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
///
/// <summary>
/// Constructor to create a new thread
///</summary>
///
/// <param name="process">A process to which thread belongs to</param>
///
2008-03-05 09:52:00 -05:00
private Thread(Process process)
{
2008-11-17 18:29:00 -05:00
// Initialize thread fields to default values
2008-03-05 09:52:00 -05:00
this.processThreadIndex = -1;
this.threadIndex = -1;
2008-11-17 18:29:00 -05:00
this.schedulerInfo.State = ThreadState.Unstarted;
// Set up thread kernel mode information
2008-03-05 09:52:00 -05:00
this.SetKernelMode();
2008-11-17 18:29:00 -05:00
// Bind thread to a process
2008-03-05 09:52:00 -05:00
this.process = process;
2008-11-17 18:29:00 -05:00
// 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);
2008-03-05 09:52:00 -05:00
// Allocate the kernel objects needed by the thread.
2008-11-17 18:29:00 -05:00
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);
2008-03-05 09:52:00 -05:00
// Try to put the thread in the thread table.
2008-11-17 18:29:00 -05:00
bool saved = Processor.DisableInterrupts();
threadTableLock.Acquire(CurrentThread);
2008-03-05 09:52:00 -05:00
try {
2008-11-17 18:29:00 -05:00
// 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
2008-03-05 09:52:00 -05:00
threadTable[index] = this;
this.threadIndex = index;
2008-11-17 18:29:00 -05:00
// Remember last we visited this place
2008-03-05 09:52:00 -05:00
threadIndexGenerator = index + 1;
2008-11-17 18:29:00 -05:00
2008-03-05 09:52:00 -05:00
// NB: We call this once, subsequently the GC visitor calls it.
context.UpdateAfterGC(this);
break;
2008-11-17 18:29:00 -05:00
}
2008-03-05 09:52:00 -05:00
}
}
finally {
2008-11-17 18:29:00 -05:00
threadTableLock.Release(CurrentThread);
Processor.RestoreInterrupts(saved);
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
// 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.
2008-03-05 09:52:00 -05:00
context.stackBegin = 0;
context.stackLimit = 0;
}
2008-11-17 18:29:00 -05:00
///
/// <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>
///
2008-03-05 09:52:00 -05:00
protected Thread(bool idle)
: this(Process.idleProcess)
{
2008-11-17 18:29:00 -05:00
// Initialize stack
2008-03-05 09:52:00 -05:00
UIntPtr stackSegment = Stacks.GetInitialStackSegment(ref context);
2008-11-17 18:29:00 -05:00
// At this point we can't fail
2008-03-05 09:52:00 -05:00
DebugStub.Assert(stackSegment != UIntPtr.Zero);
2008-11-17 18:29:00 -05:00
2008-03-05 09:52:00 -05:00
#if PAGING
2008-11-17 18:29:00 -05:00
// Initialize idle context
2008-03-05 09:52:00 -05:00
context.InitializeIdle(threadIndex, stackSegment,
unchecked((uint)Process.kernelProcess.Domain.AddressSpace.PdptPage.Value));
#else
2008-11-17 18:29:00 -05:00
// Initialize idle context
2008-03-05 09:52:00 -05:00
context.InitializeIdle(threadIndex, stackSegment, 0);
#endif
2008-11-17 18:29:00 -05:00
// Record ilde thread creation event
2008-03-05 09:52:00 -05:00
Monitoring.Log(Monitoring.Provider.Thread,
(ushort)ThreadEvent.CreateIdle, 0,
(uint)threadIndex, 0, 0, 0, 0);
}
2008-11-17 18:29:00 -05:00
///
/// <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>
///
2008-03-05 09:52:00 -05:00
protected Thread(Process process, ThreadStart start)
: this(process)
{
2008-11-17 18:29:00 -05:00
// If no start routine specified throw exception
2008-03-05 09:52:00 -05:00
if (start == null) {
throw new ArgumentNullException("start");
}
2008-11-17 18:29:00 -05:00
// Initialize thread's start routine
2008-03-05 09:52:00 -05:00
this.threadStart = start;
#if PAGING
2008-11-17 18:29:00 -05:00
// 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;
2008-03-05 09:52:00 -05:00
#endif
2008-11-17 18:29:00 -05:00
// Allocation initial stack segment for the thread
2008-03-05 09:52:00 -05:00
UIntPtr stackSegment = Stacks.GetInitialStackSegment(ref context);
2008-11-17 18:29:00 -05:00
// Assert condition - no stack, no thread - is this really true?
2008-03-05 09:52:00 -05:00
DebugStub.Assert(stackSegment != UIntPtr.Zero);
#if PAGING
2008-11-17 18:29:00 -05:00
// Initialize context
2008-03-05 09:52:00 -05:00
context.Initialize(threadIndex, stackSegment,
unchecked((uint)(Process.Domain.AddressSpace.PdptPage.Value)));
#else
2008-11-17 18:29:00 -05:00
// Initialize context
2008-03-05 09:52:00 -05:00
context.Initialize(threadIndex, stackSegment, 0);
#endif
Monitoring.Log(Monitoring.Provider.Thread,
(ushort)ThreadEvent.Create, 0,
(uint)threadIndex, 0, 0, 0, 0);
}
2008-11-17 18:29:00 -05:00
///
/// <summary>
/// An API to create a thread
///</summary>
///
/// <param name="processor">An idle thread for a given processor </param>
///
2008-03-05 09:52:00 -05:00
public static Thread CreateIdleThread(Processor processor)
{
2008-11-17 18:29:00 -05:00
// Allocate new idle thread.
2008-03-05 09:52:00 -05:00
Thread idle = new Thread(true);
2008-11-17 18:29:00 -05:00
idle.Affinity = processor.Id;
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
// Check if new thread is valid
2008-03-05 09:52:00 -05:00
if (idle.threadIndex < 0) {
Tracing.Log(Tracing.Warning, "Thread table is full.");
DebugStub.Break();
return null;
}
return idle;
}
2008-11-17 18:29:00 -05:00
///
/// <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)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
ThreadHandle handle;
// If no process specified use current one
2008-03-05 09:52:00 -05:00
if (process == null) {
process = CurrentProcess;
}
2008-11-17 18:29:00 -05:00
//Process can't be null from this point on
VTable.Assert(process != null);
2008-03-05 09:52:00 -05:00
// Allocate the thread.
Thread thread = new Thread(process, start);
2008-11-17 18:29:00 -05:00
// Check if thread is valid, return right a way if it is not
2008-03-05 09:52:00 -05:00
if (thread.threadIndex < 0) {
Tracing.Log(Tracing.Warning, "Thread table is full.");
DebugStub.Break();
return null;
}
2008-11-17 18:29:00 -05:00
// Add newly created thread to the process
process.AddThread(thread, needThreadHandle);
2008-03-05 09:52:00 -05:00
//MemoryBarrier();?
2008-11-17 18:29:00 -05:00
// Notifiy perfcounters about thread creation
2008-03-05 09:52:00 -05:00
PerfCounters.IncrementThreadsCreated();
2008-11-17 18:29:00 -05:00
DebugStub.AddToPerfCounter(4, 1);
2008-03-05 09:52:00 -05:00
return thread;
}
2008-11-17 18:29:00 -05:00
///
/// <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>
///
2008-03-05 09:52:00 -05:00
public void Start()
{
2008-11-17 18:29:00 -05:00
// Notify process that thread is about to start
process.StartThread();
// Go ahead and start thread - attach it scheduler and start running
2008-03-05 09:52:00 -05:00
StartRunningThread();
}
2008-11-17 18:29:00 -05:00
///
/// <summary>
/// Start running main thread inside of process
/// </summary>
///
///<remark> precondition: process.processLock held </remark>
2008-03-05 09:52:00 -05:00
internal void SetMainThreadRunning()
{
2008-11-17 18:29:00 -05:00
VTable.Assert(this.ThreadState == ThreadState.Unstarted);
process.StartMainThread();
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
///
/// <summary>
/// Start thread running: Attach to GC and process's scheduler
/// </summary>
///
2008-03-05 09:52:00 -05:00
internal void StartRunningThread()
{
2008-11-17 18:29:00 -05:00
VTable.Assert(this.ThreadState == ThreadState.Unstarted);
2008-03-05 09:52:00 -05:00
// Tell the GC that we have created the thread
GC.NewThreadNotification(this, false);
2008-11-17 18:29:00 -05:00
// 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>
///
2008-03-05 09:52:00 -05:00
[AccessedByRuntime("referenced from c++")]
2008-11-17 18:29:00 -05:00
private static unsafe void DispatcherThreadStub(int index)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
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();
2008-03-05 09:52:00 -05:00
}
}
2008-11-17 18:29:00 -05:00
///
/// <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>
///
2008-03-05 09:52:00 -05:00
[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();
2008-11-17 18:29:00 -05:00
// Attach thread to GC
2008-03-05 09:52:00 -05:00
Transitions.ThreadStart();
GC.ThreadStartNotification(threadIndex);
2008-11-17 18:29:00 -05:00
2008-03-05 09:52:00 -05:00
Tracing.Log(Tracing.Trace, "ThreadStub() entered");
ThreadStart startFun = currentThread.threadStart;
2008-11-17 18:29:00 -05:00
// Initialize procesor FPU
Isa.InitFpu();
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
// Start an entry point of actual thread
2008-03-05 09:52:00 -05:00
try {
startFun();
}
catch (ProcessStopException) {
2008-11-17 18:29:00 -05:00
// Stop exception is the only "good" exception expect at this point
2008-03-05 09:52:00 -05:00
// Ok, exit thread without failure.
}
catch (Exception e) {
2008-11-17 18:29:00 -05:00
// Not ok, fail: This is pretty much unhandled exception
2008-03-05 09:52:00 -05:00
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));
2008-11-17 18:29:00 -05:00
// We are done: stop thread and disassociate it from scheduler
Kernel.TheScheduler.OnThreadStop(currentThread);
}
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
///
/// <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;
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
// Indicate all waiters on this thread that we are done
joinEvent.Set();
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
// 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);
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
// If thread has never started set state by hand to stopped
if (wasUnstarted) {
this.schedulerInfo.State = ThreadState.Stopped;
2008-03-05 09:52:00 -05:00
}
}
finally {
2008-11-17 18:29:00 -05:00
this.threadLock.Release();
Processor.RestoreInterrupts(saved);
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
// Asssert preconditions
VTable.Assert(this.ThreadState == ThreadState.Stopped);
2008-03-05 09:52:00 -05:00
VTable.Assert(threadIndex > 0);
2008-11-17 18:29:00 -05:00
// Notify GC that thread is tearing down
if (!wasUnstarted) {
if (Transitions.InMutatorState(GetThreadId())){
Transitions.ThreadEnd(GetThreadId());
}
GC.DeadThreadNotification(this);
}
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
int count = 0;
2008-03-05 09:52:00 -05:00
#if !PAGING
while (context.stackBegin != 0) {
2008-11-17 18:29:00 -05:00
if (count == 0) {
Stacks.ReturnInitialStackSegment(ref context);
}
else {
Stacks.ReturnStackSegment(ref context);
}
count++;
2008-03-05 09:52:00 -05:00
}
#else
2008-11-17 18:29:00 -05:00
// Free up stack
2008-03-05 09:52:00 -05:00
while (context.stackBegin != 0) {
2008-11-17 18:29:00 -05:00
// HACK: if the thread stops abruptly, the stack may contain the abi segment
2008-03-05 09:52:00 -05:00
if (context.stackBegin == context.abiStackBegin) {
context.abiStackBegin = 0;
context.abiStackLimit = 0;
}
2008-11-17 18:29:00 -05:00
if (count == 0) {
// First segment is reported to balance AllocateInitialStackSegment
Stacks.ReturnInitialStackSegment(ref context);
}
else {
Stacks.ReturnStackSegment(ref context);
}
2008-03-05 09:52:00 -05:00
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;
2008-11-17 18:29:00 -05:00
// Free up table index
saved = Processor.DisableInterrupts();
threadTableLock.Acquire(currentThread);
2008-03-05 09:52:00 -05:00
try {
2008-11-17 18:29:00 -05:00
threadTable[threadIndex] = null;
Transitions.DeadThreadNotification(threadIndex);
}
finally {
threadTableLock.Release(currentThread);
Processor.RestoreInterrupts(saved);
2008-03-05 09:52:00 -05:00
}
if (process != null) {
process.ServiceOnThreadStop(this);
}
}
2008-11-17 18:29:00 -05:00
///
/// <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
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
[Inline]
[NoHeapAllocation]
get
{
return (this.ThreadState != ThreadState.Unstarted &&
this.ThreadState != ThreadState.Stopped);
2008-03-05 09:52:00 -05:00
}
}
2008-11-17 18:29:00 -05:00
///
/// <summary>
/// Wait for thread to stop
/// </summary>
///
/// <remark>
/// Exceptions: ThreadStateException if the thread has not been started yet.
/// </remark>
2008-03-05 09:52:00 -05:00
public void Join()
{
Join(SchedulerTime.MaxValue);
}
2008-11-17 18:29:00 -05:00
///
/// <summary>
/// Wait for thread to stop or specified timeout expires
/// </summary>
///
/// <remark>
/// Exceptions: ThreadStateException if the thread has not been started yet.
/// </remark>
2008-03-05 09:52:00 -05:00
public bool Join(TimeSpan timeout)
{
2008-11-17 18:29:00 -05:00
if (this.ThreadState == ThreadState.Unstarted) {
2008-03-05 09:52:00 -05:00
throw new ThreadStateException();
}
2008-11-17 18:29:00 -05:00
else if (this.ThreadState == ThreadState.Stopped) {
2008-03-05 09:52:00 -05:00
return true;
}
return joinEvent.WaitOne(timeout);
}
2008-11-17 18:29:00 -05:00
///
/// <summary>
/// Wait for thread to stop or specified timeout expires
/// </summary>
///
/// <remark>
/// Exceptions: ThreadStateException if the thread has not been started yet.
/// </remark>
2008-03-05 09:52:00 -05:00
public bool Join(SchedulerTime timeout)
{
2008-11-17 18:29:00 -05:00
if (this.ThreadState == ThreadState.Unstarted) {
2008-03-05 09:52:00 -05:00
throw new ThreadStateException();
}
2008-11-17 18:29:00 -05:00
else if (this.ThreadState == ThreadState.Stopped) {
2008-03-05 09:52:00 -05:00
return true;
}
return joinEvent.WaitOne(timeout);
}
2008-11-17 18:29:00 -05:00
///
/// <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;
}
}
2008-03-05 09:52:00 -05:00
//////////////////////////////////////////////////////////////////////
// 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");
2008-11-17 18:29:00 -05:00
WaitHandle.WaitAny(null, 0, stop);
2008-03-05 09:52:00 -05:00
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
2008-11-17 18:29:00 -05:00
// this method is preferable to coding a explicit busy loop because the
2008-03-05 09:52:00 -05:00
// 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);
}
2008-11-17 18:29:00 -05:00
internal bool WaitForEvent(SchedulerTime stop)
{
return autoEvent.WaitOne(stop);
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
internal bool WaitForEvent(TimeSpan timeout)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
return autoEvent.WaitOne(timeout);
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
internal static void WaitForGCEvent(int currentThreadIndex)
{
Thread.WaitForGCEvent(currentThreadIndex, SchedulerTime.MaxValue);
}
internal static void WaitForGCEvent(int currentThreadIndex, int millis)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
SchedulerTime stop = SchedulerTime.Now.AddMilliseconds(millis);
Thread.WaitForGCEvent(currentThreadIndex, stop);
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
///
/// <summary>
/// Signal thread's GC event, we can't use actual event object as it might allow
/// unneccesasry reentrancy.
/// </summary>
///
2008-03-05 09:52:00 -05:00
[Inline]
internal static void SignalGCEvent(int threadIndex) {
SignalGCEvent(Thread.GetCurrentThreadIndex(), threadIndex);
}
2008-11-17 18:29:00 -05:00
///
/// <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)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
Thread currentThread = threadTable[threadIndex];
2008-03-05 09:52:00 -05:00
Tracing.Log(Tracing.Audit, "SignalGCEvent({0})",
(UIntPtr) threadIndex);
2008-11-17 18:29:00 -05:00
VTable.Deny(ProcessorDispatcher.IsIdleThread(threadIndex));
if (currentThread != null) {
Interlocked.Exchange(ref currentThread.gcEventSignaled, 1);
2008-03-05 09:52:00 -05:00
}
}
2008-11-17 18:29:00 -05:00
internal void SignalEvent()
{
this.autoEvent.Set();
}
internal void SignalMonitor()
{
this.autoEvent.Set();
}
2008-03-05 09:52:00 -05:00
//| <include path='docs/doc[@for="Thread.CurrentThread"]/*' />
public static extern Thread CurrentThread
{
[NoStackLinkCheck]
[NoHeapAllocation]
[Intrinsic]
get;
}
public static Process CurrentProcess
{
2008-11-17 18:29:00 -05:00
[NoStackLinkCheckTrans]
2008-03-05 09:52:00 -05:00
[NoHeapAllocation]
get {
return Processor.GetCurrentThread().process;
}
}
public Process Process
{
[NoHeapAllocation]
get { return process; }
}
public ThreadHandle Handle
{
[NoHeapAllocation]
get { return threadHandle; }
}
2008-11-17 18:29:00 -05:00
[NoStackLinkCheckTrans]
2008-03-05 09:52:00 -05:00
[RequiredByBartok]
[NoHeapAllocation]
2008-11-17 18:29:00 -05:00
public static int GetCurrentThreadIndex()
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
return Processor.GetCurrentThread().threadIndex;
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
[NoStackLinkCheckTrans]
2008-03-05 09:52:00 -05:00
[RequiredByBartok]
[NoHeapAllocation]
2008-11-17 18:29:00 -05:00
private static Thread GetCurrentThreadNative()
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
return Processor.GetCurrentThread();
2008-03-05 09:52:00 -05:00
}
[NoHeapAllocation]
public static UIntPtr GetThreadLocalValue()
{
return Processor.GetCurrentThread().threadLocalValue;
}
[NoHeapAllocation]
public static void SetThreadLocalValue(UIntPtr value)
{
Processor.GetCurrentThread().threadLocalValue = value;
}
2008-11-17 18:29:00 -05:00
///
/// <summary>
/// Retrieve thread's state
/// </summary>
///
2008-03-05 09:52:00 -05:00
public ThreadState ThreadState
{
2008-11-17 18:29:00 -05:00
[Inline]
2008-03-05 09:52:00 -05:00
[NoHeapAllocation]
2008-11-17 18:29:00 -05:00
get
{
return schedulerInfo.State;
}
2008-03-05 09:52:00 -05:00
}
[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();
}
2008-11-17 18:29:00 -05:00
///
/// <summary>
/// Suspend thread and wait till it is suspended
/// </summary>
///
internal void Suspend(bool aboutToStop)
{
Kernel.TheScheduler.Suspend(this);
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
///
/// <summary>
/// Resume thread
/// </summary>
///
internal void Resume()
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
Kernel.TheScheduler.Resume(this);
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
///
/// <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)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
// 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);
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
// 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);
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
#endif // false
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
///
/// <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;
2008-03-05 09:52:00 -05:00
}
}
}
2008-11-17 18:29:00 -05:00
// We are done with thread just resume it
if (shouldResumeAtTheEnd) {
Resume();
2008-03-05 09:52:00 -05:00
}
}
2008-11-17 18:29:00 -05:00
///
/// <summary>
/// Move thread abort status to abort state
/// </summary>
///
private void Abort()
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
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;
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
///
/// <summary>
/// Handle Abort - if abort is set and we are not in dealy abort scope throw
/// abort exception
/// </summary>
///
internal void ProcessAbortIfRequired()
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
// 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);
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
return;
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
///
/// <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)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
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();
2008-03-05 09:52:00 -05:00
}
else {
2008-11-17 18:29:00 -05:00
// We are disabling abort
newInfo.DecrementDelayAbortCount();
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
// 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);
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
}
///
/// <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);
2008-03-05 09:52:00 -05:00
}
}
2008-11-17 18:29:00 -05:00
///
/// <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];
}
2008-03-05 09:52:00 -05:00
[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()
{
2008-11-17 18:29:00 -05:00
return localDataStoreMgr.AllocateDataSlot();
2008-03-05 09:52:00 -05:00
}
//////////////////////////////////////////////////////////////////////
// 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)
{
2008-11-17 18:29:00 -05:00
return localDataStoreMgr.AllocateNamedDataSlot(name);
2008-03-05 09:52:00 -05:00
}
//////////////////////////////////////////////////////////////////////
// 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)
{
2008-11-17 18:29:00 -05:00
return localDataStoreMgr.GetNamedDataSlot(name);
2008-03-05 09:52:00 -05:00
}
//////////////////////////////////////////////////////////////////////
// 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)
{
2008-11-17 18:29:00 -05:00
localDataStoreMgr.FreeNamedDataSlot(name);
2008-03-05 09:52:00 -05:00
}
//////////////////////////////////////////////////////////////////////
// Retrieves the value from the specified slot on the current thread.
//| <include path='docs/doc[@for="Thread.GetData"]/*' />
public static Object GetData(LocalDataStoreSlot slot)
{
2008-11-17 18:29:00 -05:00
localDataStoreMgr.ValidateSlot(slot);
2008-03-05 09:52:00 -05:00
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) {
2008-11-17 18:29:00 -05:00
localDataStore = localDataStoreMgr.CreateLocalDataStore();
2008-03-05 09:52:00 -05:00
}
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);
2008-11-17 18:29:00 -05:00
[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);
2008-03-05 09:52:00 -05:00
//| <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);
2008-11-17 18:29:00 -05:00
[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);
2008-03-05 09:52:00 -05:00
//| <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,
2008-11-17 18:29:00 -05:00
context.threadRecord.spill.StackPointer,
context.threadRecord.spill.FramePointer,
context.threadRecord.spill.InstructionPointer,
2008-03-05 09:52:00 -05:00
Kernel.AddressOf(context.thread),
2008-11-17 18:29:00 -05:00
context.threadRecord.spill.Flags,
2008-03-05 09:52:00 -05:00
(UIntPtr)context.prev,
(UIntPtr)context.next));
}
}
2008-11-17 18:29:00 -05:00
[NoHeapAllocation]
internal static unsafe void TraceAbbrev(ref ThreadContext context, String s)
{
fixed (ThreadContext* pContext = &context)
{
Tracing.Log(Tracing.Debug, s);
context.threadRecord.spill.Trace();
}
}
2008-03-05 09:52:00 -05:00
[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));
2008-11-17 18:29:00 -05:00
context.threadRecord.spill.Display();
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
///////////////////////////////////////////////// Blocking operatings.
2008-03-05 09:52:00 -05:00
//
2008-11-17 18:29:00 -05:00
internal int WaitAny(WaitHandle[] waitHandles,
int waitHandlesCount,
TimeSpan timeout)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
SchedulerTime stop;
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
if (timeout != TimeSpan.MaxValue) {
stop = SchedulerTime.Now + timeout;
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
else {
stop = SchedulerTime.MaxValue;
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
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);
2008-03-05 09:52:00 -05:00
}
[NoHeapAllocation]
public bool IsWaiting()
{
2008-11-17 18:29:00 -05:00
return IsBlocked;
2008-03-05 09:52:00 -05:00
}
[NoHeapAllocation]
public bool IsStopped()
{
2008-11-17 18:29:00 -05:00
return (this.schedulerInfo.State == ThreadState.Stopped);
2008-03-05 09:52:00 -05:00
}
[NoHeapAllocation]
public bool IsStopping()
{
2008-11-17 18:29:00 -05:00
return IsStopped();
2008-03-05 09:52:00 -05:00
}
[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)));
2008-11-17 18:29:00 -05:00
initialThread.schedulerInfo.State = ThreadState.Running;
2008-03-05 09:52:00 -05:00
initialThread.SetKernelMode();
initialThread.threadIndex = 0;
// Allocate tables for thread management
2008-11-17 18:29:00 -05:00
threadTableLock = new SpinLock(SpinLock.Types.ThreadTable);
2008-03-05 09:52:00 -05:00
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);
2008-11-17 18:29:00 -05:00
initialThread.timerEntry = new ThreadEntry(initialThread);
initialThread.deferredEntry= new ThreadEntry(initialThread);
2008-03-05 09:52:00 -05:00
initialThread.GetWaitEntries(1); // Cache allows wait without alloc
Transitions.RuntimeInitialized();
Transitions.ThreadStart();
// Instantiate the static variable that needs to be initialized
2008-11-17 18:29:00 -05:00
localDataStoreMgr = new LocalDataStoreMgr();
2008-03-05 09:52:00 -05:00
processStopException = new ProcessStopException();
}
/// <summary> Prepares a new Thread to take on role as kernel thread
/// for upcoming processor. Called by Bootstrap processor. </summary>
2008-11-17 18:29:00 -05:00
public static Thread PrepareKernelThread(Processor p)
2008-03-05 09:52:00 -05:00
{
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);
2008-11-17 18:29:00 -05:00
kernelThread.schedulerInfo.State = ThreadState.Running;
2008-03-05 09:52:00 -05:00
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,
2008-11-17 18:29:00 -05:00
Isa.GetStackPointer());
2008-03-05 09:52:00 -05:00
}
#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
2008-11-17 18:29:00 -05:00
//static private ulong multiplyer = Processor.CyclesPerSecond /
2008-03-05 09:52:00 -05:00
// 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();
2008-11-17 18:29:00 -05:00
try {
if (Processor.GetCurrentThread() == this) {
2008-03-05 09:52:00 -05:00
ulong now = Processor.CycleCount;
context.executionTime += now -
context.lastExecutionTimeUpdate;
LastUpdateTime = now;
}
}
2008-11-17 18:29:00 -05:00
finally {
2008-03-05 09:52:00 -05:00
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();
2008-11-17 18:29:00 -05:00
try {
if (Processor.GetCurrentThread() == this) {
2008-03-05 09:52:00 -05:00
ulong now = Processor.CycleCount;
context.executionTime += now -
context.lastExecutionTimeUpdate;
LastUpdateTime = now;
}
}
2008-11-17 18:29:00 -05:00
finally {
2008-03-05 09:52:00 -05:00
Processor.RestoreInterrupts(saved);
}
return context.executionTime;
}
}
#endif
internal static
2008-11-17 18:29:00 -05:00
void VisitBootstrapData(ReferenceVisitor visitor)
2008-03-05 09:52:00 -05:00
{
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) {
2008-11-17 18:29:00 -05:00
for (int i = 0; i < cache.Length; i++) {
2008-03-05 09:52:00 -05:00
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) {
2008-11-17 18:29:00 -05:00
for (int i = 0; i < cache.Length; i++) {
2008-03-05 09:52:00 -05:00
cache[i] = new SyncHandle();
}
selectSyncHandlesStack.Push(cache);
}
2008-11-17 18:29:00 -05:00
/// <summary> </summary>
public SchedulerTime BlockedUntil
{
[NoHeapAllocation]
get { return blockedUntil; }
}
2008-03-05 09:52:00 -05:00
// 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")]
2008-11-17 18:29:00 -05:00
[NoStackLinkCheckTrans] // We don't want to throw an exception here;
2008-03-05 09:52:00 -05:00
// 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.
2008-11-17 18:29:00 -05:00
// NOTE: Removing the 'if'. This
// will be removed soon, and the 'is' here
// results in an unbreakable cycle.
//if (!(exn is ProcessStopException)) {
2008-03-05 09:52:00 -05:00
// 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");
2008-11-17 18:29:00 -05:00
//}
2008-03-05 09:52:00 -05:00
// 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();
2008-11-17 18:29:00 -05:00
2008-03-05 09:52:00 -05:00
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")]
2008-11-17 18:29:00 -05:00
[NoStackLinkCheckTrans]
internal static unsafe void DiscardSkippedStackSegments(
System.GCs.CallStack.TransitionRecord *marker,
System.GCs.CallStack.TransitionRecord *oldMarker)
2008-03-05 09:52:00 -05:00
{
ThreadContext *context = Processor.GetCurrentThreadContext();
UIntPtr markerPtr = new UIntPtr(marker);
// while(top kernel->process marker not in stack segment)
// pop (and free) stack segment
//
2008-11-17 18:29:00 -05:00
// HACKHACK: think about what this is doing. The topmost
2008-03-05 09:52:00 -05:00
// 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:
2008-11-17 18:29:00 -05:00
context->stackMarkers = oldMarker;
// Update stack limit.
context->threadRecord.activeStackLimit = context->stackLimit;
2008-03-05 09:52:00 -05:00
}
// 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
2008-11-17 18:29:00 -05:00
///
/// <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;
2008-03-05 09:52:00 -05:00
}
}
2008-11-17 18:29:00 -05:00