/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Common Public License (CPL);
* You may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.opensource.org/licenses/cpl1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.jikesrvm.scheduler.greenthreads;
import org.jikesrvm.ArchitectureSpecific.VM_ProcessorLocalState;
import org.jikesrvm.VM;
import org.jikesrvm.VM_Constants;
import org.jikesrvm.annotations.NoSubArchCompile;
import org.jikesrvm.memorymanagers.mminterface.MM_Interface;
import org.jikesrvm.runtime.VM_Entrypoints;
import org.jikesrvm.runtime.VM_Magic;
import static org.jikesrvm.runtime.VM_SysCall.sysCall;
import org.jikesrvm.runtime.VM_Time;
import org.jikesrvm.scheduler.VM_Processor;
import org.jikesrvm.scheduler.VM_ProcessorLock;
import org.jikesrvm.scheduler.VM_Scheduler;
import org.jikesrvm.scheduler.VM_Thread;
import org.vmmagic.pragma.Inline;
import org.vmmagic.pragma.LogicallyUninterruptible;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.Offset;
/**
* Multiplex execution of large number of VM_Threads on small
* number of o/s kernel threads.
*/
@Uninterruptible
@NoSubArchCompile
public final class VM_GreenProcessor extends VM_Processor {
/**
* thread previously running on this processor
*/
public VM_GreenThread previousThread;
/**
* Should this processor dispatch a new VM_Thread when
* "threadSwitch" is called?
* Also used to decide if it's safe to call yield() when
* contending for a lock.
* A value of:
* 1 means "yes" (switching enabled)
* <= 0 means "no" (switching disabled)
*/
private int threadSwitchingEnabledCount;
/**
* Was "threadSwitch" called while this processor had
* thread switching disabled?
*/
int threadSwitchPending;
/**
* The reason given for disabling thread switching
*/
private final String[] threadSwitchDisabledReason = VM.VerifyAssertions ? new String[10] : null;
/**
* threads to be added to ready queue
*/
public VM_GlobalGreenThreadQueue transferQueue;
/** guard for transferQueue */
public final VM_ProcessorLock transferMutex;
/**
* threads waiting for a timeslice in which to run
*/
VM_GreenThreadQueue readyQueue;
/**
* Threads waiting for a subprocess to exit.
*/
VM_ThreadProcessWaitQueue processWaitQueue;
/** guard for collectorThread */
public final VM_ProcessorLock collectorThreadMutex;
/** the collector thread to run */
public VM_GreenThread collectorThread;
/**
* Lock protecting a process wait queue.
* This is needed because a thread may need to switch
* to a different <code>VM_Processor</code> in order to perform
* a waitpid. (This is because of Linux's weird pthread model,
* in which pthreads are essentially processes.)
*/
VM_ProcessorLock processWaitQueueLock;
/**
* threads waiting for i/o
*/
VM_ThreadIOQueue ioQueue;
/**
* threads waiting for migration to subarch to complete
*/
VM_ThreadSubArchWaitQueue subArchQueue;
/**
* thread to run when nothing else to do
*/
VM_GreenThreadQueue idleQueue;
/**
* Is the processor doing a select with a wait option
* A value of:
* false means "processor is not executing a select"
* true means "processor is executing a select with a wait option"
*/
boolean isInSelect;
/**
* Is there a pending flush request for this processor.
*/
boolean flushRequested;
/**
* Number of timer ticks that have actually been forwarded to the VM from
* the C time slicing code
*/
public static int reportedTimerTicks = 0;
/**
* How many times has the C time slicing code been entered due to a timer tick.
* Invariant: timerTicks >= reportedTimerTicks
* reportedTimerTicks can be lower because we supress the reporting of timer ticks during GC.
*/
public static int timerTicks = 0;
/**
* Number of timer ticks between checks of the process wait
* queue. Assuming a tick frequency of 10 milliseconds, we will
* check about twice per second. Waiting for processes
* to die is almost certainly not going to be on a performance-critical
* code path, and we don't want to add unnecessary overhead to
* the thread scheduler.
*/
public static final int NUM_TICKS_BETWEEN_WAIT_POLL = 50;
/**
* non-null --> a processor that has no work to do
*/
static VM_GreenProcessor idleProcessor;
/**
* Create data object to be associated with an o/s kernel thread
* (aka "virtual cpu" or "pthread").
* @param id id that will be returned by getCurrentProcessorId() for
* this processor.
*/
public VM_GreenProcessor(int id) {
super(id);
this.transferMutex = new VM_ProcessorLock();
this.collectorThreadMutex = new VM_ProcessorLock();
this.transferQueue = new VM_GlobalGreenThreadQueue(this.transferMutex);
this.readyQueue = new VM_GreenThreadQueue();
this.ioQueue = new VM_ThreadIOQueue();
this.subArchQueue = new VM_ThreadSubArchWaitQueue();
this.processWaitQueue = new VM_ThreadProcessWaitQueue();
this.processWaitQueueLock = new VM_ProcessorLock();
this.idleQueue = new VM_GreenThreadQueue();
this.isInSelect = false;
}
/**
* Code executed to initialize a virtual processor and
* prepare it to execute Java threads.
*/
public void initializeProcessor() {
// bind our execution to a physical cpu
//
if (VM_Scheduler.cpuAffinity != VM_Scheduler.NO_CPU_AFFINITY) {
sysCall.sysVirtualProcessorBind(VM_Scheduler.cpuAffinity + id - 1);
}
sysCall.sysPthreadSetupSignalHandling();
/* get pthread_id from the operating system and store into vm_processor
field */
pthread_id = sysCall.sysPthreadSelf();
//
// tell VM_Scheduler.boot() that we've left the C startup
// code/stack and are now running vm code/stack
//
isInitialized = true;
sysCall.sysWaitForVirtualProcessorInitialization();
// enable multiprocessing
//
enableThreadSwitching();
// wait for all other processors to do likewise
//
sysCall.sysWaitForMultithreadingStart();
// Store VM_Processor in pthread
sysCall.sysStashVmProcessorInPthread(this);
}
/**
* Is it ok to switch to a new VM_Thread in this processor?
*/
@Inline
@Override
public boolean threadSwitchingEnabled() {
return threadSwitchingEnabledCount == 1;
}
/**
* Enable thread switching in this processor.
*/
@Override
public void enableThreadSwitching() {
++threadSwitchingEnabledCount;
if (VM.VerifyAssertions) {
VM._assert(threadSwitchingEnabledCount <= 1);
if (MM_Interface.gcInProgress()) {
VM._assert(threadSwitchingEnabledCount < 1 || getCurrentProcessorId() == 0);
}
}
if (threadSwitchingEnabled() && threadSwitchPending != 0) {
takeYieldpoint = threadSwitchPending;
threadSwitchPending = 0;
}
}
/**
* Disable thread switching in this processor.
* @param reason for disabling thread switching
*/
@Inline
@Override
public void disableThreadSwitching(String reason) {
--threadSwitchingEnabledCount;
if (VM.VerifyAssertions && (-threadSwitchingEnabledCount < threadSwitchDisabledReason.length)) {
VM_Magic.setObjectAtOffset(threadSwitchDisabledReason,
Offset.fromIntZeroExtend(-threadSwitchingEnabledCount << VM_Constants.BYTES_IN_ADDRESS),
reason);
// threadSwitchDisabledReason[-threadSwitchingEnabledCount] = reason;
}
}
/**
* Request the thread executing on the processor to take the next executed yieldpoint
* and issue memory synchronization instructions
*/
@Override
public void requestPostCodePatchSync() {
if (VM.BuildForPowerPC) {
takeYieldpoint = 1;
codePatchSyncRequested = true;
} else {
if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED);
}
}
/**
* Get processor that's being used to run the current java thread.
*/
@Inline
public static VM_GreenProcessor getCurrentProcessor() {
return (VM_GreenProcessor)VM_ProcessorLocalState.getCurrentProcessor();
}
/**
* Get id of processor that's being used to run the current java thread.
*/
@Inline
public static int getCurrentProcessorId() {
return getCurrentProcessor().id;
}
/**
* Become next "ready" thread.
* Note: This method is ONLY intended for use by VM_Thread.
* @param timerTick timer interrupted if true
*/
@Override
public void dispatch(boolean timerTick) {
// no processor locks should be held across a thread switch
if (VM.VerifyAssertions) checkLockCount(0);
if (flushRequested) processMutatorFlushRequest();
VM_GreenThread newThread = getRunnableThread();
while (newThread.suspendIfPending()) {
newThread = getRunnableThread();
}
previousThread = (VM_GreenThread)activeThread;
activeThread = (VM_GreenThread)newThread;
if (!previousThread.isDaemonThread() && idleProcessor != null && !readyQueue.isEmpty()) {
// if we've got too much work, transfer some of it to another
// processor that has nothing to do
// don't schedule when switching away from a daemon thread...
// kludge to avoid thrashing when VM is underloaded with real threads.
VM_GreenThread t = readyQueue.dequeue();
if (VM.TraceThreadScheduling > 0) VM_Scheduler.trace("VM_Processor", "dispatch: offload ", t.getIndex());
scheduleThread(t);
}
// If one of the threads has an active timerInteval, then we need to
// update the timing information.
if (previousThread.hasActiveTimedInterval() || newThread.hasActiveTimedInterval()) {
long now = VM_Time.nanoTime();
if (previousThread.hasActiveTimedInterval()) {
previousThread.suspendInterval(now);
}
if (newThread.hasActiveTimedInterval()) {
newThread.resumeInterval(now);
}
}
threadId = newThread.getLockingId();
activeThreadStackLimit = newThread.stackLimit; // Delay this to last possible moment so we can sysWrite
VM_Magic.threadSwitch(previousThread, newThread.contextRegisters);
}
/**
* Handle a request from the garbage collector to flush the mutator context.
*/
private void processMutatorFlushRequest() {
VM_GreenScheduler.flushMutatorContextsMutex.lock("handling flush request");
/* One context per processor under green threads */
MM_Interface.flushMutatorContext();
flushRequested = false;
if (++VM_GreenScheduler.flushedMutatorCount >= VM_GreenScheduler.numProcessors) {
while (!VM_GreenScheduler.flushMutatorContextsQueue.isEmpty()) {
VM_GreenScheduler.flushMutatorContextsQueue.dequeue().schedule();
}
}
VM_GreenScheduler.flushMutatorContextsMutex.unlock();
}
/**
* Find a thread that can be run by this processor and remove it
* from its queue.
*/
@Inline
private VM_GreenThread getRunnableThread() {
// Is there a GC thread waiting to be scheduled?
if (collectorThread != null) {
// Schedule GC threads first. This avoids a deadlock when GC is trigerred
// during scheduling (usually due to a write barrier)
collectorThreadMutex.lock("getting runnable gc thread");
if (VM.VerifyAssertions) VM._assert(collectorThread != null);
VM_GreenThread ct = collectorThread;
if (VM.TraceThreadScheduling > 1) {
VM_Scheduler.trace("VM_Processor", "getRunnableThread: collector thread", ct.getIndex());
}
collectorThread = null;
collectorThreadMutex.unlock();
return ct;
}
for (int i = transferQueue.length(); 0 < i; i--) {
transferMutex.lock("transfer queue mutex for dequeue");
VM_GreenThread t = transferQueue.dequeue();
transferMutex.unlock();
if (VM.VerifyAssertions) VM._assert(!t.isGCThread());
if (t.beingDispatched && t != VM_Scheduler.getCurrentThread()) {
// thread's stack in use by some OTHER dispatcher
if (VM.TraceThreadScheduling > 1) {
VM_Scheduler.trace("VM_Processor", "getRunnableThread: stack in use", t.getIndex());
}
transferMutex.lock("transfer queue mutex for an enqueue due to dispatch");
transferQueue.enqueue(t);
transferMutex.unlock();
} else {
if (VM.TraceThreadScheduling > 1) {
VM_Scheduler.trace("VM_Processor", "getRunnableThread: transfer to readyQueue", t.getIndex());
}
readyQueue.enqueue(t);
}
}
if ((reportedTimerTicks % VM_GreenScheduler.numProcessors) + 1 == id) {
// it's my turn to check the io queue early to avoid starvation
// of threads in io wait.
// We round robin this among the virtual processors to avoid serializing
// thread switching in the call to select.
if (ioQueue.isReady()) {
VM_GreenThread t = ioQueue.dequeue();
if (VM.TraceThreadScheduling > 1) {
VM_Scheduler.trace("VM_Processor", "getRunnableThread: ioQueue (early)", t.getIndex());
}
if (VM.VerifyAssertions) {
// local queue: no other dispatcher should be running on thread's stack
VM._assert(!t.beingDispatched || t == VM_Scheduler.getCurrentThread());
}
return t;
}
}
if ((reportedTimerTicks % VM_GreenScheduler.numProcessors) + 1 == id) {
// it's my turn to check the subArch queue early to avoid starvation
// of threads migrating back to the main processor
if (subArchQueue.isReady()) {
VM_GreenThread t = subArchQueue.dequeue();
if (VM.TraceThreadScheduling > 1) {
VM_Scheduler.trace("VM_Processor", "getRunnableThread: subArchQueue (early)", t.getIndex());
}
if (VM.VerifyAssertions) {
// local queue: no other dispatcher should be running on thread's stack
VM._assert(!t.beingDispatched || t == VM_Scheduler.getCurrentThread());
}
return t;
}
}
// FIXME - Need to think about whether we want a more
// intelligent way to do this; for example, handling SIGCHLD,
// and using that to implement the wakeup. Polling is considerably
// simpler, however.
if ((reportedTimerTicks % NUM_TICKS_BETWEEN_WAIT_POLL) == id) {
VM_GreenThread result = null;
processWaitQueueLock.lock("looking at the wait queue");
if (processWaitQueue.isReady()) {
VM_GreenThread t = processWaitQueue.dequeue();
if (VM.VerifyAssertions) {
// local queue: no other dispatcher should be running on thread's stack
VM._assert(!t.beingDispatched || t == VM_Scheduler.getCurrentThread());
}
result = t;
}
processWaitQueueLock.unlock();
if (result != null) {
return result;
}
}
if (!readyQueue.isEmpty()) {
VM_GreenThread t = readyQueue.dequeue();
if (VM.TraceThreadScheduling > 1) {
VM_Scheduler.trace("VM_Processor", "getRunnableThread: readyQueue", t.getIndex());
}
if (VM.VerifyAssertions) {
// local queue: no other dispatcher should be running on thread's stack
VM._assert(!t.beingDispatched || t == VM_Scheduler.getCurrentThread());
}
return t;
}
if (ioQueue.isReady()) {
VM_GreenThread t = ioQueue.dequeue();
if (VM.TraceThreadScheduling > 1) VM_Scheduler.trace("VM_Processor", "getRunnableThread: ioQueue", t.getIndex());
if (VM.VerifyAssertions) {
// local queue: no other dispatcher should be running on thread's stack
VM._assert(!t.beingDispatched || t == VM_Scheduler.getCurrentThread());
}
return t;
}
if (subArchQueue.isReady()) {
VM_GreenThread t = subArchQueue.dequeue();
if (VM.TraceThreadScheduling > 1) VM_Scheduler.trace("VM_Processor", "getRunnableThread: subArchQueue", t.getIndex());
if (VM.VerifyAssertions) {
// local queue: no other dispatcher should be running on thread's stack
VM._assert(!t.beingDispatched || t == VM_Scheduler.getCurrentThread());
}
return t;
}
if (!idleQueue.isEmpty()) {
VM_GreenThread t = idleQueue.dequeue();
if (VM.TraceThreadScheduling > 1) {
VM_Scheduler.trace("VM_Processor", "getRunnableThread: idleQueue", t.getIndex());
}
if (VM.VerifyAssertions) {
// local queue: no other dispatcher should be running on thread's stack
VM._assert(!t.beingDispatched || t == VM_Scheduler.getCurrentThread());
}
return t;
}
// should only get here if the idle thread contended on a lock (due to debug)
if (VM.VerifyAssertions) VM._assert(VM_Scheduler.getCurrentThread() instanceof VM_IdleThread);
return VM_GreenScheduler.getCurrentThread();
}
//-----------------//
// Load Balancing //
//-----------------//
/**
* Add a thread to this processor's transfer queue.
*/
public void transferThread(VM_Thread thread) {
if (VM.VerifyAssertions) VM._assert(thread instanceof VM_GreenThread);
VM_GreenThread t = (VM_GreenThread) thread;
if (t.isGCThread()) {
collectorThreadMutex.lock("gc thread transfer");
collectorThread = t;
/* Implied by transferring a gc thread */
requestYieldToGC();
collectorThreadMutex.unlock();
} else if (this != getCurrentProcessor() ||
(t.beingDispatched && t != VM_Scheduler.getCurrentThread())) {
transferMutex.lock("thread transfer");
transferQueue.enqueue(t);
transferMutex.unlock();
} else if (t.isIdleThread()) {
idleQueue.enqueue(t);
} else {
readyQueue.enqueue(t);
}
}
/**
* Put thread onto most lightly loaded virtual processor.
*/
public void scheduleThread(VM_GreenThread t) {
// if thread wants to stay on specified processor, put it there
if (t.processorAffinity != null) {
if (VM.TraceThreadScheduling > 0) {
VM_Scheduler.trace("VM_Processor.scheduleThread", "outgoing to specific processor:", t.getIndex());
}
t.processorAffinity.transferThread(t);
return;
}
// if t is the last runnable thread on this processor, don't move it
if (t == VM_Scheduler.getCurrentThread() && readyQueue.isEmpty() && transferQueue.isEmpty()) {
if (VM.TraceThreadScheduling > 0) {
VM_Scheduler.trace("VM_Processor.scheduleThread", "staying on same processor:", t.getIndex());
}
getCurrentProcessor().transferThread(t);
return;
}
// if a processor is idle, transfer t to it
VM_GreenProcessor idle = idleProcessor;
if (idle != null) {
idleProcessor = null;
if (VM.TraceThreadScheduling > 0) {
VM_Scheduler.trace("VM_Processor.scheduleThread", "outgoing to idle processor:", t.getIndex());
}
idle.transferThread(t);
return;
}
// otherwise distribute threads round robin
if (VM.TraceThreadScheduling > 0) {
VM_Scheduler.trace("VM_Processor.scheduleThread", "outgoing to round-robin processor:", t.getIndex());
}
chooseNextProcessor(t).transferThread(t);
}
/**
* Cycle (round robin) through the available processors.
*/
private VM_GreenProcessor chooseNextProcessor(VM_GreenThread t) {
t.chosenProcessorId = (t.chosenProcessorId % VM_GreenScheduler.numProcessors) + 1;
return VM_GreenScheduler.processors[t.chosenProcessorId];
}
//---------------------//
// Garbage Collection //
//---------------------//
@Override
public boolean unblockIfBlockedInC() {
int newState, oldState;
boolean result = true;
Offset offset = VM_Entrypoints.vpStatusField.getOffset();
do {
oldState = VM_Magic.prepareInt(this, offset);
if (oldState != BLOCKED_IN_NATIVE) {
result = false;
break;
}
newState = IN_NATIVE;
} while (!(VM_Magic.attemptInt(this, offset, oldState, newState)));
return result;
}
/**
* sets the VP status to BLOCKED_IN_NATIVE if it is currently IN_NATIVE (ie C)
* returns true if BLOCKED_IN_NATIVE
*/
@Override
public boolean lockInCIfInC() {
int oldState;
Offset offset = VM_Entrypoints.vpStatusField.getOffset();
do {
oldState = VM_Magic.prepareInt(this, offset);
if (VM.VerifyAssertions) VM._assert(oldState != BLOCKED_IN_NATIVE);
if (oldState != IN_NATIVE) {
if (VM.VerifyAssertions) VM._assert(oldState == IN_JAVA);
return false;
}
} while (!(VM_Magic.attemptInt(this, offset, oldState, BLOCKED_IN_NATIVE)));
return true;
}
@LogicallyUninterruptible
/* GACK --dave */
public void dumpProcessorState() {
VM.sysWrite("Processor ");
VM.sysWriteInt(id);
if (this == VM_GreenProcessor.getCurrentProcessor()) VM.sysWrite(" (me)");
VM.sysWrite(" running thread");
if (activeThread != null) {
activeThread.dump();
} else {
VM.sysWrite(" NULL Active Thread");
}
VM.sysWrite("\n");
VM.sysWrite(" system thread id ");
VM.sysWriteInt(pthread_id);
VM.sysWrite("\n");
VM.sysWrite(" transferQueue:");
if (transferQueue != null) transferQueue.dump();
VM.sysWrite(" readyQueue:");
if (readyQueue != null) readyQueue.dump();
VM.sysWrite(" ioQueue:");
if (ioQueue != null) ioQueue.dump();
VM.sysWrite(" subArchQueue:");
if (subArchQueue != null) subArchQueue.dump();
VM.sysWrite(" processWaitQueue:");
if (processWaitQueue != null) processWaitQueue.dump();
VM.sysWrite(" idleQueue:");
if (idleQueue != null) idleQueue.dump();
VM.sysWrite(" status: ");
int status = vpStatus;
if (status == IN_NATIVE) VM.sysWrite("IN_NATIVE\n");
if (status == IN_JAVA) VM.sysWrite("IN_JAVA\n");
if (status == BLOCKED_IN_NATIVE) VM.sysWrite("BLOCKED_IN_NATIVE\n");
VM.sysWrite(" timeSliceExpired: ");
VM.sysWriteInt(timeSliceExpired);
VM.sysWrite("\n");
}
/**
* Fail if thread switching is disabled on this processor
*/
@Override
public void failIfThreadSwitchingDisabled() {
if (!threadSwitchingEnabled()) {
VM.sysWrite("No threadswitching on proc ", id);
VM.sysWrite(" with addr ", VM_Magic.objectAsAddress(VM_GreenProcessor.getCurrentProcessor()));
if (VM.VerifyAssertions) {
for (int i=0; i <= -threadSwitchingEnabledCount; i++) {
VM.sysWrite(" because: ", threadSwitchDisabledReason[i]);
}
}
VM.sysWriteln();
VM._assert(false);
}
}
}