nachos下的线程编程
一、 实验内容
本次实验的目的在于掌握使用nachos中的线程来解决较为复杂的并发问题。实验内容分三部分:实现事件栅栏原语并进行正确性测试;实现闹钟原语并进行正确性测试;利用事件栅栏和闹钟原语来解决电梯问题
- 实现事件栅栏原语
- 事件栅栏(EventBarrier)是一种同步机制,使用事件栅栏可以让一组线程以同步的方式等待和应答某事件(Event)。
- 事件栅栏的调用者分为两类:或者调用Wait操作,或者调用Signal操作。使用事件栅栏,无论是Wait操作的调用者或者是Signal操作的调用者最终都将同步地通过栅栏(如果他们原先不同步,则进度较快的线程将通过使自己阻塞而与进度慢的线程同步)。
- Wait操作的调用者在事件栅栏上等待Signal的发出。
- 和信号量类似,Signal操作是可以积累的。
- (没有Waiter的Signal将被记录下来。)
- 执行Wait操作的调用者在收到Signal后必须应答Signal的发出者。
- (调用Complete操作表示应答结束。)
- 只有在所有的Wait操作的调用者都已经应答后,它们才能通过事件栅栏。在所有的Wait操作的调用者都已经应答之前,Signal的发出者必须等待,并保持事件栅栏处于“SIGNALED”状态。类似地,也只有在所有的Wait操作的调用者都已经应答后,Signal的发出者才能离开事件栅栏,并恢复事件栅栏的初始状态“UNSIGNALED”。
- 只允许一个Signal操作的调用者发出Signal并等待应答。
- 实现闹钟原语
- 用Nachos提供的计时器Timer实现闹钟需要对Nachos有较深入的了解。这里的Timer是在虚拟机上模拟实现的:它的滴答不是硬件产生,而是模拟的,通常用一条指令的执行时间表示一个滴答。因此闹钟的时间也是模拟的,我们无法将这样的时间与现实世界的时间准确对应,因为模拟时间的快慢与实际硬件平台的快慢密切相关。
- 请仔细阅读machine/timer.h,machine/timer.cc
- machine/interrupt.h,machine/interrupt.cc,threads/system.h,threads/system.cc。
- Alarm类的接口定义和实现应分别包含在Alarm.h和Alarm.cc中,请自己新建这些文件,并添加构造和析构函数,并注意修改文件nachos-3.4/code/Makefile.common里的有关部分(具体项目可以参考实验一的说明)。
- Alarm必须提供某个方法(callback function),该方法由Timer的中断处理函数调用以处理闹钟时间耗尽事件。但请注意,简单地把方法的首地址作为回调函数的指针是错误的,这里涉及到对象的方法和普通函数的区别。如何处理这个问题,请参考machine/timer.cc,看看Timer是如何把间接地让interrupt处理器调用Timer中的方法。
- 请注意:不要试图通过修改Timer类或Interrupt类以实现Alarm;在实现Alarm时不要试图建立新的Timer对象。因为:Timer和Interrupt都是硬件设备!
- 另请注意,比较合理的实现是:整个系统有唯一的Alarm对象,该对象处理所有的闹铃请求。
- 解决电梯问题
- 这部分实验所需要的头文件是Elevator.h,其中包含了相应的接口声明。
- 你的实现应该放在Elevator.cc里。请自己新建该文件,并注意修改Makefile文件nachos-3.4/code/Makefile.common里的有关部分(具体项目可以参考实验一的说明)
- Elevator.h中包括了一段注释了的代码,该代码是乘客的代码片段,请仔细阅读并根据需要修改。
- 每个电梯都是一个线程;每个乘客都是一个线程;Building不需要单独的线程。这些线程之间的同步可以使用任何可用的同步机制,包括事件栅栏。值得注意的是,在这个模型里有多处可以使用事件栅栏来同步。例如,电梯的开门事件(这里电梯是调用Signal者;乘客是调用Wait者)。
二、 实验思路
首先使用Semaphore来编写锁,相应的接口在synch.h中,对于synch.h,我们只要在类里面添加某些变量即可。然后对于synch.cc,我们的具体实现要在这里编写。只要补全空的构造函数和析构函数即可。特别注意,其中的环境变量要和锁一起使用,线程在Sleep之前要释放锁,在恢复执行后要重新获取锁。编写完后,在dllist.h中引用synch.h,然后在类中定义锁,在dllist.cc中新建锁后就可以使用了。在dllist.cc中,移除节点的时候,如果遇到链表是空的,要listempty->Wait(lock),在sortedinsert和sortedremove中,进入函数的时候加锁lock->Acquire(),出去的时候释放锁lock->Release()。并且,在sortedinsert出去的时候,同样要发listempty->Signal(lock)。在dllist-driver.cc和threadtest.cc中,分别定义一个外部锁即可。用Thread::Sleep实现与上面的思路类似。至于实现一个线程安全的表结构和实现一个大小受限的缓冲区,有了以上的编写经验,然后参照头文件的接口声明编写相关函数即可。
三、 实验结果
PS:本次实验默认情况下是5个乘客10个楼层
1)
实验中第一个参数是:乘客数 第二个参数是楼层数:比如
四、 实验总结
实验过程中又不懂的就看nachos中文教程及实验上的ppt指导主要理解到:
时间栅栏
Wait
Wait操作的调用者在事件栅栏上等待Signal的发出。
收到Signal后调用Complete操作表示应答结束。
Wait操作的调用者与Signal操作的调用者最终都将同步地通过栅栏
Signal的发出者必须等待,并保持事件栅栏处于“SIGNALED”状态。
直到所有的Wait操作的调用者都已经应答之后才能通过事件栅栏
然后离开事件栅栏并恢复事件栅栏的初始状态“UNSIGNALED”
timer类的实现很简单,当生成出一个Timer类的实例时,就设计了一个模拟的时钟中断。这里考虑的问题是:怎样实现定期发生时钟中断?
一旦中断时刻到来,立即进行中断处理,处理结束后并没有机会将下一个时钟中断插入到等待处理中断队列。
调用TimerHandler函数,其调用TimerExpired方法,该方法将新的时钟中断插入到等待处理中断队列中,然后再调用真正的时钟中断处理函数。这样Nachos就可以定时的收到时钟中断。
处理始终的中断处理函数在interrupt类当中。
这个函数又加入了一个了刚才一模一样的中断,这样的话就可以实现每隔一段时间就发一个类似的中断,然后再执行中断处理函数,这个中断处理函数是在构造函数中赋值的,专门用来处理时钟中断
而闹钟
第一步: timer = new Timer(TimerInterruptHandler, 0, randomYield); 在终端等待队列中添加一个时钟中断,中断参数就是本对象
第二步 固定时间后固定中断会自动产生(系统固定时钟中断),然后判断是否就是时钟中断,如果是,则通过里面的中断参数调用时钟中断处理函数,中断处理函数首先是添加一个时钟中断,然后是调用中断的另一个参数,该参数就是实际中断处理函数。(也就是timer类里面的第一个参数)
第三步 重复第二步
电梯:
对于电梯主要是需要用到前面做过的实验,主要是事件栅栏(用于人到电梯前时们还没开则堵塞到向上或向下的栅栏上,在电梯里面则根据电梯现在在哪,堵塞到相应的楼层的事件栅栏上面等到电梯开门在一起出去,同时最后一个告诉电梯关门)信号量(1判断是否有乘客要用电梯否则把电梯进程堵塞empty信号量上面去)闹钟则是用来限制人到底以多大的频率使用电梯通过闹钟的pause()把自己堵塞到闹钟的堵塞队列知道时间到在将进程通过调度器:scheduler调度到就绪队列当中(该就绪队列的具体实现在scheduler类里面的readyList)锁则用到了每次只能一个进程运行改变每层楼上面的等待电梯向上或向下的人数计数等。
通过本次实验把书上的知识彻底的由抽象到直观,对书上的信号量,锁互斥等概念有个更好的理解。