山东大学计算机学院操作系统实验报告.docx
- 文档编号:30546011
- 上传时间:2023-08-16
- 格式:DOCX
- 页数:42
- 大小:31.73KB
山东大学计算机学院操作系统实验报告.docx
《山东大学计算机学院操作系统实验报告.docx》由会员分享,可在线阅读,更多相关《山东大学计算机学院操作系统实验报告.docx(42页珍藏版)》请在冰豆网上搜索。
山东大学计算机学院操作系统实验报告
操作系统课程设计报告
学院:
计算机科学与技术学院
专业:
计算机科学与技术
班级:
20**级*班
姓名:
***
学号:
20**********
一实验平台
开发语言:
Java
开发工具:
EclipseLuna
操作系统:
Ubuntu14.04
二Project1建立线程系统
Task1.1实现KThread.join()
1.要求
实现ImplementKThread.join()函数。
注意:
其它的线程不必调用join函数,但是如果它被调用的话,也只能被调用一次。
对join函数第二次调用的执行结果是不被定义的(即使第二次调用的线程与第一次调用的线程不同)。
2.分析
Join函数的作用即为等待某线程运行完毕.当前线程(唯一一个正在运行的线程)A调用另一个线程(处于就绪状态)B的join函数时(A和B在Nachos中均为KThread类型对象),A被挂起,直到B运行结束后,join函数返回,A才能继续运行。
3.方案
原KThread的join()中的Lib.assertTrue(this!
=currentThread)已经实现线程只能调用一次join()方法,根据要求,在调用join()方法时,让当前运行线程休眠,并将当前运行的线程加入到一个阻塞队列中。
在线程结束时,finish()函数循环唤醒所有被阻塞的线程。
4.实现代码
publicvoidjoin()
{
Lib.debug(dbgThread,"Joiningtothread:
"+toString());
Lib.assertTrue(this!
=currentThread);
booleanintStatus=Machine.interrupt().disable();
if(status!
=statusFinished)
{
waitForJoin.waitForAccess(currentThread);
KThread.sleep();
}
}
publicstaticvoidfinish()
{
Lib.debug(dbgThread,"Finishingthread:
"+currentThread.toString());
Machine.interrupt().disable();
Machine.autoGrader().finishingCurrentThread();
Lib.assertTrue(toBeDestroyed==null);
toBeDestroyed=currentThread;
currentThread.status=statusFinished;
KThreadwaitThread;
while((waitThread=currentThread.waitForJoin.nextThread())!
=null)
{
waitThread.ready();
}
sleep();
}
Task1.2利用中断提供原子性,直接实现条件变量
1.要求
通过利用中断有效和无效所提供的原子性实现条件变量。
我们已经提供类似的例子用例实现信号量。
你要按此提供类似的条件变量的实现,不能直接利用信号量来实现(你可以使用lock,虽然它间接地调用了信号量)。
在你完成时要提供条件变量的两种实现方法。
你的第二种条件变量实现要放在nachos.threads.Condition2中。
2.分析
threads.Lock类提供了锁以保证互斥.在临界代码区的两端执行Lock.acquire()和Lock.release()即可保证同时只有一个线程访问临界代码区.条件变量建立在锁之上,由threads.Condition实现,它是用来保证同步的工具.每一个条件变量拥有一个锁变量(该锁变量亦可被执行acquire和release操作,多个条件变量可共享同一个锁变量).当处于临界区内的拥有某锁L的当前线程对与锁L联系的条件变量执行sleep操作时,该线程失去锁L并被挂起.下一个等待锁L的线程获得锁L(这个过程由调度程序完成)并进入临界区.当拥有锁L的临界区内的当前线程对与锁L联系的条件变量执行wake操作时(通常调用wake之后紧接着就是Lock.release),等待在该条件变量上的之多一个被挂起的线程(由调用sleep引起)被重新唤醒并设置为就绪状态.若执行wakeall操作,则等待在该条件变量上的所有被挂起的线程都被唤醒.
3.方案
condition.sleep采用waiter.P()实现休眠(waitor是一个信号量)并将waitor放入信号量队列,在我们的实现中改成用KThread.sleep()实现休眠并将当前线程放入线程队列,并在sleep函数开始/结尾处屏蔽/允许中断以保证原子性。
condition.wake中从等待信号量队列中取出信号量并对其进行V操作实现唤醒,在我们的实现中改成从线程队列中取出线程用KThread.ready()实现唤醒(同样要在wake函数开始/结尾处屏蔽/允许中断)。
wakeall函数的实现依赖于wake().只需不断地wake直到队列为空为止.
4.实现代码
privateThreadQueuewaitQueue=
ThreadedKernel.scheduler.newThreadQueue(false);
privatebooleanhasWaiter=false;
publicvoidsleep()
{
Lib.assertTrue(conditionLock.isHeldByCurrentThread());
booleanintStatus=Machine.interrupt().disable();
waitQueue.waitForAccess(KThread.currentThread());
hasWaiter=true;
conditionLock.release();
KThread.sleep();
conditionLock.acquire();
Machine.interrupt().restore(intStatus);
}
publicvoidwake()
{
Lib.assertTrue(conditionLock.isHeldByCurrentThread());
booleanintStatus=Machine.interrupt().disable();
KThreadthread=waitQueue.nextThread();
if(thread!
=null)
thread.ready();
else
hasWaiter=false;
Machine.interrupt().restore(intStatus);
}
publicvoidwakeAll()
{
Lib.assertTrue(conditionLock.isHeldByCurrentThread());
while(hasWaiter)
wake();
}
Task1.3实现waitUntil
1.要求
通过实现waitUntil(intx)方法来完成Alarm类。
2.分析
一个线程通过调用waitUntil函数来挂起它自己,直到now+x后才被唤醒。
在实时操作中,对线程是非常有用的,例如实现光标每秒的闪烁。
这里并不要求线程被唤醒后马上执行它,只是在它等待了指定时间后将它。
放入等待队列中。
不要通过产生任何附加的线程来实现waitUntil函数,你仅需要修改waitUntil函数和时间中断处理程序。
waitUntil函数并不仅限于一个线程使用,在任意时间,任意多的线程可以调用它来阻塞自己。
3.方案
于Alarm类有关的是machine.Timer类.它在大约每500个时钟滴答使调用回调函数(由Timer.setInterruptHandler函数设置).因此,Alarm类的构造函数中首先要设置该回调函数Alarm.timerInterrupt().
为了实现waitUntil,需要在Alarm类中实现一个内部类Waiter,保存等待的线程及其唤醒时间.在调用waitUntil(x)函数时,首先得到关于该线程的信息:
(线程:
当前线程,唤醒时间:
当前时间+x),然后构造新的Waiter对象,并调用sleep操作使当前线程挂起.在时钟回调函数中(大约每500个时钟间隔调用一次)则依次检查队列中的每个对象。
如果唤醒时间大于当前时间,则将该对象移出队列并执行wake操作将相应线程唤醒。
4.实现代码
classWaiter
{
Waiter(longwakeTime,KThreadthread)
{
this.wakeTime=wakeTime;
this.thread=thread;
}
privatelongwakeTime;
privateKThreadthread;
}
publicvoidwaitUntil(longx)
{
booleanintStatus=Machine.interrupt().disable();
longwakeTime=Machine.timer().getTime()+x;
Waiterwaiter=newWaiter(wakeTime,KThread.currentThread());
waitlist.add(waiter);
System.out.println(KThread.currentThread().getName()
+"线程休眠,时间为:
"+Machine.timer().getTime()+",应该在
"+wakeTime+"醒来.");
KThread.sleep();
Machine.interrupt().restore(intStatus);
}
publicvoidtimerInterrupt()
{
Waiterwaiter;
for(inti=0;i { waiter=waitlist.remove(); if(waiter.wakeTime<=Machine.timer().getTime()) { System.out.println("唤醒线程: "+waiter.thread.getName()+", 时间为: "+Machine.timer().getTime()); waiter.thread.ready();//线程进入就绪状态 } else waitlist.add(waiter); } KThread.currentThread().yield(); } privateLinkedList Task1.4用条件变量,不使用信号量,实现同步发送接收消息,speak,listen 1.要求 使用条件变量来实现一个字长信息的发送和接收同步。 使用voidspeak(intword)和intlisten()函数来实现通讯(Communicator)类的通讯操作。 speak函数具有原子性,在相同地Communicator类中等待listen函数被调用,然后将此字发生给listen函数。 一旦传送完毕,两个函数都返回(listen函数返回此字)。 2.分析 对一个Communicator类的对象c,线程A先调用c.speaker(x)发送一个字后被挂起,直到另一线程B调用c.listen()收到这个字x后,A和B同时返回.类似地,线程B先调用c.listen(x)后被挂起,直到另一线程B调用c.speaker(x)发送一个字后,A和B同时返回.同时需要注意在一个Communicator上有多个spaker和listener的情形.此时的speaker和listener只能是一对一的,即一个speaker只能将数据发送到一个listener,一个listener也只能接收来自一个spekaer的数据,其余的speakers和listeners都需要等待. 3.方案 每个Communicator有一个锁(保证操作的原子性)和与该锁联系的两个条件变量用于保证speaker和listener间的同步.在speak函数中,首先检查若已经有一个speaker在等待(speaknum>0)或无listener等待,则挂起.否则设置变量,准备数据并唤醒一个listener.在listen函数中,增加一个listener后,首先唤醒speaker,然后将自己挂起以等待speaker准备好数据再将自己唤醒.这个问题其实是一个缓冲区长度为0的生产者/消费者问题. 4.实现代码 publicCommunicator() { lock=newLock(); con=newCondition(lock); } publicvoidspeak(intword) { lock.acquire(); if(speaknum>0||listennum==0) { speaknum++; con.sleep(); } if(listennum>0) { con.wakeAll(); listennum=0; } this.word=word; System.out.println(KThread.currentThread().getName()+"线程说"+this.word); lock.release(); } publicintlisten() { lock.acquire(); while(listennum>0||speaknum==0) { listennum++; con.sleep(); listennum--; } if(speaknum>0) { con.wake(); speaknum--; } KThread.currentThread().yield(); System.out.println(KThread.currentThread().getName()+"线程听到"+this.word); listennum=0; lock.release(); returnthis.word; } privateLocklock; privateConditioncon; privateintword; privatestaticintspeaknum; privatestaticintlistennum; Task1.5完成PriorityScheduler实现优先级调度 1.要求 通过完成PriorityScheduler类在Nachos中实现优先级调度(priorityscheduling)。 优先级调度是实时系统中的关键构建模块。 2.分析 在Nachos中,所有的调度程序都继承抽象类Scheduler.系统已经提供了一个简单的轮转调度器RoundRobinScheduler,它对所有线程不区分优先级而采用简单的FIFO队列进行调度.我们实现的优先级调度类PriorityScheduler也继承自Scheduler. 优先级调度的传统算法如下: 每个线程拥有一个优先级(在Nachos中,优先级是一个0到7之间的整数,默认为1).在线程调度时,调度程序选择一个拥有最高优先级的处于就绪状态的线程运行.这种算法的问题是可能出现“饥饿”现象: 设想有一个低优先级的线程处于临界区中运行而高优先级的线程在临界区外等待.由于前者优先级较低,它可能不会被调度器选中从而高优先级的线程也不得不浪费时间等待. 为解决上述优先级反转问题,需要实现一种“让出”优先级的机制(PriorityDonation): 提高拥有锁的低优先级线程的优先级以使它迅速完成临界区,不使其它较高优先级的线程等待太久.提高后的优先级称为有效优先级,它可以不断变化.实际调度时就是以有效优先级为评判标准的. 3.方案 在ThreadState类中增加两个表即LinkedList<>类,存放的对象是PriorityQueue,即优先级队列对象。 一个表用来记录该线程所占用资源的优先队列resourcesIHave,另一个表用来记录该线程所想占有的资源的优先队列resourceIWant。 resourcesIHave作为发生优先级反转时,捐献优先级计算有效优先级的来源依据,resourceIWant用来为线程声明得到资源做准备。 waitForAccess()将需要等待获得资源的线程加入一个等待队列等待调度。 getEffectivePriority()计算有效优先级时,遍历等待队列中所用线程的有效优先级,找出最大的优先级即可。 4.实现代码 publicvoidwaitForAccess(PriorityQueuewaitQueue) { waitQueue.waitQueue.add(this.thread); if(! waitQueue.transferPriority) waitQueue.lockHolder.effectivePriority=expiredEffectivePriority; } publicvoidacquire(PriorityQueuewaitQueue) { waitQueue.waitQueue.remove(this.thread); waitQueue.lockHolder=this; waitQueue.lockHolder.effectivePriority=expiredEffectivePriority; waitQueue.lockHolder.waiters=waitQueue; } publicintgetEffectivePriority() { if(effectivePriority! =expiredEffectivePriority) returneffectivePriority; effectivePriority=priority; if(waiters==null) returneffectivePriority; for(Iteratori=waiters.waitQueue.iterator();i.hasNext();) { ThreadStatets=getThreadState((KThread)i.next()); if(ts.priority>effectivePriority) { effectivePriority=ts.priority; } } returneffectivePriority; } protectedinteffectivePriority=expiredEffectivePriority; protectedstaticfinalintexpiredEffectivePriority=-1; protectedPriorityQueuewaiters=null; publicKThreadnextThread() { Lib.assertTrue(Machine.interrupt().disabled()); if(pickNextThread()==null) returnnull; KThreadthread=pickNextThread().thread; getThreadState(thread).acquire(this); returnthread; } protectedThreadStatepickNextThread() { if(waitQueue.isEmpty()) returnnull; ThreadStatetoPick=getThreadState((KThread)waitQueue.getFirst()); for(Iteratori=waitQueue.iterator();i.hasNext();) { ThreadStatets=getThreadState((KThread)i.next()); if(ts.getEffectivePriority()>toPick.getEffectivePriority()) toPick=ts; } returntoPick; } LinkedList ThreadStatelockHolder=null; Task1.6 1.要求 用以上实现的线程互斥/同步机制解决一个过河问题。 成人和小孩都试图从oahu出发到molokai。 一只船只可以携带最多两个小孩或一个成人(但不能是一个小孩和一个成人)。 船上可划回瓦胡岛,但这么做需要一个引航员。 安排一个能让所有人到molokai岛的解决方案 2.分析 需要记录的信息: 1)O岛上大人/小孩的人数 2)M岛上大人/小孩的人数 3)船的位置(在O岛还是M岛) 4)船的状态(空/半满/全满)(半满指只有一个小孩,全满指有两个小孩或一个大人) 初始状态: 1)大人和小孩都在O岛上 2)船在O岛 3)船为空 对于大人比较简单.若满足以下条件则独自乘船过河(每个大人过且仅过一次河,线程即告结束),否则(在O岛)等待: 1)O岛上只有一个小孩或没有小孩 2)船在O岛 3)船为空 对于小孩,分以下5种情况讨论 1)某小孩在O岛,船在O岛,船为空,O岛上的小孩数大于等于2: 该小孩上船等另外一个小孩上船后,两人一起划船过河到M 2)某小孩在O岛,船在O岛,船为空,O岛上没有大人: 该小孩上船过河 3)某小孩在O岛,且不属于以上三种情况: 等待 4)某小孩在M岛,船在O岛: 等待 5)当所有的大人运完了之后开始运大人,当运过去两个大人后,O岛出现了两个孩子,这个时候这两个孩子划船过河,即使此时大人还没有完全被运送完全。 返程: 只有小孩可以有返程路线,大人返程没有意义。 3.方案 使用三个锁变量保证互斥,三个条件变量保证同步。 4.实现代码 packagenachos.threads; importnachos.ag.Boa
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 山东大学 计算机 学院 操作系统 实验 报告