TCB任务控制块
processArgs()检测参数信息
new TCB.start(…)进入TCB类,调用构造函数初始化变量。、
创建一个新KThread:
如果当前线程为空,即第一个是主线程。将tcb块置为当前的TCB。实例化readyQueue为readyQueue分配参数。
如果当前线程不为空,则为当前线程实例化tcb。
KThread执行fork():
tcb.start():如果不是第一个线程,将TCB的running状态置为true,等待被start。则会执行waitForInterrupt()等待。
最高,将KThread加入readyQueue。
在KThread被创建的时候,要添加一个waitQueue。即每一个KThread对应一个waitQueue,表明正在等待该线程执行完毕的所有线程。
一个线程的run()方法中,例如A调用B.join()
(A,B均为KThread对象),在调用 join()方法时,让当前的运行线程休眠。即A被挂起,加入到B的等待队列(waitQueue)中。B线程运行结束后,会调用Finish()方法从等待队列中取出一个KThread加入就绪队列,A才有可能继续运行。而且join()函数只会被调用一次,而且调用join()的不能是当前线程,所以给出如下断言。
finish()方法在runThread()中自动调用。但当前唯一运行的线程不能立刻被结束,需要在其他的线程begin()方法中保存运行状态,结束线程。
join()方法:
若当前线程状态非完成状态,则将线程挂到调用join()线程的等待队列(waitQueue<waitQueue是一个KThread的内部等待队列>)当中,并将currentThread.sleep(){
会将currentThread的状态置为 阻塞,并执行下一个线程}
finish()方法:
从当前线程的等待队列(waitQueue)中ready(){将线程加入就绪队列(readyQueue)}一个线程,并将自己sleep()。
条件变量使线程可以睡眠等待某种条件出现,利用线程间共享全局变量进行同步的一种机制。其中有sleep(){使一个线程等待条件变量成立}、wake(){给出条件成立}。为了防止竞争,条件变量和一个互斥锁结合。锁也有自己的等待队列,一个线程要求获取锁(lock.acquire())的时候需要判断当前锁是否被占用,如果被占用则加入锁的等待队列。
声明互斥锁(conditionLock)和等待队列(waitqueue)
实现3个方法:sleep(),wake(),wakeAll()。条件变量是基于锁的,锁用来控制线程的互斥,条件变量用来控制公共资源的同步访问。
sleep():需要检查当前线程是否抓住锁。如果抓住锁,先关中断原子执行,再释放锁,然后将当前线程加入该条件变量的等待队列(sleep())。最后线程睡眠。即调用这个方法的线程将自己挂起到调用方法的条件变量的等待队列,阻塞自己的同时将自己拥有的锁交出去,被唤醒后等待锁的分配。
wake():唤醒条件变量等待队列中第一个线程。
timerInterrupt()在每一次timer产生时钟中断时遍历链表,检查链表中每个线程的信息,当满足等待时间(当前时间<list中的某个时间)时就把线程从队列中取出放入就绪队列。
waitUntil()使用一个链表可以存放线程以及线程信息。调用时,计算唤醒时间后,创建线程信息对象。将对象加入等待链表,等待时钟中断唤醒。
当前线程睡眠至少x时间周期,在定时器中断处理程序中将其唤醒。
注意:每次alarm()唤醒一个线程执行,该线程执行的时候会切换。执行时会导致timer又跳过500个滴答,所以即使两个线程的等待时间很近,但是执行时间会差500左右。
使一个通许对象实例中多个speaker和listener能够相互通讯。每一个Communicator类都有锁(保证操作的原子性)和两个条件变量用于保证speaker和listener的同步。
声明 speaker条件变量、listener条件变量、一个用于存储话的队列
speak():先获得锁,再检查是否有listener等待。如果有听者,将说的话放入语言队列,并唤醒一个listener。如果没有listener等待则将speaker等待的数量+1,将话放入语言队列后在speaker条件变量下sleep()。当speaker被唤醒时继续执行:应该去唤醒一个listener并将等待的speaker数量-1。
listener():先获得锁,如果speaker数量不为0则唤醒一个speaker;否则将listener数量+1后在listener条件变量下睡眠。
由于线程被fork()后是被加入readyQueue,按照readyQueue中的线程顺序执行。
有几种情况,程序执行的情况说明:
说者声明为s1,s2;听者声明为l1,l2;
说说听听
s1进入,listener=0;听者数量+1,保存内容,s1在speaker条件变量waitQueue中睡眠。
s2进入,listener=0;听者数量+1,保存内容,s1在speaker条件变量waitQueue中睡眠。
l1进入,speaker!=0,唤醒一个听者(s1加入就绪队列readyQueue),l1在listener条件变量waitQueue中睡眠。
l2进入,speaker!=0,唤醒一个听者(s2加入就绪队列readyQueue),l2在listener条件变量waitQueue中睡眠。
执行完上述过程后 readyQueue{s1,s2}
speaker.waitQueue{}
listener.waitQueue{l1,l2}
按照readyQueue中的顺序继续执行
s1被唤醒,继续执行。唤醒一个听者(l1加入就绪队列readyQueue),将说者数量-1,释放锁,返回。输出,s1执行结束。 s2被唤醒,继续执行。唤醒一个听者(l2加入就绪队列readyQueue),将说者数量-1,释放锁,返回。输出,s1执行结束。
l1被唤醒,继续执行。返回内容顶,输出,l1执行结束。
l2被唤醒,继续执行。返回内容顶,输出,l2执行结束。
说听说听:能够按照正常的执行流程进行执行
说听听说
听说说听
听说听说
听听说说
Note that:
you will need to change a line in nachos.conf that
specifies(指定)the scheduler class to use.
implement the methods:
must: getPriority(),getEffectivePriority(),setPriority()
optional: increasPriority(),decreasePriority()
primary idea:
在选择哪一个线程出等待队列时,应选择一个有最高有效优先级的线程。如果多个具有相同最高优先级的线程等待中,则选择一个在等待队列时间最长的线程
issue:priorityinversion
等待中的线程把它的优先级捐献给低优先级的线程。
priority donation implementationdetails:
一个线程的有效优先级是通过计算捐献者和接收者的 最大的优先级。
一个线程捐献优先级后不会丢失自己的优先级。
优先级捐献是传递的,A→B,B→C,则C具有A、B、C中的最高优先级。
LinkList<ThreadState>waitQueue = new LinkedList<ThreadState>();
waitQueue中保存的是线程的由优先级状态
ThreadState:KThread,priority,effectivePriority,waitQueue
ThreadState对象,用来把线程和优先级绑定在一起。内部有一个队列(PriorityQueue对象)用来记录等待它的线程。
getEffectivePriority():计算有效优先级,遍历等待队列中所用线程的有效优先级,找出最大的优先级。首先在PriorityQueue类下创建一个装有KThread的linkedlist,即等待队列waitQueue,声明一个effectivePriority,遍历waitQueue,递归找出EffectivePriority最大的那个KThread,将它的Priority赋值给effectivePriorit。其中,waitQueue中的每个ThreadState对象都遍历自己的waitQueue,获取最高优先级。
waitForAccess()将需要等待获得资源的线程(调用方法的线程)加入一个等待队列等待调度。如果队列的主人不为空,且不是当前的线程,则将该线程还要加入主人的等待队列。
nextThread():返回下一个要执行的线程,遍历队列,计算出所有线程的有效优先级(计算的时候是递归计算每个ThreadState队列中每个线程的有效优先级,大于自己的优先级,则将它的有效优先级赋给自己),取出有效优先级最大的线程执行。
theyhave only one boat which can carry maximally two children or one adult(but notone child and one adult).
Theboat can be rowed back to Oahu,but it requires a pilot to do so.
调用Boat.begin()是父线程,你的调度机制不能依赖事先知道有多少孩子和成人
不能有忙式等待
因为一个船只能载一个成人,成人在两个岛之间往返是无用的,所以成人只用考虑从Oahu岛到Molokai岛。
成人可以从Oahu岛岛Molokai岛的条件:
船在Oahu岛、成人可以出行(Molokai岛至少有一个孩子,因为如果Molokai岛没有孩子则船无法从Molokai岛再返回Oahu岛)
如果成人不可以出行则将成人线程sleep();
否则成人划船去Molokai:
bg.AdultRowToMolokai(); //adult to Molokai
adult_inOahu--; //在Oahu的成人数量-1
adult_inMolokai++; //在Molokai的成人数量+1
adult_can_go = false; //这一次是成人走,下一次必须是孩子走。因为要保证Molokai要至少有一个孩子
boat_Oahu = false; //成人过去了,船也过去了。
成人到达Molokai岛后,wake()一个在Molokaisleep()的孩子,让他将船划回Oahu。
由于成人不能划船,因此孩子必须在Oahu岛和Molokai岛之间往返划船,因此除非旅行结束否则每一个孩子都必须一直做事,因此要设置一个while循环。孩子可能在Oahu岛和Molokai岛
如果船没在Oahu岛、或者成人可以走了。孩子不能出行,则孩子在Oahu 睡眠。
if(!boat_Oahu||adult_can_go||newOne){
//如果船没在Oahu、或者该成人走了,该孩子睡眠
children_condition_Oahu.sleep();
}
否则孩子即将出行:
孩子在船上有两种情况:
把此线程(孩子)的状态设为不在Oahu岛(在Molokai岛),在Oahu岛的孩子减一个,在Molokai岛的孩子增加一个。因为他是pilot则把isPilot设为false,唤醒另一个孩子一起走。到达Molokai岛后sleep。
因为最后一次运送必然是两个孩子划船去M岛,所以孩子作为乘客时要判断运送是否即将完成。(在O岛的成人为0,减少一个作为船长的孩子后只剩一个孩子)。如果此次运送不是最后一次,则下次运送可以是成人(下次船在O岛时,M岛至少有一个小孩),如果成人数量不为0,将成人运输标志置为true。在此乘客与船长孩子过去后,就没有孩子船长了,isPilot置为true为下次运输设置变量。如果此时运输没有完成,唤醒一个M岛的孩子后,睡眠。
划船去O岛,设置孩子状态、船状态,通过状态判断下一次该谁走了就去唤醒谁:如果成人数量为0,则唤醒孩子;否则,根据是否满足成人可以走的条件,唤醒成人或孩子。