[山东大学操作系统课程设计]实验三

0.写在前面(重点)

由于一些突发事件,导致目前大家手里或多或少都有了完整版的答案了。甚至很多学长学姐们写的代码远比我写的要好很多。

但是这个系列我觉得还是稍微坚持下去一点,或许某些地方可以帮到未来的同学们。

还是那句话,有需要可以随时向我反馈你遇到的问题,你的指点就是我最大的动力

1.实验代码解析

注意,这个实验比较特殊,不是想以前一样,直接从nachos源码文件中复制文件到本目录下,然后做拓展。而是重新创建一个新的文件,实现一些功能。

这个文件叫什么,local文件中新增的条目已经向我们指明了,threadsbar.cc()函数。

而且这个函数需要重写什么东西,也很容易了解了,就在这个文件中[山东大学操作系统课程设计]实验三_第1张图片

至于实验大纲中提到的,关于那个n屏障,我酱在下一个小目录中实现。

2.手把手攻略

这里要说一下n屏障机制

N线程屏障(N-thread barrier)是一种同步机制,用于确保多个线程在达到某个点之前都会被阻塞,直到所有线程都到达该点后才能继续执行。它提供了一种线程同步的方式,以便在多线程环境中协调并发操作。

N线程屏障中的N表示参与屏障同步的线程数量。当N个线程都到达屏障位置时,屏障将打开,所有线程将被释放并可以继续执行后续的操作。如果有任何一个线程到达屏障位置之前发生阻塞(未到达屏障位置),则其他线程也会被阻塞,直到所有线程都准备好后才会继续执行。

而对于这个代码段,我估计写过go语言的同学都不陌生

rendezvous

mutex . wait ()
count = count + 1
mutex . signal ()

if count == n: barrier . signal ()

barrier . wait ()
barrier . signal ()

critical point

这是一个比较典型的创建n屏蔽的过程

首先mutex在go语言中,我们经常称之为互斥锁,互斥锁内的资源,每次之允许一个线程进行访问,其他的线程将会存入队列中等待被执行。在nachos中,使用Semaphore实现了mutex这个东西。我们可以用图上的方式完成这些操作

而barrier其实也是通过Semaphore来实现的,在这里我们先不关心是如何实现的,具体的实现和定义我们会放在下面的代码中解释。barrier理想中的状态,是在代码的某处拦住所有的线程,当某个线程发送了signal这个指令的时候,其他被阻塞的线程就”跨过“自己当前所在的wait语句。

因此这一整段代码实现的就是,在第n个线程到达之前,前面n-1个线程都处在被阻塞的状态,第n个线程到达if语句以后,经过判断释放signal信号,让其他线程可以脱离。

而自身则可能会被wait洽住,但是此时已经”逃脱“的线程,重新发送了信号。这样让第n个县城也可以顺利脱困

(至于这个东西需要不需要时间机制。。。我想说的事barrier只是一个逻辑上的概念,尤其是在nachos上的模拟实现,所以底层是存在信号量这一资源的,所谓的”信号“也是通过资源数目来确定能否通行,比如barrier本质上是一个初始资源数目为0的semaphore对象,而mutex为初始资源数目为1的semaphore对象)

3.实验过程分析

首先说明,具体的实验流程和名称在这里先简写了,所以题目可能对不上,不过内容是大致一致的

3.1:分析说明nachos信号量如何实现

信号量主要依靠如下semaphore个类的实现

[山东大学操作系统课程设计]实验三_第2张图片

该类中使用P V两个函数来完成信号量增加和减少的原语操作

另外这个类中还使用了value来模拟”可支配资源“的数目,以及信号等待队列List的实现,这个队列中用来阻塞线程。

3.2:说明并发进程如何创建以及运行

在nachos中,并发进程的创建是通过for循环进行多重创建,生成数个可以并行的thread对象,如图所示

在nachos中,默认每个时刻只有一个线程在运行,因此如果需要调度的话需要Threads类中的yield,sleep,以及finish方法进行辅助。并且还需要scheduler中的这几个方法作为调度逻辑

并发的执行运行则可以在scheduler.cc中的方法看到

ReadyToRun负责进行从就绪转化为运行态

FindNextToRun方法负责将运行态转化为结束态,并且将下一个线程移入

Run方法负责直接执行线程

3.3,修改代码以及实现n屏障机制

在文件夹lab3下面,创建一个名为threadsbar.cc的cpp文件,如图所展示

在lab3文件夹下存在一个readme的文件,里面展示了一个代码框架,我们根据这个代码框架实现了如下的代码,在threadsbar.cc中

//事先说明,需要改动的其实就是这些东西
//我们根据信号量Semaphore这个类来实现互斥锁mutex,以及n线程屏障barrier

//首先要导入一些新的包
#include 
#include 

#include "copyright.h"
#include "system.h"
#include "synch.h"


#define N_THREADS 20    // the number of threads
#define MAX_NAME 16    //设置最大长度“名称”
#define N_TICKS 1000  // the number of ticks to advance simulated time

//创建线程队列
Thread *threads[N_THREADS];
char thread_names[N_THREADS][MAX_NAME]; //给每个线程队列都加上一个名字

//提前设置好指针
Semaphore *barrier;
Semaphore *mutex;

int count=0;


void MakeTicks(int n)  // advance n ticks of simulated time n个模拟时间推进
{                        //说实话至少这个函数哥们是没看懂一点的。。。。
   int i;                //首先是我不会操作系统,其次我不写cpp
   IntStatus oldLevel;   //根据代码框架的提示,在最开始就写好了这个开关来增加时间的方法
   oldLevel=interrupt->SetLevel(IntOff);
   for(i=0;iSetLevel(IntOff);
      interrupt->SetLevel(IntOn);
   }
   interrupt->SetLevel(oldLevel);
}


void BarThread(_int which)   //下面这个其实就是基本实现了
{
    MakeTicks(N_TICKS);
    printf("Thread %d rendezvous\n", which);//这个东西被称作rendezvous循环什么的??乱七八糟的
    //总之就是首先由互斥锁保证,计数器是合理运行的
    mutex->P();
    count++;                     
    mutex->V();
    /*
    
    if(count==N_THREADS){
        printf("Thread %d is the last===================\n",which);
        barrier->V();                            //
    }

    barrier->P();                               //前面n-1个线程会被阻塞在这里
    barrier->V();                               //会去解救原本的第n个线程
    */
    

    //好像出现的问题之一是有的线程会觉得自己是最后一个。。。。???没发现这种问题???
    //好的,加大力度,现在是线程数目为20的时候,随机种子为114514,会发生两个线程都认为自己是最后一个的情况
    //原因在于临界资源的访问,无论是读写,都应该加上一个互斥锁
    //在最开始的代码中,对于count的判断并没有使用互斥,导致了两个线程的同时访问

    //导致的错误:最后一个线程需要处理内存相关的东西,但是当有多个判断自己为最后一个线程的时候,可能会导致内存等其他临界区域发生问题
    //解决方法:对临界资源的读写仍然是加上互斥锁,如下图代码所示,即可解决这个问题

    //最后是关于随机种子的情况:
    //rz并非无效,根据实验代码提供的提示,应该是为每个进程分配的时间片不足导致的
    //使用空循环耗时并没有解决实际问题:首先较少的空循环并不能起到拖延很多时间的作用,其次空循环是在用户状态下执行的任务,对整体的时钟没有什么改变效果
    //使用linux下的sleep则根本无法解决问题:因为sleep的作用是linux上的进程,而不是nachos上的“线程”,nachos在linux的视角下本身就是一个进程而已

    //makeTick的开关中断处理了这个问题,由于给的代码框架比较完善,所以在第一次就完善了这个方法
    //导致后面就没有出现这些问题
    //所以正确的解决方法就是在makeTick中,增加(根据题目给出的框架,我们默认使用1000来进行实验)
    //的开关次数,使用nachos内部的“进程”的开关中断进行模拟
    mutex->P();
    if(count==N_THREADS){
        printf("Thread %d is the last===================\n",which);
        barrier->V();                            
    }
    mutex->V();

    barrier->P();                               //前面n-1个线程会被阻塞在这里
    barrier->V();                               //会去解救原本的第n个线程

    printf("Thread %d critical point\n", which);
}


void ThreadsBarrier()               //这个函数用来设置县城之间的同步/并发控制
{
    int i;
    DEBUG('t',"ThreadsBarrier");     //DEBUG函数使用来干啥的来着??

    //创建互斥锁以及屏障
    barrier=new Semaphore("barrier",0);//障碍的信号量设置为0,带波奥这是个屏障
    mutex=new Semaphore("mutex",1);//互斥锁的信号量设置为1,代表统一时刻只能有一个线程访问这个东西

    // Create and fork N_THREADS threads   //在这里创造多个线程,也就是县城初始化的部分
    for(int i = 0; i < N_THREADS; i++) {
        //...............
        sprintf(thread_names[i],"thread_%d",i);
        //...............
        threads[i]=new Thread(thread_names[i]);
        threads[i]->Fork(BarThread, i);
    };
}

其中对于n屏蔽机制,如图所示(代码加上了一些注释,因为这段代码是需要后期改进的,这里是完成了第三步要求的展示。)

[山东大学操作系统课程设计]实验三_第3张图片

使用make指令进行编译,并且根据-rz指令设置随机种子

在这里我们直接使用随机种子114514,并且为了让结果更加明显,我们创建了而是个并发的县城,结果如下

使用指令

./nachos -rz 114514

结果如下

[山东大学操作系统课程设计]实验三_第4张图片

出现了明显的问题,进程0和进程1都认为自己是最后一个进程

这个现象的原因是因为,对于最后的count的读取判断,并没有是互斥枷锁,倒是了0和1都能读取到count=n的情况,导致二者均认为自己可以处理这个问题。

处理方法为:对于if的count判断,也要加上互斥锁

3.4:使用随机种子进行测试,可能会发生-rz失效的情况,如何处理,以及怎么处理

rz确实会发生”无效“的情况,根据实验代码提供的提示,应该是为每个进程分配的时间片不足导致的

1。使用空循环耗时并没有解决实际问题:首先较少的空循环并不能起到拖延很多时间的作用,其次空循环是在用户状态下执行的任务,对整体的时钟没有什么改变效果

2。使用linux下的sleep则根本无法解决问题:因为sleep的作用是linux上的进程,而不是nachos上的“线程”,nachos在linux的视角下本身就是一个进程而已

makeTick的开关中断处理了这个问题,由于给的代码框架比较完善,所以在第一次就完善了这个方法,导致在最开始的测试中就没有出现这些问题,所以正确的解决方法就是在makeTick中,增加(根据题目给出的框架,我们默认使用1000来进行实验)

如图所示,我们进一步填充了maketick函数的内容

void MakeTicks(int n)  // advance n ticks of simulated time n个模拟时间推进
{                        //说实话至少这个函数哥们是没看懂一点的。。。。
   int i;                //首先是我不会操作系统,其次我不写cpp
   IntStatus oldLevel;   //根据代码框架的提示,在最开始就写好了这个开关来增加时间的方法
   oldLevel=interrupt->SetLevel(IntOff);
   for(i=0;iSetLevel(IntOff);
      interrupt->SetLevel(IntOn);
   }
   interrupt->SetLevel(oldLevel);
}

你可能感兴趣的:(操作系统课设,课程设计,windows)