398 lines
14 KiB
C#
398 lines
14 KiB
C#
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Microsoft Research Singularity
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// File: MpExecution.cs
|
|
//
|
|
// Note:
|
|
// The purpose of this class is to control the execution state of
|
|
// processors on MP systems. Specifically, to stop processors running
|
|
// while the debugger is active and while a GC is running.
|
|
//
|
|
// NB These functions may be called with the debugger
|
|
// communications lock held. Any calls to DebugStub.Print or
|
|
// its ilk will cause processor to spin on lock forever.
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
|
|
using Microsoft.Singularity.Hal;
|
|
using Microsoft.Singularity.Io;
|
|
using Microsoft.Singularity.Isal;
|
|
|
|
namespace Microsoft.Singularity
|
|
{
|
|
[CLSCompliant(false)]
|
|
[AccessedByRuntime("referenced from halkd.cpp")]
|
|
public class MpExecution
|
|
{
|
|
// Enumeration of Processor states
|
|
const int Uninitialized = 0x00; // Processor state uninitialized
|
|
const int Running = 0x01; // Processor running normally
|
|
const int TargetFrozen = 0x02; // Processor should stop
|
|
const int TargetThaw = 0x04; // Processor should continue
|
|
const int FreezeActive = 0x08; // Processor is active during freeze
|
|
const int FreezeOwner = 0x10; // Processor initiating freeze
|
|
|
|
static internal volatile bool FreezeRequested;
|
|
|
|
static SpinLock freezeLock;
|
|
static volatile int freezeCount;
|
|
static unsafe ProcessorContext* activeCpuContext;
|
|
static unsafe ProcessorContext* ownerCpuContext;
|
|
|
|
[ NoHeapAllocation ]
|
|
static internal unsafe void AddProcessorContext(ProcessorContext* context)
|
|
{
|
|
// Add processor to list of processors in MP system. Careful
|
|
// to avoid adding processor mid-freeze or without lock.
|
|
start:
|
|
freezeLock.Acquire();
|
|
try {
|
|
if (FreezeRequested)
|
|
goto start;
|
|
ProcessorContext* head = Processor.processorTable[0].context;
|
|
|
|
context->nextProcessorContext = head->nextProcessorContext;
|
|
head->nextProcessorContext = context;
|
|
context->ipiFreeze = Running;
|
|
// From this point on the processor is visible
|
|
// in the debugger
|
|
DebugStub.AddProcessor(context->cpuRecord.id);
|
|
}
|
|
finally {
|
|
freezeLock.Release();
|
|
}
|
|
}
|
|
#if NOP
|
|
[ AccessedByRuntime("referenced from halkd.cpp") ]
|
|
[ NoHeapAllocation ]
|
|
static internal unsafe void FreezeAllProcessors()
|
|
{
|
|
freezeLock.Acquire();
|
|
try {
|
|
freezeCount++;
|
|
if (FreezeRequested == true) {
|
|
// Processors are already frozen
|
|
return;
|
|
}
|
|
|
|
ownerCpuContext = Processor.GetCurrentProcessorContext();
|
|
activeCpuContext = ownerCpuContext;
|
|
if (activeCpuContext->displacedProcessorContext->nextProcessorContext == activeCpuContext) {
|
|
return;
|
|
}
|
|
FreezeRequested = true;
|
|
|
|
activeCpuContext->displacedProcessorContext->ipiFreeze = FreezeActive | FreezeOwner;
|
|
//TODOMP Platform.FreezeProcessors();
|
|
|
|
// Wait until all running processors have gone into freeze
|
|
ProcessorContext* context = activeCpuContext->displacedProcessorContext->nextProcessorContext;
|
|
do
|
|
{
|
|
if ((context->displacedProcessorContext->ipiFreeze & (TargetFrozen | FreezeActive)) != 0) {
|
|
context = context->displacedProcessorContext->nextProcessorContext;
|
|
}
|
|
} while (context->displacedProcessorContext->nextProcessorContext != activeCpuContext);
|
|
}
|
|
finally {
|
|
freezeLock.Release();
|
|
}
|
|
}
|
|
#else
|
|
|
|
[ AccessedByRuntime("referenced from halkd.cpp") ]
|
|
[ NoHeapAllocation ]
|
|
[ Conditional("SINGULARITY_MP") ]
|
|
static internal unsafe void FreezeAllProcessors()
|
|
{
|
|
// This method is only called on MP systems when the
|
|
// number of running processors is greater than 1.
|
|
freezeLock.Acquire();
|
|
try {
|
|
freezeCount++;
|
|
if (FreezeRequested == true) {
|
|
// Processors are already frozen
|
|
return;
|
|
}
|
|
|
|
ownerCpuContext = Processor.GetCurrentProcessorContext();
|
|
activeCpuContext = ownerCpuContext;
|
|
if (activeCpuContext->nextProcessorContext == activeCpuContext) {
|
|
return;
|
|
}
|
|
FreezeRequested = true;
|
|
|
|
activeCpuContext->ipiFreeze = FreezeActive | FreezeOwner;
|
|
// Generate an NMI on all processors (except this one)
|
|
Platform.ThePlatform.FreezeAllCpus();
|
|
|
|
// Wait until all running processors have gone into freeze
|
|
ProcessorContext* context = activeCpuContext->nextProcessorContext;
|
|
do
|
|
{
|
|
if ((context->ipiFreeze & (TargetFrozen | FreezeActive)) != 0) {
|
|
context = context->nextProcessorContext;
|
|
}
|
|
} while (context != activeCpuContext);
|
|
}
|
|
finally {
|
|
freezeLock.Release();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
[ NoHeapAllocation ]
|
|
static internal unsafe void
|
|
FreezeProcessor(ref SpillContext threadContext)
|
|
{
|
|
ProcessorContext* context = Processor.GetCurrentProcessorContext();
|
|
|
|
if (context->ipiFreeze == Uninitialized) {
|
|
// Processor was still being initialized when IPI issued
|
|
while (FreezeRequested == true)
|
|
; // just spin until thaw occurs
|
|
return;
|
|
}
|
|
|
|
context->ipiFreeze = TargetFrozen;
|
|
|
|
while (context->ipiFreeze != TargetThaw && FreezeRequested) {
|
|
if ((context->ipiFreeze & FreezeActive) == FreezeActive) {
|
|
//
|
|
// This processor has been made the active processor
|
|
//
|
|
activeCpuContext = context;
|
|
|
|
// Pass state over to debugger stub
|
|
if (DebugStub.TrapForProcessorSwitch(ref threadContext)) {
|
|
// We're returning to Freeze owner, make it active
|
|
context->ipiFreeze &= ~FreezeActive;
|
|
ownerCpuContext->ipiFreeze |= FreezeActive;
|
|
}
|
|
}
|
|
}
|
|
|
|
context->ipiFreeze = Running;
|
|
}
|
|
|
|
// Return true maps to ContinueNextProcessor
|
|
// Return false maps to ContinueProcessorReselected
|
|
[ AccessedByRuntime("referenced from halkd.cpp") ]
|
|
[ NoHeapAllocation ]
|
|
static internal unsafe bool SwitchFrozenProcessor(int cpuId)
|
|
{
|
|
ProcessorContext* currentContext = Processor.GetCurrentProcessorContext();
|
|
if (currentContext->cpuRecord.id == cpuId) {
|
|
// No processor to switch to.
|
|
return false;
|
|
}
|
|
|
|
ProcessorContext* context = currentContext->nextProcessorContext;
|
|
do
|
|
{
|
|
if (context->cpuRecord.id == cpuId) {
|
|
currentContext->ipiFreeze &= ~FreezeActive;
|
|
context->ipiFreeze |= FreezeActive;
|
|
goto WaitForWakeupOrReselection;
|
|
}
|
|
context = context->nextProcessorContext;
|
|
} while (context != activeCpuContext);
|
|
|
|
// cpuId not found so no processor to switch to.
|
|
return false;
|
|
|
|
WaitForWakeupOrReselection:
|
|
// If this processor is in the frozen state already, return
|
|
// (it'll fall back into FreezeProcessor() loop). NB Initiating
|
|
// processor can never be in this state.
|
|
if (currentContext->ipiFreeze == TargetFrozen) {
|
|
return true;
|
|
}
|
|
|
|
// This processor is the freeze owner, wait to be
|
|
// reselected as the active processor.
|
|
while ((currentContext->ipiFreeze & FreezeActive) != FreezeActive)
|
|
;
|
|
|
|
return false;
|
|
}
|
|
|
|
[ AccessedByRuntime("referenced from halkd.cpp") ]
|
|
[ NoHeapAllocation ]
|
|
[ Conditional("SINGULARITY_MP") ]
|
|
static internal unsafe void ThawAllProcessors()
|
|
{
|
|
// This method is only called on MP systems when the
|
|
// number of running processors is greater than 1.
|
|
freezeLock.Acquire();
|
|
try {
|
|
if (FreezeRequested == false) {
|
|
return;
|
|
}
|
|
|
|
freezeCount--;
|
|
if (freezeCount != 0) {
|
|
return;
|
|
}
|
|
|
|
FreezeRequested = false;
|
|
|
|
ProcessorContext* context = activeCpuContext;
|
|
do
|
|
{
|
|
context->ipiFreeze = TargetThaw;
|
|
context = context->nextProcessorContext;
|
|
} while (context != activeCpuContext);
|
|
}
|
|
finally {
|
|
freezeLock.Release();
|
|
}
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////
|
|
// Initialization function
|
|
|
|
internal static void Initialize()
|
|
{
|
|
freezeLock = new SpinLock(SpinLock.Types.MpExecutionFreeze);
|
|
}
|
|
|
|
//
|
|
// This is called for a StopTheWorldCollector to ensure CPU's
|
|
// other than the one performing the GC do not service interrupts
|
|
// that can corrupt the heap.
|
|
//
|
|
[NoHeapAllocation]
|
|
static internal unsafe void StopProcessorsForGC()
|
|
{
|
|
ProcessorContext* current = Processor.GetCurrentProcessorContext();
|
|
|
|
// Set gcIpiGate to 0 for all processors
|
|
ProcessorContext* context = current;
|
|
do {
|
|
context->gcIpiGate = 0;
|
|
|
|
context = context->nextProcessorContext;
|
|
|
|
} while (context != current);
|
|
|
|
//
|
|
// We don't want to send a broadcast to all but self on a single
|
|
// processor system since the call will fail with no receivers.
|
|
//
|
|
// Note we may have other processors that have not been enabled
|
|
// by the OS and are sitting in SIPI, and won't answer the IPI.
|
|
//
|
|
if (Processor.GetRunningProcessorCount() > 1) {
|
|
Platform.BroadcastFixedIPI((byte)Isal.IX.EVectors.GCSynchronization, false);
|
|
}
|
|
|
|
// Wait for ackknowledgement from all CPU's but self
|
|
context = current->nextProcessorContext;
|
|
|
|
//DebugStub.WriteLine("StopProcessorsForGC: GC CPU (sender) is {0}\n",
|
|
//__arglist(current->cpuId));
|
|
|
|
while (context != current) {
|
|
|
|
//DebugStub.WriteLine("StopProcessorsForGC: Waiting for CPU {0}\n",
|
|
//__arglist(context->cpuId));
|
|
|
|
while (context->gcIpiGate == 0) ;
|
|
|
|
//DebugStub.WriteLine("StopProcessorsForGC: CPU {0} Acknowledged\n",
|
|
//__arglist(context->cpuId));
|
|
|
|
context = context->nextProcessorContext;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Called by the StopTheWorldCollector to allow processors
|
|
// to re-enable their interrupts
|
|
//
|
|
[ NoHeapAllocation ]
|
|
static internal unsafe void ResumeProcessorsAfterGC()
|
|
{
|
|
// clear the GC wait flag for each CPU
|
|
ProcessorContext* current = Processor.GetCurrentProcessorContext();
|
|
|
|
ProcessorContext* context = current;
|
|
do {
|
|
context->gcIpiGate = 0;
|
|
|
|
context = context->nextProcessorContext;
|
|
|
|
} while (context != current);
|
|
|
|
//DebugStub.WriteLine("ResumeProcessorsAfterGC: All processors resumed\n");
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// This interrupt occurs when the processor executing the elected
|
|
// GC thread requires all the other processors to stop touching the
|
|
// heap.
|
|
//
|
|
// The GC has ensured that no threads are running through the GC thread
|
|
// synchronization, but interrupts may still occur and enter managed
|
|
// code, and this must be stopped to prevent corruptions on the heap
|
|
// for non-concurrent collectors.
|
|
//
|
|
// The processor with the elected GC thread has already masked off its
|
|
// interrupts and is waiting for the non-GC processors to report
|
|
// that their interrupts are disabled.
|
|
//
|
|
[NoHeapAllocation]
|
|
static internal unsafe void GCSynchronizationInterrupt()
|
|
{
|
|
// - member barriers and other issues with spinwaiting on a variable
|
|
|
|
bool en = Processor.DisableInterrupts();
|
|
|
|
//DebugStub.WriteLine("Processor {0} received GCSynchronizationInterrupt!\n",
|
|
//__arglist(Processor.GetCurrentProcessorId()));
|
|
|
|
|
|
ProcessorContext* current = Processor.GetCurrentProcessorContext();
|
|
|
|
// Write value for this processor stating we ackknowledge the interrupt
|
|
current->gcIpiGate = 1;
|
|
|
|
// Question: Are we at a GC safe point? For all GC's?
|
|
|
|
//
|
|
// Spinwait until the GC processor indicates its done by clearing
|
|
// this flag.
|
|
//
|
|
|
|
while (current->gcIpiGate != 0) {
|
|
//
|
|
}
|
|
|
|
Processor.RestoreInterrupts(en);
|
|
|
|
//DebugStub.WriteLine("Processor {0} done with GCSynchronizationInterrupt!\n",
|
|
//__arglist(Processor.GetCurrentProcessorId()));
|
|
|
|
return;
|
|
}
|
|
|
|
// Test ..
|
|
internal static void Test()
|
|
{
|
|
|
|
}
|
|
}
|
|
}
|