nachos系统实验报告:实验二 下载本文

//两个线程的Remove都遇到空链表的情况

//中间的这么多Switch时因为链表函数的代码中插入的

currentThread->Yield(); 强制切换,用于检测锁机制的正确性。

//插入结束,之后开始删除操作

//运行结束,链表元素全部删除

2.Table类的实现: a)Table的实现较为简单,这里简单贴一下它的主要代码。 Entery辅助类的定义: class Entery{ private: void *object ; public: Entery() { object = NULL ; } void SetObj (void *item) { object = item ;} void *GetObj () { return object ;} };

Table类定义:

Table类中主要函数为Alloc,别的函数实现简单,这里不做解释 int Table::Alloc(void* object) { lock->Acquire() ; for (int i = 0; i < size ;i++){ if (entery[i].GetObj() == NULL){ //若entery[i]==NULL则表示这个 entery[i].SetObj(object); //位置没有分配过,找到位置则为其 lock->Release() ; //非为空间 return i ; } } lock->Release() ; return -1 ; //表示没有多余的slot分为给object } b)对Table函数一个简单的测试先Alloc 3 个slot再Release 3个slot如右图所示, 结果如预期正确。

class Table { public: Table(int size); int Alloc(void *object); void *Get(int index); void Release(int index); private: int size ; Entery *entery ; Lock *lock ; }; // create a table to hold at most 'size' entries. //返回第index位置的Entery的元素,没有则返回NULL // free a table slot // 储存其大小 // Entery数组,大小为size // 锁 3.BoundBuffer的实现 a)主要代码分析 BoundBuffer类中 读与写 函数的size大小都可好过类本身的buffer大小。当无 数据可读时则挂起程序,等待buffer中重新填入数据,由 调用写函数的 线程来唤醒 因无数据可读而挂起的程序,把其加入就绪队列。 BoundBuffer类中主要是Read和 Write函数的实现。

void BoundedBuffer::Read(void* data, int size) { lock->Acquire(); ASSERT (used >= 0 || used <= bufferSize) //检测读写过程中是否出现错误 s = (char*)data ; for (i = 0; i < size ;i++){ while (IsEmpty()){ ReadEmpty->Wait(lock); //无资源可读,挂起线程 } s[i] = buffer[start] ; //读取一个数据 used-- ; //使用空间减少1 start = (start + 1) % bufferSize ; WriteFull->Signal(lock) ; //将因 无资源可读 而挂起的线程 加入就绪队列 } s[i] = '\\0' ; lock->Release() ; } class BoundedBuffer { public: BoundedBuffer(int maxsize); void Read(void *data, int size); void Write(void *data, int size); private: bool IsEmpty () { return used == 0 ;} bool IsFull () { return used == bufferSize ;} void ShowBuffer () ; //打印buffer内容 (debug用) char *buffer ; //buffer用于储存数据 int bufferSize ; //记录buffer的大小 int start ; //用于记录buffer的头位置 int end ; //用于记录buffer的尾位置 int used ; //记录多少空间已写 LockO *lock ; ConditionO *WriteFull ; //用于挂起和释放写进程 ConditionO *ReadEmpty; //用于挂起和释放读进程 LockO *sublock ;

void BoundedBuffer::Write(void* data, int size) { char *s ; lock->Acquire() ; ASSERT (used >= 0 || used <= bufferSize) s = (char *)data ; for (int i = 0; i < size ;i++){ while (IsFull()) { WriteFull->Wait(lock); //无空间可写, 挂起线程 } buffer[end] = s[i] ; //写入一个数据 used++ ; //已用空间增加 end = (end + 1) % bufferSize ; ReadEmpty->Signal(lock); //将因 无资源可写 而挂起的线程 加入就绪队列 } lock->Release(); }

因为buffer每次的读写都会 进行ReadEmpty->Signal(lock) / WriteFull->Signal(lock)所 已不会出现buffer中的数据还可以 读或写 时还有线程被挂起在conditionLock中 b)举例测试分析 1)运行三个线程对BoundBuffer进行测试,前两个线程进行读,第三个线程 进行写。因为前两个进程进行写时,buffer中还没有数据所以此时,前 两个线程应该先直接被挂起,由第三个线程来唤醒它们。

//Thread0和Thread1

因为buffer没数据而挂起,

//Thread2进行写数据此时已经Thread0和Threat1唤醒

//Thread1要读取的个数大于一个,所以读取一个后Thread1挂起

//这里面已将

Thread1唤醒,放到就绪队列

//Thread继续读取(其中a8为上次读取的)

程序运行结果和预期想通过结果正确。

2)第二个测试. 此时也有三个线程同时运行,与1)相同此时前两个线程读取,第三个 写入。不同的是,此时写入和读取的大小是随机的,写入时大小为0~39随即 数,而读取时大小为0~19随机数。且每个线程各自循环100次。(将写入设 的大于读取避免写入过早运行完,读取的都进入挂起状态程序结束)读取和写入函数都有ASSERT (used >= 0 || used <= bufferSize) 用于检查错误。 最终结果程序可以正常运行至结束,整个过程设置的ASSERT没有报错。 因结果过长这里不放截图。

四.实验总结

1.本次实验Lock和conditonLock用于实现锁机制,Table用于以后实验, 保存进程信息,线程信息,页框信息等等。BoundBuffer用于磁盘读写的 缓冲区。

2.conditionLock是在一个线程拥有Lock的情况下因为某种情况需要挂起而使用的。conditionLock操作必须在线程拥有Lock时完成,此时需要Lock->Acquire()放在conditionLock前面以保持条件的原子性,防止检查条件时,条件被改变。

3.Table的结构适合用来储存单个物体,而Buffer适合用于储存多个物体。代表Buffer头和尾的指针会随着Read和Write的操作不断变化,不适合对单个物体的存储和定位适合储存多个连续的资源。而Table变量可以将PID(进程ID)设为Index,当寻找进程等单个资源时较方便。

这次实验中我在实验前并没有完全弄清楚每个小实验所设置的类它们真正的作用及含义,如conitionLock变量设置的原因等。nachos实验课是很好的锻炼机会,要努力去弄明白每个类作用,如此设置类定义的原因。更好的去理解操纵系统的工作原理。