操作系统模拟实验——进程调度实验(时间片轮转调度、短进程优先调度和多级反馈队列调度)

一、实验目的

  1. 理解在单处理器环境下的进程调度及状态转换的基本原理和关键点。
  2. 编程实现时间片轮转调度、短进程优先调度和多级反馈队列调度算法。
  3. 分析并比较不同调度算法的效率和公平性。

二、实验要求

  1. 模拟每一个进程,需要包含到达时间,运行时间,优先级等属性。
  2. 每一种调度算法都需要实现一个模拟函数,对进程结束时间进行仿真模拟。多级反馈队列调度算法至少包含三个队列,每个队列的时间片大小不同。
  3. 对给定的一组进程,当采用不同的调度算法时,需仿真模拟这组进程的状态转换过程。进程的状态至少有就绪、运行和阻塞三种状态。
  4. 当这组进程调度执行完成时,根据仿真模拟的结果,计算平均周转时间/平均带权周转时间,平均等待时间、平均响应时间、利用率等性能指标及公平性指标。
  5. 能够提供合适的测试数据集,对每一种算法进行测试,并分析其结果。

三、实验代码

  下为实验完整c++代码:

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>

using namespace std;

const int N = 15;//PCB上限个数
const int M = 1005;//最大CPU运行终止时间
const int TSNUM = 2;//设定时间片大小

//PCB的状态
enum ProcessState {
    READY,//就绪态
    RUNNING,//运行态
    WAITING,//等待态
    TERMINATED//终止态
};

//定义PCB的数据结构
struct PCB{
    int id;//进程编号
    int priority;//优先级
    int start;//到达时间
    int runtime;//运行时间
    int usedtime = 0;//已运行时间
    int finish;//完成时间
    int selected_time = -1;//响应时间,-1代表该进程还未响应
    ProcessState status;//状态
    //t时刻,给进程分配cpu
    void selectPcb(int t)
    {
        if(selected_time == -1)//记录响应时间
            selected_time = t;
        status = RUNNING;
        printf("在%d时刻,给%d号进程分配cpu,运行时间:%d,已运行时间:%d\n", t, id, runtime, usedtime);
    }
}p[N];

int pcb_num;//进程个数(15以内) 
vector<int> arrivedList[M];//保存某时刻到达的进程

//返回CPU运行终止时间
int GetEndtime()
{
    int tmp1 = 0, tmp2 = 0;//分别代表最大到达时间和运行总时长
    for(int i = 0; i < pcb_num; i++)
    {
        tmp1 = max(tmp1, p[i].start);
        tmp2 += p[i].runtime;
    }
    return tmp1 + tmp2;
}

//RR算法
void RR()
{
    queue<int> q;//就绪队列
    int ts;//时间片
    int id = -1;//当前运行进程的编号,-1代表没有进程正在运行
    int endtime = GetEndtime();//CPU运行终止时间
    for(int t = 0; t <= endtime; t++)
    {
        //将t时刻到达的所有进程放入就绪队列
        for(int i = 0; i < arrivedList[t].size(); i++)
        {
            q.push(arrivedList[t][i]);
            p[arrivedList[t][i]].status = READY;//进程变为就绪态
        }
        if(q.empty() && id == -1)//如果就绪队列为空,且当前没有进程正在运行,则跳转到下一时间
            continue;
        if(id != -1)
        {
            if(ts == 0 || p[id].usedtime == p[id].runtime)//如果时间片用完了,或者该进程已运行完毕,就进行调度
            {
                if(p[id].usedtime == p[id].runtime)//如果该进程运行完毕,就设置完成时刻
                {
                    p[id].finish = t;
                    p[id].status = TERMINATED;
                }
                else//否则就将进程放入就绪队列的队尾(同一时刻如果一个进程下CPU,一个进程刚刚到达,我们默认刚到达的进程排在前面)
                    q.push(id);

                if(!q.empty())//如果就绪队列非空,就取出新的进程放入CPU
                {
                    id = q.front();//取就绪队列队首的进程放入cpu
                    p[id].selectPcb(t);//更改进程状态、输出信息
                    q.pop();//队首元素出队
                    ts = TSNUM;//时间片重置
                }
                else//否则,将id设为-1
                    id = -1;
            }
        }
        else//就绪队列非空,且当前没有进程正在运行
        {
            id = q.front();//取就绪队列队首的进程放入cpu
            p[id].selectPcb(t);//更改进程状态、输出信息
            q.pop();//队首元素出队
            ts = TSNUM;//时间片重置
        }
        //将当前执行进程的已运行时间++,时间片--
        if(id != -1)
        {
            p[id].usedtime++;
            ts--;
        }
    }
}

//SJF算法(非抢占式)
void SJF()
{
    vector<int> q;//就绪队列
    int id = -1;//当前运行进程的编号,-1代表没有进程正在运行
    int endtime = GetEndtime();//CPU运行终止时间
    for(int t = 0; t <= endtime; t++)
    {
        //将t时刻到达的所有进程放入就绪队列
        for(int i = 0; i < arrivedList[t].size(); i++)
        {
            q.push_back(arrivedList[t][i]);
            p[arrivedList[t][i]].status = READY;//进程变为就绪态
        }
        if(q.empty() && id == -1)//如果就绪队列为空,且当前没有进程正在运行,则跳转到下一时间
            continue;
        if(id != -1)
        {
            if(p[id].usedtime == p[id].runtime)//如果该进程已运行完毕,就进行调度
            {
                p[id].finish = t;
                p[id].status = TERMINATED;

                if(!q.empty())//如果就绪队列非空,就取出新的进程放入CPU
                {
                    int tmp_qid = -1, minn = 0x3f3f3f3f;
                    for(int i = 0; i < q.size(); i++)//从就绪队列中选出运行时间最短的进程
                    {
                        if(p[q[i]].runtime < minn)
                        {
                            minn = p[q[i]].runtime;
                            tmp_qid = i;
                        }
                    }

                    id = q[tmp_qid];//将运行时间最短选出的进程放入cpu
                    p[id].selectPcb(t);//更改进程状态、输出信息
                    q.erase(q.begin() + tmp_qid);//在就绪队列中删除选出的进程
                }
                else//否则,将id设为-1
                    id = -1;
            }
        }
        else//就绪队列非空,且当前没有进程正在运行
        {
            int tmp_qid = -1, minn = 0x3f3f3f3f;
            for(int i = 0; i < q.size(); i++)//从就绪队列中选出运行时间最短的进程
            {
                if(p[q[i]].runtime < minn)
                {
                    minn = p[q[i]].runtime;
                    tmp_qid = i;
                }
            }

            id = q[tmp_qid];//将运行时间最短选出的进程放入cpu
            p[id].selectPcb(t);//更改进程状态、输出信息
            q.erase(q.begin() + tmp_qid);//在就绪队列中删除选出的进程
        }
        //将当前执行进程的已运行时间++
        if(id != -1)
            p[id].usedtime++;
    }
}

//SJF算法(抢占式)
void SJF1()
{
    vector<int> q;//就绪队列
    int id = -1;//当前运行进程的编号,-1代表没有进程正在运行
    int endtime = GetEndtime();//CPU运行终止时间
    for(int t = 0; t <= endtime; t++)
    {
        //将t时刻到达的所有进程放入就绪队列
        for(int i = 0; i < arrivedList[t].size(); i++)
        {
            p[arrivedList[t][i]].status = READY;//进程变为就绪态
            //如果新到达的进程剩余时间比当前运行的进程的剩余时间更短,新进程占用cpu,运行进程回到就绪队列
            if(id != -1 && p[arrivedList[t][i]].runtime < p[id].runtime - p[id].usedtime)
            {
                q.push_back(id);
                p[id].status = READY;
                id = arrivedList[t][i];
                p[id].selectPcb(t);//更改进程状态、输出信息
            }
            else
                q.push_back(arrivedList[t][i]);
        }
        if(q.empty() && id == -1)//如果就绪队列为空,且当前没有进程正在运行,则跳转到下一时间
            continue;
        if(id != -1)
        {
            if(p[id].usedtime == p[id].runtime)//如果该进程已运行完毕,就进行调度
            {
                p[id].finish = t;
                p[id].status = TERMINATED;

                if(!q.empty())//如果就绪队列非空,就取出新的进程放入CPU
                {
                    int tmp_qid = -1, minn = 0x3f3f3f3f;
                    for(int i = 0; i < q.size(); i++)//从就绪队列中选出运行时间最短的进程
                    {
                        if(p[q[i]].runtime - p[q[i]].usedtime < minn)
                        {
                            minn = p[q[i]].runtime - p[q[i]].usedtime;
                            tmp_qid = i;
                        }
                    }

                    id = q[tmp_qid];//将运行时间最短选出的进程放入cpu
                    p[id].selectPcb(t);//更改进程状态、输出信息
                    q.erase(q.begin() + tmp_qid);//在就绪队列中删除选出的进程
                }
                else//否则,将id设为-1
                    id = -1;
            }
        }
        else//就绪队列非空,且当前没有进程正在运行
        {
            int tmp_qid = -1, minn = 0x3f3f3f3f;
            for(int i = 0; i < q.size(); i++)//从就绪队列中选出运行时间最短的进程
            {
                if(p[q[i]].runtime - p[q[i]].usedtime < minn)
                {
                    minn = p[q[i]].runtime - p[q[i]].usedtime;
                    tmp_qid = i;
                }
            }

            id = q[tmp_qid];//将运行时间最短选出的进程放入cpu
            p[id].selectPcb(t);//更改进程状态、输出信息
            q.erase(q.begin() + tmp_qid);//在就绪队列中删除选出的进程
        }
        //将当前执行进程的已运行时间++
        if(id != -1)
            p[id].usedtime++;
    }
}

//多级反馈队列调度算法
void MLFQ()
{
    printf("三级就绪队列,时间片分配分别为1,2,4\n");
    int level = 3;//就绪队列级数
    queue<int> q[level];//三级就绪队列
    int ts;//时间片
    int id = -1;//当前运行进程的编号,-1代表没有进程正在运行
    int qnum = -1;//当前运行进程所在的就绪队列编号,-1代表没有进程正在运行
    int endtime = GetEndtime();//CPU运行终止时间
    for(int t = 0; t <= endtime; t++)
    {
        //将t时刻到达的所有进程放入就绪队列
        for(int i = 0; i < arrivedList[t].size(); i++)
        {
            p[arrivedList[t][i]].status = READY;//进程变为就绪态
            //如果新到达的进程的优先级比当前运行的进程的优先级更高,新进程获得cpu,运行进程回到原级别就绪队列
            if(id != -1 && qnum > 0)
            {
                q[qnum].push(id);
                p[id].status = READY;
                id = arrivedList[t][i];
                qnum = 0;
                printf("新到达的进程的优先级比当前运行的进程的优先级更高!\n");
                p[id].selectPcb(t);//更改进程状态、输出信息
                printf("%d号进程先前所在就绪队列为第%d级就绪队列\n\n", id, 1);
            }
            else
                q[0].push(arrivedList[t][i]);
        }
        
        // printf("%d时刻就绪队列状态:\n", t);
        // for(int i = 0; i < level; i++)
        // {
        //     printf("第%d级就绪队列:", i + 1);
        //     int qcnt = q[i].size();
        //     while(qcnt--)
        //     {
        //         int qtmp = q[i].front();
        //         q[i].pop();
        //         printf("%d ", qtmp);
        //         q[i].push(qtmp);
        //     }
        //     printf("\n");
        // }
        // printf("\n");

        int pnum = 0;//目前所有就绪队列中的进程个数
        for(int i = 0; i < level; i++)
            pnum += q[i].size();
        if(pnum == 0 && id == -1)//如果所有就绪队列为均为空,且当前没有进程正在运行,则跳转到下一时间
            continue;
        if(id != -1)
        {
            if(ts == 0 || p[id].usedtime == p[id].runtime)//如果时间片用完了,或者该进程已运行完毕,就进行调度
            {
                if(p[id].usedtime == p[id].runtime)//如果该进程运行完毕,就设置完成时刻
                {
                    p[id].finish = t;
                    p[id].status = TERMINATED;
                }
                else//否则就将进程放入下一级就绪队列的队尾(如果已经是最后一级,就仍然放到最后一级)
                {
                    pnum++;
                    if(qnum < level - 1)
                        q[qnum + 1].push(id);
                    else
                        q[qnum].push(id);
                }

                if(pnum != 0)//如果就绪队列非空,就取出新的进程放入CPU
                {
                    for(int i = 0; i < level; i++)
                    {
                        if(!q[i].empty())
                        {
                            printf("当前%d号进程执行完毕或时间片用完\n", id);
                            id = q[i].front();//取就绪队列队首的进程放入cpu
                            qnum = i;
                            p[id].selectPcb(t);//更改进程状态、输出信息
                            printf("%d号进程先前所在就绪队列为第%d级就绪队列\n\n", id, i + 1);
                            q[i].pop();//队首元素出队
                            ts = TSNUM;//时间片重置,三级就绪队列的时间片分别为1,2,4
                            if(i != 1)
                                ts = (i > 1) ? TSNUM * 2 : TSNUM / 2;
                            break;
                        }
                    }
                }
                else//否则,将id设为-1,qnum设为-1
                {
                    id = -1;
                    qnum = -1;
                }
            }
        }
        else//就绪队列非空,且当前没有进程正在运行
        {
            for(int i = 0; i < level; i++)
            {
                if(!q[i].empty())
                {
                    id = q[i].front();//取就绪队列队首的进程放入cpu
                    qnum = i;
                    printf("当前没有进程正在运行\n");
                    p[id].selectPcb(t);//更改进程状态、输出信息
                    printf("%d号进程先前所在就绪队列为第%d级就绪队列\n\n", id, i + 1);
                    q[i].pop();//队首元素出队
                    ts = TSNUM;//时间片重置,三级就绪队列的时间片分别为1,2,4
                    if(i != 1)
                        ts = (i > 1) ? TSNUM * 2 : TSNUM / 2;
                    break;
                }
            }
        }
        //将当前执行进程的已运行时间++,时间片--
        if(id != -1)
        {
            p[id].usedtime++;
            ts--;
        }
    }
}

//输入模块
void Input()
{
    cout << "请输入将要执行的进程个数pcb_num:" << '\n';
    cin >> pcb_num;
    cout << "接下来pcb_num行,请分别输入每个进程的到达时间和运行时间:" << '\n';
    for(int i = 0; i < pcb_num; i++)
    {
        int start, runtime;//到达时间、运行时间
        cin >> start >> runtime;
        p[i].id = i;
        p[i].start = start;
        p[i].runtime = runtime;
        arrivedList[start].push_back(i);//将start时刻i进程到达的信息保存起来
    }
}

//算法选择模块
void ChooseAlgorithm()
{
    cout << "请选择将要使用的进程调度算法:" << '\n';
    cout << "1.时间片轮转调度(RR)" << '\n';
    cout << "2.短进程优先调度(SJF非抢占式)" << '\n';
    cout << "3.短进程优先调度(SJF抢占式)" << '\n';
    cout << "4.多级反馈队列调度(MLFQ)" << '\n';
    int op;
    cin >> op;
    switch(op)
    {
        case 1:
            RR();
            break;
        case 2:
            SJF();
            break;
        case 3:
            SJF1();
            break;
        case 4:
            MLFQ();
            break;
        default:
            break;
    }
}

//统计数据模块
void ShowData()
{
    double sum1 = 0, sum2 = 0, sum3 = 0, sum4 = 0;//总周转时间,总带权周转时间,总等待时间,总响应时间
    cout << "结果统计如下:" << '\n';
    printf("%2s %8s %8s %8s %8s %12s\n", "ID", "到达时间", "运行时间","完成时间", "周转时间", "带权周转时间");
    for(int i = 0; i < pcb_num; i++)
    {
        printf("%-2d %-8d %-8d %-8d %-8d %-4lf\n", p[i].id, p[i].start, p[i].runtime, p[i].finish,
            p[i].finish - p[i].start, (p[i].finish-p[i].start) * 1.0 / p[i].runtime);

        sum1 += p[i].finish - p[i].start;
        sum2 += (p[i].finish - p[i].start) * 1.0 / p[i].runtime;
        sum3 += p[i].finish - p[i].start - p[i].runtime;
        sum4 += p[i].selected_time - p[i].start;
    }
    cout << "平均周转时间: " << sum1 / pcb_num << '\n';
    cout << "平均带权周转时间: " << sum2 / pcb_num << '\n';
    cout << "平均等待时间: " << sum3 / pcb_num << '\n';
    cout << "平均响应时间: " << sum4 / pcb_num << '\n';
}

int main()
{
    Input();//输入要执行的进程
    ChooseAlgorithm();//选择将要执行的调度算法
    ShowData();//显示统计数据
    return 0;
}

四、实验代码解读

4.1前言

   模拟实验代码语言为c++。

4.2数据结构

4.2.1PCB的数据结构

//定义PCB的数据结构
struct PCB{
   int id;//进程编号
   int priority;//优先级
   int start;//到达时间
   int runtime;//运行时间
   int usedtime = 0;//已运行时间
   int finish;//完成时间
   int selected_time = -1;//响应时间,-1代表该进程还未响应
   ProcessState status;//状态
   //t时刻,给进程分配cpu
   void selectPcb(int t)
   {
       if(selected_time == -1)//记录响应时间
           selected_time = t;
       status = RUNNING;
       printf("在%d时刻,给%d号进程分配cpu,运行时间:%d,已运行时间:%d\n", t, id, runtime, usedtime);
   }
}p[N];

  用一个结构体定义了PCB(进程控制块)。PCB保存进程的相关数据:进程编号、优先级、到达时间、运行时间、已运行时间、完成时间、响应时间和状态。该结构体内置selectPcb函数,当该进程被分配CPU时,执行该函数,并向用户展示相关信息。

4.2.2PCB的状态

//PCB的状态
enum ProcessState {
   READY,//就绪态
   RUNNING,//运行态
   WAITING,//等待态
   TERMINATED//终止态
};

  ProcessState枚举出三种PCB状态:就绪态、等待态和终止态。

4.3主要函数

4.3.1获得CPU运行终止时间

//返回CPU运行终止时间
int GetEndtime()
{
   int tmp1 = 0, tmp2 = 0;//分别代表最大到达时间和运行总时长
   for(int i = 0; i < pcb_num; i++)
   {
       tmp1 = max(tmp1, p[i].start);
       tmp2 += p[i].runtime;
   }
   return tmp1 + tmp2;
}

  GetEndtime函数用来返回CPU运行终止时间。用运行总时长加上最大到达时间作为运行终止时间,当有相关错误出现时,可以及时被程序员发现。

4.3.2时间片轮转调度算法

//RR算法
void RR()
{
   queue<int> q;//就绪队列
   int ts;//时间片
   int id = -1;//当前运行进程的编号,-1代表没有进程正在运行
   int endtime = GetEndtime();//CPU运行终止时间
   for(int t = 0; t <= endtime; t++)
   {
       //将t时刻到达的所有进程放入就绪队列
       for(int i = 0; i < arrivedList[t].size(); i++)
       {
           q.push(arrivedList[t][i]);
           p[arrivedList[t][i]].status = READY;//进程变为就绪态
       }
       if(q.empty() && id == -1)//如果就绪队列为空,且当前没有进程正在运行,则跳转到下一时间
           continue;
       if(id != -1)
       {
           if(ts == 0 || p[id].usedtime == p[id].runtime)//如果时间片用完了,或者该进程已运行完毕,就进行调度
           {
               if(p[id].usedtime == p[id].runtime)//如果该进程运行完毕,就设置完成时刻
               {
                   p[id].finish = t;
                   p[id].status = TERMINATED;
               }
               else//否则就将进程放入就绪队列的队尾(同一时刻如果一个进程下CPU,一个进程刚刚到达,我们默认刚到达的进程排在前面)
                   q.push(id);

               if(!q.empty())//如果就绪队列非空,就取出新的进程放入CPU
               {
                   id = q.front();//取就绪队列队首的进程放入cpu
                   p[id].selectPcb(t);//更改进程状态、输出信息
                   q.pop();//队首元素出队
                   ts = TSNUM;//时间片重置
               }
               else//否则,将id设为-1
                   id = -1;
           }
       }
       else//就绪队列非空,且当前没有进程正在运行
       {
           id = q.front();//取就绪队列队首的进程放入cpu
           p[id].selectPcb(t);//更改进程状态、输出信息
           q.pop();//队首元素出队
           ts = TSNUM;//时间片重置
       }
       //将当前执行进程的已运行时间++,时间片--
       if(id != -1)
       {
           p[id].usedtime++;
           ts--;
       }
   }
}

  RR算法轮流让就绪队列的进程依次执行一个时间片(大小为2)。从0到endtime枚举时间t,模拟每个时刻发生的情况。首先将t时刻到达的所有进程放入就绪队列。然后分类讨论三种情况:1. 就绪队列为空,且当前没有进程正在运行。2.当前有进程在运行。3. 就绪队列非空,且当前没有进程正在运行用户交互。

4.3.3短进程优先调度算法(非抢占式)

//SJF算法(非抢占式)
void SJF()
{
    vector<int> q;//就绪队列
    int id = -1;//当前运行进程的编号,-1代表没有进程正在运行
    int endtime = GetEndtime();//CPU运行终止时间
    for(int t = 0; t <= endtime; t++)
    {
        //将t时刻到达的所有进程放入就绪队列
        for(int i = 0; i < arrivedList[t].size(); i++)
        {
            q.push_back(arrivedList[t][i]);
            p[arrivedList[t][i]].status = READY;//进程变为就绪态
        }
        if(q.empty() && id == -1)//如果就绪队列为空,且当前没有进程正在运行,则跳转到下一时间
            continue;
        if(id != -1)
        {
            if(p[id].usedtime == p[id].runtime)//如果该进程已运行完毕,就进行调度
            {
                p[id].finish = t;
                p[id].status = TERMINATED;

                if(!q.empty())//如果就绪队列非空,就取出新的进程放入CPU
                {
                    int tmp_qid = -1, minn = 0x3f3f3f3f;
                    for(int i = 0; i < q.size(); i++)//从就绪队列中选出运行时间最短的进程
                    {
                        if(p[q[i]].runtime < minn)
                        {
                            minn = p[q[i]].runtime;
                            tmp_qid = i;
                        }
                    }

                    id = q[tmp_qid];//将运行时间最短选出的进程放入cpu
                    p[id].selectPcb(t);//更改进程状态、输出信息
                    q.erase(q.begin() + tmp_qid);//在就绪队列中删除选出的进程
                }
                else//否则,将id设为-1
                    id = -1;
            }
        }
        else//就绪队列非空,且当前没有进程正在运行
        {
            int tmp_qid = -1, minn = 0x3f3f3f3f;
            for(int i = 0; i < q.size(); i++)//从就绪队列中选出运行时间最短的进程
            {
                if(p[q[i]].runtime < minn)
                {
                    minn = p[q[i]].runtime;
                    tmp_qid = i;
                }
            }

            id = q[tmp_qid];//将运行时间最短选出的进程放入cpu
            p[id].selectPcb(t);//更改进程状态、输出信息
            q.erase(q.begin() + tmp_qid);//在就绪队列中删除选出的进程
        }
        //将当前执行进程的已运行时间++
        if(id != -1)
            p[id].usedtime++;
    }
}

  SJF(非抢占式)算法每次从就绪队列中选取运行时间最短的进程。模拟方式与RR算法类似,枚举时间t,然后分类讨论三种情况:1. 就绪队列为空,且当前没有进程正在运行。2.当前有进程在运行。3. 就绪队列非空,且当前没有进程正在运行用户交互。与RR算法不同的是,SJF(非抢占式)算法用vector模拟就绪队列,因为queue不支持遍历,使用vector可以方便查询运行时间最短的进程。

4.3.4短进程优先调度算法(抢占式)

//SJF算法(抢占式)
void SJF1()
{
    vector<int> q;//就绪队列
    int id = -1;//当前运行进程的编号,-1代表没有进程正在运行
    int endtime = GetEndtime();//CPU运行终止时间
    for(int t = 0; t <= endtime; t++)
    {
        //将t时刻到达的所有进程放入就绪队列
        for(int i = 0; i < arrivedList[t].size(); i++)
        {
            p[arrivedList[t][i]].status = READY;//进程变为就绪态
            //如果新到达的进程剩余时间比当前运行的进程的剩余时间更短,新进程占用cpu,运行进程回到就绪队列
            if(id != -1 && p[arrivedList[t][i]].runtime < p[id].runtime - p[id].usedtime)
            {
                q.push_back(id);
                p[id].status = READY;
                id = arrivedList[t][i];
                p[id].selectPcb(t);//更改进程状态、输出信息
            }
            else
                q.push_back(arrivedList[t][i]);
        }
        if(q.empty() && id == -1)//如果就绪队列为空,且当前没有进程正在运行,则跳转到下一时间
            continue;
        if(id != -1)
        {
            if(p[id].usedtime == p[id].runtime)//如果该进程已运行完毕,就进行调度
            {
                p[id].finish = t;
                p[id].status = TERMINATED;

                if(!q.empty())//如果就绪队列非空,就取出新的进程放入CPU
                {
                    int tmp_qid = -1, minn = 0x3f3f3f3f;
                    for(int i = 0; i < q.size(); i++)//从就绪队列中选出运行时间最短的进程
                    {
                        if(p[q[i]].runtime - p[q[i]].usedtime < minn)
                        {
                            minn = p[q[i]].runtime - p[q[i]].usedtime;
                            tmp_qid = i;
                        }
                    }

                    id = q[tmp_qid];//将运行时间最短选出的进程放入cpu
                    p[id].selectPcb(t);//更改进程状态、输出信息
                    q.erase(q.begin() + tmp_qid);//在就绪队列中删除选出的进程
                }
                else//否则,将id设为-1
                    id = -1;
            }
        }
        else//就绪队列非空,且当前没有进程正在运行
        {
            int tmp_qid = -1, minn = 0x3f3f3f3f;
            for(int i = 0; i < q.size(); i++)//从就绪队列中选出运行时间最短的进程
            {
                if(p[q[i]].runtime - p[q[i]].usedtime < minn)
                {
                    minn = p[q[i]].runtime - p[q[i]].usedtime;
                    tmp_qid = i;
                }
            }

            id = q[tmp_qid];//将运行时间最短选出的进程放入cpu
            p[id].selectPcb(t);//更改进程状态、输出信息
            q.erase(q.begin() + tmp_qid);//在就绪队列中删除选出的进程
        }
        //将当前执行进程的已运行时间++
        if(id != -1)
            p[id].usedtime++;
    }
}

  SJF(抢占式)算法与SJF(非抢占式)算法的区别在于:新进程到达时就需要调度。于是在新进程到达时判断:新到达的进程剩余时间是否比当前运行的进程的剩余时间更短,其余部分与SJF(非抢占式)算法相同。

4.3.5多级反馈队列调度算法

//多级反馈队列调度算法
void MLFQ()
{
    printf("三级就绪队列,时间片分配分别为1,2,4\n");
    int level = 3;//就绪队列级数
    queue<int> q[level];//三级就绪队列
    int ts;//时间片
    int id = -1;//当前运行进程的编号,-1代表没有进程正在运行
    int qnum = -1;//当前运行进程所在的就绪队列编号,-1代表没有进程正在运行
    int endtime = GetEndtime();//CPU运行终止时间
    for(int t = 0; t <= endtime; t++)
    {
        //将t时刻到达的所有进程放入就绪队列
        for(int i = 0; i < arrivedList[t].size(); i++)
        {
            p[arrivedList[t][i]].status = READY;//进程变为就绪态
            //如果新到达的进程的优先级比当前运行的进程的优先级更高,新进程获得cpu,运行进程回到原级别就绪队列
            if(id != -1 && qnum > 0)
            {
                q[qnum].push(id);
                p[id].status = READY;
                id = arrivedList[t][i];
                qnum = 0;
                printf("新到达的进程的优先级比当前运行的进程的优先级更高!\n");
                p[id].selectPcb(t);//更改进程状态、输出信息
                printf("%d号进程先前所在就绪队列为第%d级就绪队列\n\n", id, 1);
            }
            else
                q[0].push(arrivedList[t][i]);
        }
        
        printf("%d时刻就绪队列状态:\n", t);
        for(int i = 0; i < level; i++)
        {
            printf("第%d级就绪队列:", i + 1);
            int qcnt = q[i].size();
            while(qcnt--)
            {
                int qtmp = q[i].front();
                q[i].pop();
                printf("%d ", qtmp);
                q[i].push(qtmp);
            }
            printf("\n");
        }
        printf("\n");

        int pnum = 0;//目前所有就绪队列中的进程个数
        for(int i = 0; i < level; i++)
            pnum += q[i].size();
        if(pnum == 0 && id == -1)//如果所有就绪队列为均为空,且当前没有进程正在运行,则跳转到下一时间
            continue;
        if(id != -1)
        {
            if(ts == 0 || p[id].usedtime == p[id].runtime)//如果时间片用完了,或者该进程已运行完毕,就进行调度
            {
                if(p[id].usedtime == p[id].runtime)//如果该进程运行完毕,就设置完成时刻
                {
                    p[id].finish = t;
                    p[id].status = TERMINATED;
                }
                else//否则就将进程放入下一级就绪队列的队尾(如果已经是最后一级,就仍然放到最后一级)
                {
                    pnum++;
                    if(qnum < level - 1)
                        q[qnum + 1].push(id);
                    else
                        q[qnum].push(id);
                }

                if(pnum != 0)//如果就绪队列非空,就取出新的进程放入CPU
                {
                    for(int i = 0; i < level; i++)
                    {
                        if(!q[i].empty())
                        {
                            printf("当前%d号进程执行完毕或时间片用完\n", id);
                            id = q[i].front();//取就绪队列队首的进程放入cpu
                            qnum = i;
                            p[id].selectPcb(t);//更改进程状态、输出信息
                            printf("%d号进程先前所在就绪队列为第%d级就绪队列\n\n", id, i + 1);
                            q[i].pop();//队首元素出队
                            ts = TSNUM;//时间片重置,三级就绪队列的时间片分别为1,2,4
                            if(i != 1)
                                ts = (i > 1) ? TSNUM * 2 : TSNUM / 2;
                            break;
                        }
                    }
                }
                else//否则,将id设为-1,qnum设为-1
                {
                    id = -1;
                    qnum = -1;
                }
            }
        }
        else//就绪队列非空,且当前没有进程正在运行
        {
            for(int i = 0; i < level; i++)
            {
                if(!q[i].empty())
                {
                    id = q[i].front();//取就绪队列队首的进程放入cpu
                    qnum = i;
                    printf("当前没有进程正在运行\n");
                    p[id].selectPcb(t);//更改进程状态、输出信息
                    printf("%d号进程先前所在就绪队列为第%d级就绪队列\n\n", id, i + 1);
                    q[i].pop();//队首元素出队
                    ts = TSNUM;//时间片重置,三级就绪队列的时间片分别为1,2,4
                    if(i != 1)
                        ts = (i > 1) ? TSNUM * 2 : TSNUM / 2;
                    break;
                }
            }
        }
        //将当前执行进程的已运行时间++,时间片--
        if(id != -1)
        {
            p[id].usedtime++;
            ts--;
        }
    }
}

  多级反馈队列调度算法要求设置多级就绪队列,优先级从高到低,时间片从小到大。依然是枚举时间t,分三种情况讨论,与上述算法一致。设置三个queue类型的队列q,用来模拟三级就绪队列。时间片大小分别为1,2,4。设置时间片ts,在每个时刻考虑时间片是否用完。每个时刻从高优先级到低优先级访问就绪队列。

4.4用户交互

4.4.1输入模块

//输入模块
void Input()
{
    cout << "请输入将要执行的进程个数pcb_num:" << '\n';
    cin >> pcb_num;
    cout << "接下来pcb_num行,请分别输入每个进程的到达时间和运行时间:" << '\n';
    for(int i = 0; i < pcb_num; i++)
    {
        int start, runtime;//到达时间、运行时间
        cin >> start >> runtime;
        p[i].id = i;
        p[i].start = start;
        p[i].runtime = runtime;
        arrivedList[start].push_back(i);//将start时刻i进程到达的信息保存起来
    }
}

  输入模块要求用户输入要执行的进程个数pcb_num。后面pcb_num行,每行输入各个进程的到达时间和运行时间。

4.4.2算法选择模块

//算法选择模块
void ChooseAlgorithm()
{
    cout << "请选择将要使用的进程调度算法:" << '\n';
    cout << "1.时间片轮转调度(RR)" << '\n';
    cout << "2.短进程优先调度(SJF非抢占式)" << '\n';
    cout << "3.短进程优先调度(SJF抢占式)" << '\n';
    cout << "4.多级反馈队列调度(MLFQ)" << '\n';
    int op;
    cin >> op;
    switch(op)
    {
        case 1:
            RR();
            break;
        case 2:
            SJF();
            break;
        case 3:
            SJF1();
            break;
        case 4:
            MLFQ();
            break;
        default:
            break;
    }
}

  算法选择模块打印算法列表,并要求用户输入要使用的进程调度算法,以此来执行相应算法。

4.4.3统计数据模块

//统计数据模块
void ShowData()
{
    double sum1 = 0, sum2 = 0, sum3 = 0, sum4 = 0;//总周转时间,总带权周转时间,总等待时间,总响应时间
    cout << "结果统计如下:" << '\n';
    printf("%2s %8s %8s %8s %8s %12s\n", "ID", "到达时间", "运行时间","完成时间", "周转时间", "带权周转时间");
    for(int i = 0; i < pcb_num; i++)
    {
        printf("%-2d %-8d %-8d %-8d %-8d %-4lf\n", p[i].id, p[i].start, p[i].runtime, p[i].finish,
            p[i].finish - p[i].start, (p[i].finish-p[i].start) * 1.0 / p[i].runtime);

        sum1 += p[i].finish - p[i].start;
        sum2 += (p[i].finish - p[i].start) * 1.0 / p[i].runtime;
        sum3 += p[i].finish - p[i].start - p[i].runtime;
        sum4 += p[i].selected_time - p[i].start;
    }
    cout << "平均周转时间: " << sum1 / pcb_num << '\n';
    cout << "平均带权周转时间: " << sum2 / pcb_num << '\n';
    cout << "平均等待时间: " << sum3 / pcb_num << '\n';
    cout << "平均响应时间: " << sum4 / pcb_num << '\n';
}

  统计数据模块会显示pcb中保存的部分数据,直观地告诉用户相关性能指标。

4.4.4主函数

int main()
{
    Input();//输入要执行的进程
    ChooseAlgorithm();//选择将要执行的调度算法
    ShowData();//显示统计数据
    return 0;
}

  主函数依次执行输入模块、算法选择模块和显示统计数据模块。

五、实验结果

5.1时间片轮转调度算法测试结果

  如下图所示:在这里插入图片描述
  测试用例:

4
0 5
2 4
4 1
5 6

5.2短进程优先调度算法(非抢占式)测试结果

  如下图所示:
在这里插入图片描述

  测试用例:

4
0 7
2 4
4 1
5 4

5.3短进程优先调度算法(抢占式)测试结果

  如下图所示:
在这里插入图片描述

  测试用例:

4
0 7
2 4
4 1
5 4

5.4多级反馈队列调度算法测试结果

  如下图所示:
在这里插入图片描述

  测试用例:

5
0 4
1 3
3 5
5 2
9 6

六、结语

  本实验设计思路参考王道考研操作系统,90%以上代码为本人原创。如果有什么错误,欢迎大伙儿在评论区指正ヾ(●´▽‘●)ノ。

  • 28
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值