操作系统nachoes一些问题与解决方法

本文详细记录了在操作系统课程设计中,使用nachoes进行实验时遇到的问题及其解决策略,包括线程同步、条件变量、定时器、优先级调度等多个方面。通过对实验项目的详细分析,提出了具体的技术解决方案,对于遇到的如多线程同步问题、随机数种子固定导致的重复唤醒时间问题等进行了深入探讨和解决。
摘要由CSDN通过智能技术生成

操作系统nachoes一些问题与解决方法

山东大学操作系统课设,自己做的过程中的遇到的一些问题,如果有参考价值可以使用:

  • 一些粗略的问题和感想
  • 详细project1思路与解决
  • 详细project2思路和解决

一些粗略的问题和感想

首先,我花了很长时间在了解 Nachos 的运行机制与框架上,尤其是与 java 虚拟机 线程的关系,但在具体实现中,仍然感到抓住系统的大体构架有些难度。每次修改时,会有种牵一发而动全身的感觉。
前几周,看完实验要求后,感觉前几节上课讲的 nachos 的基本原理、运行机制, 安全防护之类的与实验关系不大,但我留下了笔记。这些细节的操作在写代码的时候起了很大的作用。
开始做实验时,我第一步可能和别人不太一样,我是先把nachos里面的代码注释基本上都翻译了一遍,用这个方法来加深我对nachos系统主体框架的理解。第二步真正进入实验,刚才沈立返讲了第一个实验,那我就不讲了。但是稍微讲一下自己遇到的问题,就是个线程的时候测试没错,多个线程一起创建就会有错,改正方法是:主动调用等待队列的所有线程,每一次都要用到nextThread方法。

后面project1里面的几个问题其实和第一个差不多,都是同步的问题,基本都是当一个线程运行着,其他有线程创建了或者是要执行,要实现原子性,所以需要加锁进入等待队列sleep,然后当前线程执行完就唤醒其他线程,他们就释放锁准备执行。其中出现的一个问题是,我在条件变量那个题时,想成了让线程去调用线程。查了一下资料发现,线程由执行状态转变为阻塞状态是有线程自己调用阻塞原语实现的。进程要就绪,是另一个发现者进程(或系统中断(实验三ALArm))调用唤醒原语来实现的。

还有一个当时遇到的问题是关于waitUntil多次运行得到的唤醒时间都是一致的这一点,最初阅读源码发现唤醒正在等待特定的时间唤醒的进程是由系统中断的handler来处理的,该中断的触发时间是一个随机数,并且在500跳左右浮动,然后每次中断时间多次运行都是一致的,开始考虑为什么是相同的。思考之后,觉得可能是随机数生成器的种子一直是设置的一个定值,为了找到生成器种子的设定我和同学讨论并且解读了相关部分的源码,并且当时为了确定setSeed方法的调用地点,还在该方法内部抛出了一个null pointer异常,来通过java的异常track来查看调用递归序列,找到了设置的地点,最终发现seed一直都被设置为0,所以所有的生成序列都是相同的

第六个主要是逻辑上的问题,一定是孩子先过去,然后就是相关操作和条件的控制。(speak?)都是条件变量的问题,而且比较独立。方案是:使用一个锁变量保证互斥,四个条件变量保证同步。结构如下:首先创建大人线程和孩子线程——获取锁——唤醒一个孩子进程——释放锁。 。刚才韩楚怡讲了我就不详细讲了。
只有小孩可以有返程路线,大人返程没有意义;要避 免出现当大人到 M 岛上而没有孩子在 M 岛上出现这种情况。开始之前让所有人 sleep, 然后 begin()叫醒一个 child, 然后每次一个人干完了活叫醒另一个然 后自己睡,所有人都是被动的,相比所有人都主动竞争当驾驶员或乘客,这样可 以避免冲突。
现在遇到的问题,就是第五个问题中感觉自己写完了,但是运行的不是很对,要实现nachos中的优先级调度。问了同学,说是需要改一下conf文件,但是我还没有实验。
大概就是这些

关于两个进程之间进行的通信的设计,采用一个等待队列的设计,因为同一时段队列中只可能存在一种进程,全是speaker或者全是listener,因为假设如果存在speaker、listener同时在队列中,那么当第一个非相同类型的进程准备进入队列的时候,就会取出一个相对类型的进程进行通信,这样就造成队列中只会有一种类型的进程在等待着,因此就只需要有一个队列存在就可以了。

单做第一个join,大概花了2周的时间,开始总感觉自己找不到题目的脉络,后来就和同学一起讨论,发现这就是个线程同步的问题。比如当A线程正在运行时,A执行B.join(),则B开始运行,A就被挂起,B结束后才能再执行A。关键一点是Jion完后需要唤醒等待队列中的所有线程,因为在这期间,并不一定只有两个线程在执行。所以修改finish的时候要注意它既可以主动被调用也可以直接调用,而且不能结束当前运行的线程。在测试的时候也是遇到这个问题,两个线程的时候测试没错,多个线程一起创建就会有错,改正方法是:

KThread waitThread= currentThread.waitJoinQueue.nextThread(); //调用等待队列上的第一个进程;
while (waitThread != null) //while
{
waitThread.ready(); //唤醒等待队列上所有被阻塞的进程
waitThread= currentThread.waitJoinQueue.nextThread(); //调用等待队列上的第一个进程
}

主动调用等待队列的所有线程,每一次都要用到nextThread方法。

详细project1思路与解决

Task 1 实现 KThread.join()
要求
实现KThread.join()函数。注意:其他线程没必要调用join(),但是一旦调用只能调用一次。对一个线程调用两次join()的行为是未定义的,即使第二个调用者是一个不同的线程。无论一个线程是否被调用都要能够正常执行结束。
分析
join()函数的作用应为,若A线程调用 B.join(),则A线程需要刮起,等待B线程执行结束再继续执行。
方案
在KThread类中添加一个阻塞队列,每次其他线程调用该线程的join()方法时都阻塞在该队列中,当该线程执行结束时,将队列中阻塞的线程取出并加入就绪队列。
实现代码
修改KThread.join()和KThread.finish().
public void join() {
Lib.debug(dbgThread, “Joining to thread: ” + toString());

    Lib.assertTrue(this != currentThread);

    boolean iniStatus = Machine.interrupt().disable();

    if (status != statusFinished) {
        waitForJoin.waitForAccess(currentThread);
        KThread.sleep();
    }
    Machine.interrupt().restore(iniStatus);
}

public static void finish() {
    Lib.debug(dbgThread, "Finishing thread: " + currentThread.toString());

    Machine.interrupt().disable();

    Machine.autoGrader().finishingCurrentThread();

    Lib.assertTrue(toBeDestroyed == null);
    toBeDestroyed = currentThread;

    currentThread.status = statusFinished;
    KThread waitThread = null;
    while ((waitThread = currentThread.waitForJoin.nextThread()) != null) {
        waitThread.ready();
    }
    sleep();
}

测试代码
将如下代码添加入 KThread.selfTest() 方法
/* Task 1 test */
System.out.println(“Task 1 Test begin:”);
KThread kt = new KThread(new PingTest(1));
kt.fork();
kt.join();
// new KThread(new PingTest(1)).setName(“forked thread”).fork();
new PingTest(0).run();
Task 2 通过开关中断提供原子性/直接实现条件变量
要求
通过开关中断提供原子性,直接实现条件变量。我们提供了一个使用信号量实现的例子。你的工作是实现一个相同的功能而不直接利用信号量(你可以使用锁,即使锁间接地使用了信号量)。一旦你完成,你将拥有两个能提供完全相同功能的实现。你的第二个实现需要在nachos.threads.Condition2类内部实现。
分析
我们通过开关中断来保证完整性Machine.interupt().disable(),Machine.interupt().restore(boolean),同时,我们通过Lock.acquire()和Lock.release(),来保证同时只有一个线程访问临界代码区。每个条件变量拥有一个锁变量,这一点跟Condition的构造是很类似的。
方案
sleep()方法中通过关中断来保证完整性,并且在sleep之前将锁释放掉,通过KThread.sleep()来休眠。
wake()通过ready()方法将一个线程加入就绪队列,来实现唤醒。
wakeAll()基于wake()来实现的,将等待队列中的每一个都wake()一次,并从队列中移除。
实现代码
实现Condition2.sleep(),Condition2.wake()和Condition2.wakeAll(),另外,本部分的测试将于Task4的测试一起进行。
public void sleep() {
Lib.assertTrue(conditionLock.isHeldByCurrentThread());

    boolean iniStatus = Machine.interrupt().disable();

    waitQueue.waitForAccess(KThread.currentThread());

    conditionLock.release();
    KThread.sleep();
    conditionLock.acquire();

    Machine.interrupt().restore(iniStatus);
}
public void wake() {
    Lib.assertTrue(conditionLock.isHeldByCurrentThread());
    boolean iniStatus = Machine.interrupt().disable();
    KThread kt;
    if ((kt = waitQueue.nextThread()) != null) {
        kt.ready();
    }
    Machine.interrupt().restore(iniStatus);
}
public void wakeAll() {
    Lib.assertTrue(conditionLock.isHeldByCurrentThread());
    boolean iniStatus = Machine.interrupt().disable();
    KThread kt;
    while ((kt = waitQueue.nextThread()) != null) {
        kt.ready();
    }
    Machine.interrupt().restore(iniStatus);
}

Task 3 实现Alarm.WaitUntil(long x)方法
要求
通过实现waitUntil(long x)方法来实现Alarm类。线程通过调用waitUntil来阻塞运行知道到达now+x时间再继续运行。不要创建新的线程来实现waikUntil(),需要修改waitUntil()和时钟中断处理器。waitUntil是不能限制在一个线程上的,任意数量的线程都能调用并且可以在任意时间阻塞。
注意:全局只有一个Alarm对象。
分析
首先,我们不能另开一个线程进行计时然后到时间唤醒这个线程,但是要求中给出提示,我们可以运用时钟中断来处理这个事情,给时钟中断添加处理器。
方案
由于Machine.Timer类大约每个500个时钟周期调用回调函数,我们为中断添加处理器(通过Timer.setInteruptHandler()来设置)。为了实现waitUntil()需要在Alarm类内部添加内部类Waiter,包含属性Thread,wakeTime,每当有线程调用waitUntil()方法便将该线程与其该唤醒的时间构成Waiter对象,并加入队列。每次时钟中断时,将队列中元素取出,查看是否到达唤醒时间,如果到达则可以将该线程取出,如果未到达,便重新放入等待队列。
关于测试时,调用waitUntil唤醒时间多次运行均为定值的原因分析
首先,我们知道只有在时钟中断的时候才会检查该线程是否到达唤醒时间。
然后,我们在每次调用时钟中断时,输出以下当前时间,发现输出的序列是一样的。
在之后实现阅读源码之后发现在Machine.Timer类中的scheduleInterrupt()为设置下次中断时间的方法。具体源码如下:
private void scheduleInterrupt() {
int delay = Stats.TimerTicks;
delay += Lib.random(delay / 10) - (delay / 20);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值