如何让Alarm类更像一个“闹钟”

2019-07-12 19:26发布

问题描述 nachos.thread包下的Alarm类要求实现“闹钟”的功能,“闹钟”功能是对于每个Kthread而言的。像我们生活中使用起床闹钟那样,闹钟会在起床时间将我们叫醒。同样的,Alarm类要求完成在正确的第一时间将Kthread唤醒。 结合nachos系统特 {MOD}的细节分析 上面的描述用到了“正确的第一时间”这个修饰词,为什么不能说是“准确的时间”呢? 原因也很简单,nachos系统是一个模拟实现的操作系统,当然也会维护时钟系统(以clock tick为单位),而Alarm类就是基于时钟系统构建的,规定每500 clock ticks,“闹钟”可以生效一次(对应着timerInterrupt()方法调用一次)。所以“闹钟”对Kthread的唤醒时间并不一定是准确的,它只能在每500 clock ticks间隔下唤醒应该被唤醒的Kthread,因此叫做“正确的第一时间”。
实现思路 Alarm类有两个成员函数。 第一个:public void waitUntil(long x)方法,完成当前Kthread的闹钟设置,长整形参数x表示“睡眠”的时间。在方法的内部我们将Kthread加入等待队列(新增的Alarm类类成员变量,用来存储“睡眠”的Kthread),防止其被加入就绪队列,也就实现了“睡眠”的功能。 第二个:public void timerInterrupt()方法,其每500 clock ticks被系统调用一次。在方法内部我们需要对当前每一个正在“睡眠”的Kthread进行检查,并唤醒需要被唤醒的所有Kthread,将其加入到就绪队列。 结合方法功能,我们可以想到:不同的Kthread之间应该可以设置独立的“睡眠”的时间,所以“睡眠”时间需要作为Kthread的属性存在,则为Kthread类成员增添睡眠时间属性waitTime,初始化为0。当Kthread被创建后而不想立即被加入到就绪队列时,则调用waitUntil(long x)方法,设置“睡眠”时间(waitTime = x)。同时在 timerInterrupt()方法内部,我们需要更新等待队列所有Kthread的“睡眠”时间waitTime ,如果某个Kthread的waitTime <=0,则需要将其加入就绪队列。
代码摘取 1)添加Kthread类成员变量 // 添加Kthread等待时间属性 public long waitTime = 0;

(2)添加Alarm类类成员变量 // readyQueue用来保存未到达唤醒时间的KThread public static ThreadQueue waitQueue = ThreadedKernel.scheduler.newThreadQueue(false); // readyQueue0用来保存更新过waitTime的KThread private static ThreadQueue waitQueue0 = ThreadedKernel.scheduler.newThreadQueue(false);

(3)waitUntil(long x)方法实现 public void waitUntil(long x) { // for now, cheat just to get something working (busy waiting is bad) //确定唤醒时间 long wakeTime = Machine.timer().getTime() + x; KThread.currentThread().waitTime = x; if (wakeTime > Machine.timer().getTime()) { System.out.println("waitTime " + x); waitQueue.waitForAccess(KThread.currentThread()); KThread.sleep(); } }

(4)timerInterrupt()方法实现 public void timerInterrupt() { System.out.println("call timerInterrupt() time: " + Machine.timer().getTime()); boolean intStatus = Machine.interrupt().disable(); if (waitQueue != null) { // 如果等待队列不为空,则更新每个KThread等待时间waitTime KThread refreshThread = waitQueue.nextThread(); while (refreshThread != null) { long oldTime = refreshThread.waitTime; long newTime = oldTime - 500; System.out.println("newTime: " + newTime); refreshThread.waitTime = newTime; if (newTime <= 0) { // 唤醒 refreshThread.ready(); } else { waitQueue0.waitForAccess(refreshThread); } refreshThread = waitQueue.nextThread(); } // 周转依旧需要睡眠的KThread KThread kt = waitQueue0.nextThread(); while (kt != null) { waitQueue.waitForAccess(kt); kt = waitQueue0.nextThread(); } } KThread.currentThread().yield(); Machine.interrupt().restore(intStatus); }

(5)测试方法 修改后的PingTest类 private static class PingTest implements Runnable { PingTest(int which) { this.which = which; } public void run() { this.setWakeupTime(530); System.out.println("time to wake up at: " + Machine.timer().getTime() + " ticks"); for (int i = 0; i < 5; i++) { System.out.println("*** thread " + which + " looped " + i + " times"); currentThread.yield(); } } // setWakeupTime(long t)方法 public void setWakeupTime(long t) { boolean intStatus = Machine.interrupt().disable(); Alarm alarm = ThreadedKernel.alarm; alarm.waitUntil(t); Machine.interrupt().restore(intStatus); } private int which; }

Test Alarm public static void selfTest3() { System.out.println("/**************Test Alarm****************/"); KThread thread = new KThread(new PingTest(1)).setName("forked thread"); thread.fork(); }

(6)测试截图
从截图中可以看出测试线程设置的等待时间是530 clock ticks,所以其在timerInterrupt()方法第二次被调用时被唤醒(加入到就绪队列),随后被执行。

功能优化 问题:当把线程的睡眠时间设置的足够大时,该线程并不会得到执行。

可以看到线程设置“睡眠”时间在1530 clock ticks时,其并不能得到执行。
原因:因为主线程(main线程)在执行完成后,并不知道还有子线程在“睡眠”,所以直接停机。

解决步骤:
(1)为了让主线程知道有子线程在“睡眠”,可以在子线程被创建后,主线程立即执行join()方法,这样主线程被加入到子线程的等待队列,直到子线程执行结束,主
线程才能继续执行,也就保证了“睡眠”的线程一定能被唤醒。
Test Alarm
public static void selfTest3() { System.out.println("/**************Test Alarm****************/"); KThread thread = new KThread(new PingTest(1)).setName("forked thread"); thread.fork(); thread.join(); }

这里设置同样的“睡眠”时间,子线程得到执行。 (2)虽然现在所有“睡眠”的线程都得到了执行的机会,但是在线程睡眠的期间,主线程是阻塞的。所以我们可以再系统停机之前来判 断是否还有线程在“睡眠”,如果有则主线程等待睡眠线程都执行完后在执行停机指令。 去掉Test Alarm的'thread.join();'语句 public static void selfTest3() { System.out.println("/**************Test Alarm****************/"); KThread thread = new KThread(new PingTest(1)).setName("forked thread"); thread.fork(); // thread.join(); }

修改Machine类的halt()方法 public static void halt() { //添加对“睡眠”线程的判断 boolean intStatus = Machine.interrupt().disable(); KThread refreshThread = Alarm.waitQueue.nextThread(); while(refreshThread != null){ Alarm.waitQueue.waitForAccess(refreshThread); refreshThread.join(); refreshThread = Alarm.waitQueue.nextThread(); } Machine.interrupt().restore(intStatus); System.out.print("Machine halting! "); stats.print(); terminate(); }

总结
这样,我们就得到了nachos系统下真正意义的“闹钟”,在子线程“睡眠”的时候,主线程可以去干其他事而不用阻塞;同时也保证了每一个“睡眠”的子线程都会被
唤醒。是不是就很像我们生活中使用的闹钟了呢?