操作系统课程设计--使用多线程模拟时间片轮转法调度

本篇博文分享操作系统课程设计–使用多线程模拟时间片轮转法调度的思路及代码。
实验环境:虚拟机ubuntu18.04 ,VS Code

博主分享仅为互相学习之用,不懂的地方可以留言提问,谨防抄袭!!!谢谢配合

                                    操作系统课程设计

                              学    校:广州大学
                              学    院:计算机科学与网络工程学院 
                              专业班级:软件183         
                              姓    名:幼儿园老小			 		       
                              学    号:1806300***              
                              指导老师:金zz老师               
                              完成日期:2020.07.03

1、题目:

设计一个按照时间片轮转法实现处理机调度的程序,时间片轮转法实现处理机调度的程序设计提示如下:
(1) 假设系统有n个进程,每个进程用一个进程控制块(PCB)来代表。进程控制块的格式如下表所示,且参数意义也相同。
操作系统课程设计--使用多线程模拟时间片轮转法调度_第1张图片
(2) 按照进程到达的先后顺序排成一个循环队列,设一个队首指针指向第一个到达进程的首址。另外再设一个当前运行进程指针,指向当前正运行的进程。
(3) 执行处理机调度时,首先选择队首的第一个进程运行。
(4) 由于本题目是模拟实验,所以对被选中的进程并不实际启动运行,而只是执行如下操作:
1)估计运行时间减1;
2)输出当前运行进程的名字。
用这两个操作来模拟进程的一次运行。
(5) 进程运行一次后,以后的调度则将当前指针依次下移一个位置,指向下一个进程,即调整当前运行指针指向该进程的链接指针所指进程,以指示应运行进程,同时还应判断该进程的剩余运行时间是否为0,若不为0,则等待下一轮的运行,若该进程的剩余运行时间为0,则将该进程的状态置为完成状态“C”,并退出循环队列。
(6) 若就绪队列不为空,则重复上述的步骤(4)和(5)直到所有进程都运行完为止。
(7) 在所设计的调度程序中,应包含显示或打印语句,以便显示或打印每次选中进程的名称及运行一次后队列的变化情况。

2、模拟思路

      首先声明一个信号量数组sem_t sem[n]; 调用初始化函数让线程按到达时间排成一个循环队列,循环
   赋值时调用sem_init(&sem[k],0,0); 使得每个线程对应一个信号量,初值为0。每当执行调度时,通过
   一个当前运行指针run循环扫描,直到找到第一个到达时间小于或等于系统已运行时间(即计时器count,
   初始值为0)的线程,那么就在主函数中通过sem_post(&sem[run->name]);来使信号量加1,从而使阻塞在
   这个信号量上的一个线程不再阻塞,我的程序中一个信号量就对应一个线程,那么对应的线程就执行一次
   线程函数,也即被调度运行一次,运行一次之后它的运行时间run_time减1,计时器count加1,run指针
   往后找到下一个到达时间小于或等于计时器count的线程,同时相关指针也要相应变动,主函数再通过
   sem_post(&sem[run->name]);来使信号量加1,从而使得因为这个信号量而阻塞的线程开始运行。因为是
   循环队列,所以run会循环扫描、调度运行,一个线程直至运行结束才会退出队列,直至所有线程都执行
   完毕,队列长度length变为0为止,主线程在所有子线程终止后结束,程序结束。
【说明】此处只是模拟调度,没有设计真正的选择队首线程,只是让run指针循坏移动,通过run指针指向
来选择要运行的线程。

3、详细代码

#include
#include                  
#include        //用到多线程编程的函数,所以#include
#include      //使用信号量
using namespace std;       
typedef struct PCB         //PCB结构
{
    int name;              //线程名
    int  reach_time;       //到达时间
    int run_time;          //估计运行时间
    char status;           //线程状态,设初始为“R”
    struct PCB *next;      //链接指针
}PCB;                                    

/*********函数声明***********/
PCB* createList(int m);    
void show(PCB *head);
void *Thread_Func(void *arg);

static int  threads;                             //线程数,随机生成4-6个
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; //静态初始化互斥锁
//pthread_mutex_t是一个结构,而PTHREAD_MUTEX_INITIALIZER则是一个结构常量。
sem_t sem[10];                                
//信号量的数据类型为结构sem_t,它本质上是一个长整型的数。
static PCB *run;                                 //指向当前运行线程
struct PCB  *head;                              //头指针
struct PCB  *runfront;                          //当前运行线程的前一个指针
struct PCB *rear;                              //队列尾指针,指向头指针
static int count=0;                         
//计时器,每调度运行一次便+1,到达时间与之相比,只有线程的 到达时间<=计时器  ,其才可被调度
int  reach[6]={0,1,3,5,6,8};                  //记录到达时间
struct PCB *temp;                             //声明一个PCB结构的指针
int length=0;                                 //就绪队列长度,初始为0
int over=0;                                   //记录执行结束的线程个数,初始为0

/*************初始化函数**************/
PCB* createList(int m)
{
    for(int k=0;k<m;k++)                       //m个线程按到达时间先后排队
    {
      sem_init(&sem[k],0,0);                                   
      /*sem_init函数用于创建信号量,
      原型:int sem_init(sem_t *sem,int pshared,unsigned int value);,value是信号量
      的初值*/
      temp=(PCB*)malloc(sizeof(PCB));          //申请一个新结点
      if(k==0)                                                                   
      //如果是第一个结点,则初始化相关指针,初始时指针均指向第一个结点
      {
        head=temp;
        run=head;
        runfront=rear;
        rear=head;
      }
      temp->run_time=rand()%8+1;               //此处随机生成线程估计运行时间,范围:1-8
      temp->reach_time=reach[k];
      temp->name=k;
      temp->status='R';
      temp->next=rear->next;                   //首先该结点指针指向尾指针所指向的
      rear->next=temp;                         //然后尾指针指向temp,即链上新结点
      rear=temp;                               //修改尾指针,指向当前末尾结点                 
      length++;                                //队列长度+1
    }
     rear->next=head;                          //循环队列,尾结点指针指向首结点
     PCB  *d=NULL;                               
     //记录当前运行线程的上一个线程的指针,要赋值给runfront
     while(run->reach_time!=0)                 //run指向到达时间=0的第一个线程
      {
       d=run;
       run=run->next;
      }
    runfront=d;                                //runfront指向当前运行线程的前一个线程
    show(head);                                //打印队列结果
    return head;
}

/***********打印函数*************/
void show(PCB *head)
{
  PCB *p=head;
  cout<<"就绪队列详情如下:"<<endl;
  cout<<"进程名 || 到达时间 || 估计运行时间 || 进程状态"<<endl;
  do
  {
    cout<<left;
    cout<<p->name<<"           "<<p->reach_time<<"            "<<
    p->run_time<<"             "<<p->status<<endl;
    p=p->next;
  }while(p!=head);                             
  //当p再次等于head,说明已经打印完一遍了,无需重复,结束打印
  cout<<endl;
  }
    
/**********模拟调度函数**********/
void *Thread_Func(void *arg)             //模拟时间片轮转法调度
{
  int id=*((int*)arg);                  //得到当前运行线程的信号量的数组下标:id
  while(run->run_time!=0)                
  //当线程运行时间不等于0,则根据时间轮转调度算法循环调度,直至运行时间变为0
  {
   sem_wait(&sem[id]);                  
   /*该函数用于以原子操作的方式将信号量的值-1,sem[id]指向的对象是由sem_init调用初始化的
   信号量,它永远会先等待该信号量为一个非零值才开始做减法。*/
   pthread_mutex_lock(&mutex);         //加锁
   PCB *q=run;
   q->run_time--;                     //运行时间-1
   count++;                           //运行一次,计时器+1
   cout<<"当前正在运行的进程是:"<<q->name<<endl;
   if(q->run_time==0)                //如果运行时间=0
       {
         over++;                     //执行结束的线程个数+1
         q->status='C';              //线程状态置为C
         cout<<"进程"<<q->name<<"运行结束!    "<<endl<<endl;
         if(q==head&&head!=rear) 
         //第一个结点运行结束,但不是最后一个,head后移,相关指针变动
         {
           head=head->next;
           rear->next=head;
           q=rear;                  //保证q->next指向之前运行的线程的下一个线程
           show(head);
         }
         else if(head==rear)       //队列的最后一个结点运行结束,直接输出空白队列
         {
           cout<<"就绪队列详情如下:"<<endl;
           cout<<"进程名 || 到达时间 || 估计运行时间 || 进程状态"<<endl<<endl;
         }
         else 
         {
           if(rear==q)   rear=runfront; //如果是末尾结点运行结束了,则rear指针往前移
           runfront->next=q->next;      //去掉运行完的结点,修改相关指针
           q=runfront;                  //保证q->next指向之前运行的线程的下一个线程
           show(head);
         }
         length--;
         if(length==0)                 //所有线程都执行完毕
            {   
              cout<<"就绪队列已空,模拟调度结束!"<<endl<<endl; 
              pthread_mutex_unlock(&mutex);
              exit(0);
            }
       }
    int t=0;  
    while(q->next->reach_time>count)      
         {
          q=q->next;
          t++;
          if(t==length)             
          /*到达时间均大于计时器count,则空转进程运行一个时间片,count+1。
          没有这一处理的话,当进程0和进程1的估计运行时间都随机生成为1时,进程0和进程1依次运行
          一次后结束了,然后while语句就会循环比较(因为第2个进程到达时间为3,但此时count=2,
          这时没有哪一个进程的到达时间reach_time<=count),程序在这种情况下会卡住!*/
          {
            cout<<"当前正在运行的进程是:空转进程"<<endl;
            count++;
            t=0;
          }
         }
   runfront=q;
   run=q->next;                     
   //run指向下一个到达时间<=计时器的线程名,准备下次调度
   pthread_mutex_unlock(&mutex);
  }
}

/*************主函数*************/
int main()
{   
    srand((unsigned)time(NULL));        //随机数发生器的初始化函数
    int a[6]={0,1,2,3,4,5};             //线程编号
    pthread_t tid[6];                  //pthread_t用于声明线程id
    threads=rand()%3+4;                //随机生成线程数,在4-6之间 
    cout<<"||****时间片轮转法实现处理机调度****||"<<endl;
    cout<<"系统中有"<<threads<<"个进程"<<endl;
    createList(threads);
    cout<<"****************************************"<<endl;
    for(int l=0;l<threads;l++) 
        {
          pthread_create(&tid[l], NULL,Thread_Func, &a[l]);  //创建线程
            /*第一个参数为指向线程标识符的指针;
           第二个参数用来设置线程属性;
           第三个参数是线程运行函数的起始地址;
           最后一个参数是线程运行函数的参数,这里传的是线程编号,也即线程名*/
        }
     while(1)
        {
          if(over==threads)   break;    //执行完毕的线程数=系统总线程数,则break退出
          pthread_mutex_lock(&mutex);
          sem_post(&sem[run->name]);                        
          /*sem_post是给信号量的值+1, 当有线程阻塞在这个信号量上时,调用这个函数
          会使其中一个线程不再阻塞*/
          pthread_mutex_unlock(&mutex);
        }
     for(int y=0;y<threads;y++)    
        {
          pthread_join(tid[y],NULL);    
          //主线程等待所有子线程终止后才执行return 0;  结束
        }
    return 0;
}

4、运行结果

【注意】
thread 库不是 Linux 系统默认的库,连接时需要使用静态库 libpthread.a,所以在使用
pthread_create()创建线程,以及调用 pthread_atfork()函数建立fork处理程序时,需要链接该库。

【解决办法】
在编译中要加 -lpthread参数
例:g++ RR.cpp -o 01 -lpthread  (生成一个名为01的可执行文件,这种格式可随意指定可执行文件名)
或:g++ RR.cpp -lpthread       (默认生成一个名为a.out的可执行文件)
(gcc编译也是一样的)

测试1:
操作系统课程设计--使用多线程模拟时间片轮转法调度_第2张图片
测试2:
操作系统课程设计--使用多线程模拟时间片轮转法调度_第3张图片

       本次课程设计过程及结果分享到此就结束啦!这是我目前为止做得最完美的课程设计了,请教同学和
   上网学习相关知识点后,因为比较熟悉时间片轮转法调度的原理,所以就比较快得写好了基本代码,
   调试期间也出现了挺多bug,但是经过同学耐心指教和自己认真思考,最终修好了发现的bug,现在是可以
   正常运行了,运行了几百遍都没有再发现bug了。如果大家参考了我的代码运行发现了bug,希望能告诉我
   ,我好继续改善,但需要说出出bug的具体地方,不要把你的整个代码直接丢给我... 谢谢!
      完成了这次操作系统课程设计后我觉得很有成就感,以后我将继续努力!一起加油吧
      希望此次分享对大家有所帮助咯,看了之后能有所提升。

想看更多博文?请戳—>大白的博客 ,欢迎来访嗷!

你可能感兴趣的:(操作系统,g++,多线程,C++,OS,时间片轮转算法)