Nachos操作系统课设 浅谈优先级调度

来源:互联网 发布:关于宇宙的软件 编辑:程序博客网 时间:2024/06/05 16:06

题目


Task1.5 实现优先级调度

  Implement priority scheduling in Nachos by completing the PriorityScheduler class. Priority scheduling is a key building block in real-time systems. Note that in order to use your priority scheduler, you will need to change a line in nachos.confthat specifies the scheduler class to use. The ThreadedKernel.scheduler key is initially equal to nachos.threads.RoundRobinScheduler. You need to change this to nachos.threads.PriorityScheduler when you're ready to run Nachos with priority scheduling.

  Note that all scheduler classes extend the abstract class nachos.threads.Scheduler. You must implement the methods getPriority(),getEffectivePriority(), and setPriority(). You may optionally also implement increasePriority() and decreasePriority() (these are not required). In choosing which thread to dequeue, the scheduler should always choose a thread of the highest effective priority. If multiple threads with the same highest priority are waiting, the scheduler should choose the one that has been waiting in the queue the longest.

  An issue with priority scheduling is priority inversion. If a high priority thread needs to wait for a low priority thread (for instance, for a lock held by a low priority thread), and another high priority thread is on the ready list, then the high priority thread will never get the CPU because the low priority thread will not get any CPU time. A partial fix for this problem is to have the waiting thread donate its priority to the low priority thread while it is holding the lock.

  Implement the priority scheduler so that it donates priority, where possible. Be sure to implement Scheduler.getEffectivePriority(), which returns the priority of a thread after taking into account all the donations it is receiving.

  Note that while solving the priority donation problem, you will find a point where you can easily calculate the effective priority for a thread, but this calculation takes a long time. To receive full credit for the design aspect of this project, you need to speed this up by caching the effective priority and only recalculating a thread's effective priority when it is possible for it to change.

  It is important that you do not break the abstraction barriers while doing this part -- the Lock class does not need to be modified. Priority donation should be accomplished by creating a subclass of ThreadQueue that will accomplish priority donation when used with the existing Lock class, and still work correctly when used with the existing Semaphore and Condition classes. Priority should also be donated through thread joins.

  Priority Donation Implementation Details:

  1) A thread's effective priority is calculated by taking the max of the donor's and the recipient's priority. If thread A with priority 4 donates to thread B with priority 2, then thread B's effective priority is now 4. Note that thread A's priority is also still 4. A thread that donates priority to another thread does not lose any of its own priority. For these reasons, the term "priority inheritance" is in many ways a more appropriate name than the term "priority donation".

  2) Priority donation is transitive. If thread A donates to thread B and then thread B donates to thread C, thread B will be donating its new effective priority (which it received from thread A) to thread C.


浅说  

        题目要求了要用较低的复杂度实现带有优先级传递的优先级调度,这要求我们在更新一个线程的优先级后要递归地将优先级向上传递。但是优先级的传递并不是一条链状的,因为一个线程可以通过Lock类或者join()方法持有多个等待队列。那么我们再来考虑一个问题,优先级的传递是否会构成环或者复杂的有向图呢?不会,因为在Nachos系统中,一个线程acquire()一个锁或者join()另一个线程后,就会立刻进入等待状态,而不继续执行其他操作,这就意味着一个线程不会处于多个等待队列中,所以优先级的传递关系实际上是一个有向森林,是树形的。这里我对一个线程用一个链表holdQueues来存储了它所持有的全部等待队列,用waitQueue来代表它所处的等待队列,并在队列的实现中设置了lockholder,来表示该队列锁的持有者,递归的实现在getEffectivePriority()中,每次进程修改优先级就会去调用该方法,waitQueue的lockholder来向上更新全部的优先级关系。

        另外一个问题就是如何在保证逻辑完备的情况下将调度高效地实现,这里大多数的同学都没有做对,他们犯了一个严重的错误,使用了java自带的PirorityQueue或者TreeSet,但是在setPirority时没有将线程从队列中移出再放回,而是直接修改了优先级,PirorityQueue是一个二叉堆,TreeSet是利用红黑树实现的,它们的形状在节点插入或者移出时改变,只修改权值并不会引起结构的改变,修改其实是无效的。采用PirorityQueue的方法无疑是最糟糕的,因为这意味着你每次修改优先级都需要使用Iterator遍历整个队列找到你要修改的线程。

        使用TreeSet可以在log(n)的复杂度内移出一个特定线程,已经比较好了,但还是会出现小问题,因为TreeSet是一个集合,里面不能有重复的元素,而TreeSet是采用权重来区分线程的,这样如果只将优先级作为权重,相同优先级的线程就会被覆盖,所以我们要设法使每个线程权重均不相同。注意到要求中有这样一句话,要求相同优先级的进程先进入队列的先出队列,所以我们就可以将权重设置为一个双关键字的值,先比较优先级,再比较时间。如果你将进入队列时系统的时钟滴答作为时间,这样看起来充分利用了系统特性,好像是个不错的主意,但是你又搞错了,因为nachos的时钟滴答一般是在一个线程yield()时才会增加10个,这样如果你一次性将多个线程放入就绪队列的话,他们进入队列的时钟滴答实际是一样的,Treeset同样无法识别这些线程。好的做法是在队列中设置一个计数器,每次线程调用waitForAccess()都会使该队列的计数器加一,通过这个计数器来代表线程进入队列的时间。

        能够完整地将上面的做法实现已经很不错了,但是算法还可以进一步优化。意识到优先级只有0~7这8个等级,我们可以在队列中设置一个大小为8的TreeSet数组,每个线程根据优先级放入相应的TreeSet中。这样的做法有一个好处,就是在更新一个线程的有效优先级时,我们要查询它所持有的全部等待队列中线程优先级的最大值,如果我们直接用一个TreeSet复杂度将是O(logn),当TreeSet中线程数量超过256个时,操作次数是大于8的,但是用刚才提到的做法我们只需要查队列中的8个TreeSet的size()是否大于0即可,size()方法的复杂度是O(1)的,这样不管有多少进程,对每个队列的查询操作次数都不会超过8,复杂度达到了常数级。

        在代码的实现中还有诸多细节,这里不再一一赘述。


实现代码

package nachos.threads;import nachos.machine.*;import java.util.*;public class PriorityScheduler extends Scheduler {    public PriorityScheduler() {    }        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());                           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;    }    public static int priorityDefault = 1;    public static int priorityMinimum = 0;    public static int priorityMaximum = 7;        protected ThreadState getThreadState(KThread thread) {        if (thread.schedulingState == null)            thread.schedulingState = new ThreadState(thread);            return (ThreadState) thread.schedulingState;    }        protected class PriorityQueue extends ThreadQueue {        PriorityQueue(boolean transferPriority) {            this.transferPriority = transferPriority;            init();        }        public void init() {            cnt=0;            wait=new TreeSet[priorityMaximum+1];            for(int i=0;i<=priorityMaximum;i++)                wait[i]=new TreeSet<ThreadState>();        }            public void waitForAccess(KThread thread) {            Lib.assertTrue(Machine.interrupt().disabled());            getThreadState(thread).waitForAccess(this);        }            public void acquire(KThread thread) {            Lib.assertTrue(Machine.interrupt().disabled());            getThreadState(thread).acquire(this);            if(transferPriority)                lockholder=getThreadState(thread);        }            public KThread nextThread() {            Lib.assertTrue(Machine.interrupt().disabled());            ThreadState res=pickNextThread();                        return res==null?null:res.thread;        }            protected ThreadState pickNextThread() {            ThreadState res=NextThread();                        if(lockholder!=null)            {                lockholder.holdQueues.remove(this);                lockholder.getEffectivePriority();                lockholder=res;            }            if(res!=null) res.waitQueue=null;            return res;        }                protected ThreadState NextThread() {            ThreadState res=null;            for(int i=priorityMaximum;i>=priorityMinimum;i--)               if((res=wait[i].pollFirst())!=null) break;                        return res;        }                public void print() {            Lib.assertTrue(Machine.interrupt().disabled());        }                public void add(ThreadState state) {            wait[state.effectivepriority].add(state);        }                public boolean isEmpty() {            for(int i=0;i<=priorityMaximum;i++)                 if(!wait[i].isEmpty()) return false;            return true;        }            protected long cnt;        public boolean transferPriority;        protected TreeSet<ThreadState>[] wait;        protected ThreadState lockholder=null;    }    protected class ThreadState implements Comparable<ThreadState>{        public ThreadState(KThread thread) {            this.thread = thread;            holdQueues=new LinkedList<PriorityQueue>();                        setPriority(priorityDefault);            getEffectivePriority();        }        public int getPriority() {            return priority;        }        public int getEffectivePriority() {            int res=priority;            if(!holdQueues.isEmpty()) {                Iterator it=holdQueues.iterator();                while(it.hasNext())                {                    PriorityQueue holdQueue=(PriorityQueue)it.next();                    for(int i=priorityMaximum;i>res;i--)                        if(!holdQueue.wait[i].isEmpty()) { res=i;break;}                }            }            if(waitQueue!=null&&res!=effectivepriority)            {                ((PriorityQueue)waitQueue).wait[effectivepriority].remove(this);                ((PriorityQueue)waitQueue).wait[res].add(this);            }            effectivepriority=res;            if(lockholder!=null)                lockholder.getEffectivePriority();            return res;        }            public void setPriority(int priority) {            if (this.priority == priority)                return;                        this.priority = priority;                        getEffectivePriority();        }            public void waitForAccess(PriorityQueue waitQueue) {            Lib.assertTrue(Machine.interrupt().disabled());                        time=++waitQueue.cnt;                        this.waitQueue=waitQueue;            waitQueue.add(this);            lockholder=waitQueue.lockholder;            getEffectivePriority();        }            public void acquire(PriorityQueue waitQueue) {            Lib.assertTrue(Machine.interrupt().disabled());                         if(waitQueue.transferPriority) holdQueues.add(waitQueue);            Lib.assertTrue(waitQueue.isEmpty());        }                  protected KThread thread;        protected int priority,effectivepriority;        protected long time;        protected ThreadQueue waitQueue=null;        protected LinkedList holdQueues;        protected ThreadState lockholder=null;        public int compareTo(ThreadState ts) {            if(time==ts.time) return 0;            return time>ts.time?1:-1;        }    }}



测试


测试代码:

    private static class PingTest implements Runnable    {        Lock a=null,b=null;        int name;        PingTest(Lock A,Lock B,int x)        {            a=A;b=B;name=x;        }        public void run() {            System.out.println("Thread "+name+" starts.");            if(b!=null)            {                System.out.println("Thread "+name+" waits for Lock b.");                b.acquire();                System.out.println("Thread "+name+" gets Lock b.");            }            if(a!=null)            {                System.out.println("Thread "+name+" waits for Lock a.");                a.acquire();                System.out.println("Thread "+name+" gets Lock a.");            }            KThread.yield();            boolean intStatus = Machine.interrupt().disable();            System.out.println("Thread "+name+" has priority "+ThreadedKernel.scheduler.getEffectivePriority()+".");            Machine.interrupt().restore(intStatus);            KThread.yield();            if(b!=null) b.release();            if(a!=null) a.release();            System.out.println("Thread "+name+" finishs.");                    }    }        public static void selfTest()    {        Lock a=new Lock();        Lock b=new Lock();                Queue<KThread> qq=new LinkedList<KThread>();        for(int i=1;i<=5;i++)        {            KThread kk=new KThread(new PingTest(null,null,i));            qq.add(kk);            kk.setName("Thread-"+i).fork();        }        for(int i=6;i<=10;i++)        {            KThread kk=new KThread(new PingTest(a,null,i));            qq.add(kk);            kk.setName("Thread-"+i).fork();        }        for(int i=11;i<=15;i++)        {            KThread kk=new KThread(new PingTest(a,b,i));            qq.add(kk);            kk.setName("Thread-"+i).fork();        }        KThread.yield();        Iterator it=qq.iterator();        int pp=0;        while(it.hasNext())        {            boolean intStatus = Machine.interrupt().disable();            ThreadedKernel.scheduler.setPriority((KThread)it.next(),pp+1);            Machine.interrupt().restore(intStatus);            pp=(pp+1)%6+1;        }    }



结果:

        线程1~5不要任何锁,线程6~10需要锁a,线程11~15需要锁a和锁b,并将15个线程的优先级设置如下:1 2 3 4 5 6 1 2 3 4 5 6 1 2 3,这样根据优先级传递的原则,运行结果如下:

Thread 1 starts.

Thread 2 starts.

Thread 3 starts.

Thread 4 starts.

Thread 5 starts.

Thread 6 starts.

Thread 6 waits for Lock a.

Thread 6 gets Lock a.

Thread 7 starts.

Thread 7 waits for Lock a.

Thread 8 starts.

Thread 8 waits for Lock a.

Thread 9 starts.

Thread 9 waits for Lock a.

Thread 10 starts.

Thread 10 waits for Lock a.

Thread 11 starts.

Thread 11 waits for Lock b.

Thread 11 gets Lock b.

Thread 11 waits for Lock a.

Thread 12 starts.

Thread 12 waits for Lock b.

Thread 13 starts.

Thread 13 waits for Lock b.

Thread 14 starts.

Thread 14 waits for Lock b.

Thread 15 starts.

Thread 15 waits for Lock b.

Thread 6 has priority 7.

Thread 6 finishs.

Thread 7 gets Lock a.

Thread 7 has priority 7.

Thread 7 finishs.

Thread 11 gets Lock a.

Thread 11 has priority 7.

Thread 11 finishs.

Thread 13 gets Lock b.

Thread 13 waits for Lock a.

Thread 5 has priority 5.

Thread 5 finishs.

Thread 4 has priority 4.

Thread 10 gets Lock a.

Thread 4 finishs.

Thread 10 has priority 4.

Thread 10 finishs.

Thread 13 gets Lock a.

Thread 13 has priority 7.

Thread 13 finishs.

Thread 12 gets Lock b.

Thread 12 waits for Lock a.

Thread 3 has priority 3.

Thread 9 gets Lock a.

Thread 3 finishs.

Thread 9 has priority 3.

Thread 9 finishs.

Thread 12 gets Lock a.

Thread 12 has priority 6.

Thread 12 finishs.

Thread 15 gets Lock b.

Thread 15 waits for Lock a.

Thread 2 has priority 2.

Thread 8 gets Lock a.

Thread 2 finishs.

Thread 8 has priority 2.

Thread 8 finishs.

Thread 15 gets Lock a.

Thread 15 has priority 3.

Thread 15 finishs.

Thread 14 gets Lock b.

Thread 14 waits for Lock a.

Thread 14 gets Lock a.

Thread 14 has priority 2.

Thread 14 finishs.

Thread 1 has priority 1.

Thread 1 finishs.
0 0
原创粉丝点击