package nachos.threads;
import nachos.machine.*;
import java.util.TreeSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
/**
* A scheduler that chooses threads based on their priorities.
*
* <p>
* A priority scheduler associates a priority with each thread. The next thread
* to be dequeued is always a thread with priority no less than any other
* waiting thread's priority. Like a round-robin scheduler, the thread that is
* dequeued is, among all the threads of the same (highest) priority, the
* thread that has been waiting longest.
*
* <p>
* Essentially, a priority scheduler gives access in a round-robin fassion to
* all the highest-priority threads, and ignores all other threads. This has
* the potential to
* starve a thread if there's always a thread waiting with higher priority.
*
* <p>
* A priority scheduler must partially solve the priority inversion problem; in
* particular, priority must be donated through locks, and through joins.
*/
public class PriorityScheduler extends Scheduler {
/**
* Allocate a new priority scheduler.
*/
public PriorityScheduler() {
}
/**
* Allocate a new priority thread queue.
*
* @param transferPriority <tt>true</tt> if this queue should
* transfer priority from waiting threads
* to the owning thread.
* @return a new priority thread queue.
*/
public ThreadQueue newThreadQueue(boolean transferPriority) {
return new PriorityQueue(transferPriority);
}
public int getPriority(KThread thread) {
Lib.assertTrue(Machine.interrupt().disabled());
return getThreadState(thread).getPriority();
}
public int getEffectivePriority(KThread thread) {
Lib.assertTrue(Machine.interrupt().disabled());
return getThreadState(thread).getEffectivePriority();
}
public void setPriority(KThread thread, int priority) {
Lib.assertTrue(Machine.interrupt().disabled());
System.out.println("Set priority " + priority + " for " + thread);
Lib.assertTrue(priority >= priorityMinimum &&
priority <= priorityMaximum);
getThreadState(thread).setPriority(priority);
}
public boolean increasePriority() {
boolean intStatus = Machine.interrupt().disable();
KThread thread = KThread.currentThread();
int priority = getPriority(thread);
if (priority == priorityMaximum)
return false;
setPriority(thread, priority+1);
Machine.interrupt().restore(intStatus);
return true;
}
public boolean decreasePriority() {
boolean intStatus = Machine.interrupt().disable();
KThread thread = KThread.currentThread();
int priority = getPriority(thread);
if (priority == priorityMinimum)
return false;
setPriority(thread, priority-1);
Machine.interrupt().restore(intStatus);
return true;
}
/**
* The default priority for a new thread. Do not change this value.
*/
public static final int priorityDefault = 1;
/**
* The minimum priority that a thread can have. Do not change this value.
*/
public static final int priorityMinimum = 0;
/**
* The maximum priority that a thread can have. Do not change this value.
*/
public static final int priorityMaximum = 7;
/**
* Return the scheduling state of the specified thread.
*
* @param thread the thread whose scheduling state to return.
* @return the scheduling state of the specified thread.
*/
protected ThreadState getThreadState(KThread thread) {
if (thread.schedulingState == null)
thread.schedulingState = new ThreadState(thread);
return (ThreadState) thread.schedulingState;
}
/**
* A <tt>ThreadQueue</tt> that sorts threads by priority.
*/
protected class PriorityQueue extends ThreadQueue {
PriorityQueue(boolean transferPriority) {
this.transferPriority = transferPriority;
this.donationController = new DonationController(queue);
}
public void waitForAccess(KThread thread) {
Lib.assertTrue(Machine.interrupt().disabled());
Lib.assertTrue(!threadStates.containsKey(getThreadState(thread)));
threadStates.put(getThreadState(thread), new ThreadWrapper(getThreadState(thread)));
queue.add(threadStates.get(getThreadState(thread)));
getThreadState(thread).waitForAccess(this);
if(transferPriority)
donationController.transferPriority(getThreadState(thread));
Lib.debug('P', "Inserted: " + thread + ", priority = " + getThreadState(thread).getPriority() + ", effective priority = " + getThreadState(thread).getEffectivePriority() + ", size = " + queue.size());
}
public void acquire(KThread thread) {
Lib.assertTrue(Machine.interrupt().disabled());
Lib.assertTrue(!threadStates.containsKey(getThreadState(thread)));
getThreadState(thread).acquire(this);
if(transferPriority)
donationController.setTarget(getThreadState(thread));
Lib.debug('P', "Acquired: " + thread + ", priority = " + getThreadState(thread).getPriority() + ", effective priority = " + getThreadState(thread).getEffectivePriority() + ", size = " + queue.size());
}
public KThread nextThread() {
Lib.assertTrue(Machine.interrupt().disabled());
if(queue.isEmpty())
return null;
ThreadWrapper w = queue.poll();
threadStates.remove(w.state);
if(transferPriority)
donationController.resetMaximumPriority(w.state);
Lib.debug('P', "NextThread: " + w.state.thread + ", priority = " + w.state.getPriority() + ", effective priority = " + w.state.getEffectivePriority() + ", size = " + queue.size());
acquire(w.state.thread);
return w.state.thread;
}
public void updateThreadState(ThreadState s) {
if(threadStates.containsKey(s)) {
Lib.debug('P', "Updating: " + s.thread + ", priority = " + s.getPriority() + ", effective priority = " + s.getEffectivePriority());
ThreadWrapper w = threadStates.get(s);
queue.remove(w);
queue.add(w);
}
}
/**
* Return the next thread that <tt>nextThread()</tt> would return,
* without modifying the state of this queue.
*
* @return the next thread that <tt>nextThread()</tt> would
* return.
*/
protected ThreadState pickNextThread() {
return queue.peek().state;
}
public void print() {
Lib.assertTrue(Machine.interrupt().disabled());
System.out.println("In queue: " + queue.size() + " total");
for(Map.Entry<ThreadState, ThreadWrapper> e : threadStates.entrySet()) {
System.out.println(" " + e.getKey().thread + " priority " + e.getKey().getEffectivePriority());
}
}
/**
* <tt>true</tt> if this queue should transfer priority from waiting
* threads to the owning thread.
*/
public boolean transferPriority;
public java.util.PriorityQueue<ThreadWrapper> queue = new java.util.PriorityQueue<ThreadWrapper>();
public HashMap<ThreadState, ThreadWrapper> threadStates = new HashMap<ThreadState, ThreadWrapper>();
public DonationController donationController;
protected class ThreadWrapper implements Comparable {
public ThreadWrapper(ThreadState s) {
state = s;
timeInserted = Machine.timer().getTime();
}
public int compareTo(Object o) {
Lib.assertTrue(o instanceof ThreadWrapper);
ThreadWrapper s = (ThreadWrapper) o;
return (state.getEffectivePriority() == s.state.getEffectivePriority()) ? (int)(timeInserted - s.timeInserted) : (s.state.getEffectivePriority() - state.getEffectivePriority());
}
public ThreadState state;
public long timeInserted;
}
}
protected class DonationController {
public DonationController(java.util.PriorityQueue<PriorityQueue.ThreadWrapper> queue) {
this.target = null;
this.maximumPriority = priorityMinimum;
this.queue = queue;
}
public void setTarget(ThreadState t) {
if(target != null)
target.retractDonatedPriority(this);
target = t;
target.donatePriority(this, maximumPriority);
Lib.debug('P', "Donation target is set to " + t.thread);
}
public void resetMaximumPriority(ThreadState t) {
if(t.getEffectivePriority() == maximumPriority) {
maximumPriority = priorityMinimum;
for(PriorityQueue.ThreadWrapper w : queue)
maximumPriority = Math.max(maximumPriority, w.state.getEffectivePriority());
Lib.debug('P', "Reset maximum priority to " + maximumPriority);
}
}
public void transferPriority(ThreadState t) {
maximumPriority = Math.max(t.getEffectivePriority(), maximumPriority);
if(target != null && maximumPriority > target.getEffectivePriority()) {
target.donatePriority(this, maximumPriority);
Lib.debug('P', "Maximum priority " + maximumPriority + " transferred to " + t.thread);
}
}
protected ThreadState target;
protected int maximumPriority;
protected java.util.PriorityQueue<PriorityQueue.ThreadWrapper> queue;
}
/**
* The scheduling state of a thread. This should include the thread's
* priority, its effective priority, any objects it owns, and the queue
* it's waiting for, if any.
*
* @see nachos.threads.KThread#schedulingState
*/
protected class ThreadState {
/**
* Allocate a new <tt>ThreadState</tt> object and associate it with the
* specified thread.
*
* @param thread the thread this state belongs to.
*/
public ThreadState(KThread thread) {
this.thread = thread;
setPriority(priorityDefault);
}
/**
* Return the priority of the associated thread.
*
* @return the priority of the associated thread.
*/
public int getPriority() {
return priority;
}
/**
* Return the effective priority of the associated thread.
*
* @return the effective priority of the associated thread.
*/
public int getEffectivePriority() {
return effectivePriority.getEffectivePriority();
}
public void donatePriority(DonationController q, int donation) {
effectivePriority.donate(q, donation);
notifyParents();
Lib.debug('P', "Donated to: " + thread + " with donation " + donation);
}
public void retractDonatedPriority(DonationController q) {
effectivePriority.retract(q);
notifyParents();
Lib.debug('P', "Retract donation: " + thread + ", now priority = " + getPriority() + ", effective priority = " + getEffectivePriority());
}
/**
* Set the priority of the associated thread to the specified value.
*
* @param priority the new priority.
*/
public void setPriority(int priority) {
if (this.priority == priority)
return;
this.priority = priority;
effectivePriority.setPriority(priority);
notifyParents();
}
public void notifyParents() {
for(PriorityQueue q : parents) {
q.updateThreadState(this);
}
}
/**
* Called when <tt>waitForAccess(thread)</tt> (where <tt>thread</tt> is
* the associated thread) is invoked on the specified priority queue.
* The associated thread is therefore waiting for access to the
* resource guarded by <tt>waitQueue</tt>. This method is only called
* if the associated thread cannot immediately obtain access.
*
* @param waitQueue the queue that the associated thread is
* now waiting on.
*
* @see nachos.threads.ThreadQueue#waitForAccess
*/
public void waitForAccess(PriorityQueue waitQueue) {
parents.add(waitQueue);
}
/**
* Called when the associated thread has acquired access to whatever is
* guarded by <tt>waitQueue</tt>. This can occur either as a result of
* <tt>acquire(thread)</tt> being invoked on <tt>waitQueue</tt> (where
* <tt>thread</tt> is the associated thread), or as a result of
* <tt>nextThread()</tt> being invoked on <tt>waitQueue</tt>.
*
* @see nachos.threads.ThreadQueue#acquire
* @see nachos.threads.ThreadQueue#nextThread
*/
public void acquire(PriorityQueue waitQueue) {
parents.remove(waitQueue);
}
/** The thread with which this object is associated. */
protected KThread thread;
/** The priority of the associated thread. */
protected int priority;
protected EffectivePriorityDesc effectivePriority = new EffectivePriorityDesc(0);
protected HashSet<PriorityQueue> parents = new HashSet<PriorityQueue>();
protected class EffectivePriorityDesc {
EffectivePriorityDesc(int priority) {
this.priority = priority;
this.max_donation = 0;
}
void setPriority(int priority) {
this.priority = priority;
}
int getEffectivePriority() {
return Math.max(priority, max_donation);
}
void donate(DonationController q, int priority) {
this.donations.put(q, priority);
max_donation = Math.max(max_donation, priority);
}
void retract(DonationController q) {
if(donations.containsKey(q)) {
int p = donations.get(q);
donations.remove(q);
if(max_donation == p) {
max_donation = 0;
for(Map.Entry<DonationController, Integer> e : donations.entrySet()) {
max_donation = Math.max(max_donation, e.getValue());
}
}
}
}
protected int priority, max_donation;
protected HashMap<PriorityScheduler.DonationController, Integer> donations = new HashMap<DonationController, Integer>();
}
}
}