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

题目


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.
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
山东大学的操作系统课程中,学生通常会进行课设项目,其中一项是实现一个nachos操作系统nachos是一个开源的教学操作系统,旨在帮助学生理解操作系统的原理以及实现方式。它提供了一个轻量级的操作系统框架,可以在模拟的硬件上运行。通过进行nachos操作系统课设,学生能够深入学习操作系统的内部机制和实现细节。 在山东大学的课设中,学生通常需要从头开始实现一个简单的nachos操作系统。他们需要理解操作系统的基本原理,如进程管理、内存管理、文件系统和设备管理等。然后,他们可以利用nachos提供的框架,根据自己的设计思路逐步实现这些功能。 在实现过程中,学生会面临许多挑战和困难。他们需要处理进程调度、内存分配、文件系统的设计和实现,以及对设备的管理等。他们需要通过深入研究和不断的试验来解决这些问题,从而加深对操作系统的理解。 这个课设对于学生来说是一次非常有价值的实践和学习机会。通过亲手实现一个操作系统,他们可以更好地理解操作系统的工作原理,并掌握操作系统的设计和实现技巧。同时,这也是一个锻炼他们团队合作和问题解决能力的过程。 总之,山东大学的nachos操作系统课设对于学生来说是一次难得的学习机会。通过实践和探索,他们可以更深入地理解操作系统,提升自己的技能和能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值