实验1《CPU Scheduling》
实验学时: 2 实验地点: CSU 实验日期:
一、实验目的
多道系统中,当就绪进程数大于处理机数时,须按照某种策略决定哪些进程优先占用处理机。本实验模拟实现处理机调度,加深了解处理机调度的工作过程。
- 实验内容
选择一个调度算法,实现处理机调度。
1)设计一个按优先权调度算法实现处理机调度的程序;
2)设计按时间片轮转实现处理机调度的程序。
三、实验方法
1)构建进程PCB,PCB内容包括:
进程名/PID;
要求运行时间(单位时间);
优先权;
状态:
PCB指针;
2)设置后备队列和挂起队列,设计作业调度算法、进程调度算法,以及挂起、解挂功能;
3)采用图形界面,动态展示调度过程中各进程及队列变化。
四、实验步骤
1)、需求分析与概要设计
① 首先进行图形界面设计,分为添加进程、后备队列、就绪队列、CPU运行进程、挂起队列、终止进程,分别含有进程名、优先权、运行时间等信息。
②、首先是进程创建,用户可以直接输入进程名、运行时间及优先级从而添加进程到后备队列,进入后备队后,会根据优先级进行排列,高优先级在上面,低优先级在下面,等待CPU运行。
③、点击重新填写,会清空所写数据,重新填写进程信息。
④、点击开启按钮,cpu调度开始,将所有进程加入就绪队列,第一优先级的进程加入运行队列开始运行,程序会实时显示进程所需时间,当进程所需时间变为0时,进程从运行队列进入完成队列,完成队列所有进程所需运行时间都为0。
⑤、点击停止按钮,cpu调度停止,运行时间静止,等待下一步动作。
⑥、点击再次开启,cpu调度继续,运行队列中的进程开始运行。
⑦、设置就绪队列中的挂起状态,选中进程名然后点击挂起时,该进程的所有信息从就绪队列中退出,进入到挂起进程队列中。
⑧、挂起进程队列中设置解挂按钮,选中进程后点击解挂按钮该进程进入到就绪队列中,就绪队列中所有进程按优先级重新排序显示并进入CPU
⑨、cpu的道数为后备队列进入的进程数,这样可以使程序的可用性更加强。
⑩、程序pid为系统分配,每个程序的pid均不同,可以使进程统一编号,加强对进程的控制和监视。优化界面,各部分大小位置微调,设置主界面的标题以及程序运行时显示在电脑屏幕中间,添加退出按钮,方便用户操作。
2)详细设计
1、建立一个PCB类,用于表示进程,一个进程即为一个对象,包括进程名,运行时间,优先级,pid以及构造析构函数
class PCB
{
int PID;
QString name;
int runtime;
int priority;
public:
// bool operator<(const PCB& a)const //重载 < 操作符,用于进程间优先级比较
// {
// return (priority < a.priority) ;
// }
// static bool compareByValue(const PCB &a, const PCB &b) {
// return a.priority < b.priority;
// }
PCB() = default;
PCB(QString name,int PID,int runtime,int priority)
{
this->name=name;
this->PID=PID;
this->runtime=runtime;
this->priority=priority;
}
//拷贝构造函数
PCB(const PCB &other) {
this->PID = other.PID;
this->name = other.name;
this->runtime = other.runtime;
this->priority = other.priority;
}
int getPID()
{
return PID;
}
int getPID()const
{
return PID;
}
QString getName()
{
return name;
}
int getRuntime()
{
return runtime;
}
int getRuntime()const
{
return runtime;
}
int getPriority()
{
return priority;
}
int getPriority()const
{
return priority;
}
void changeRuntime( int m)
{
runtime=m;
}
};
2)运用qt图形化界面ui技术构建图形化界面,添加lineEdit按钮、label,pushbutton,以及reservequequetableWidget、runquequetableWidget、
readyquequetableWidget、hangupquequetableWidget、donequequetableWidget用于分别存储各个QVector
QVector<PCB>reservequeque;
QVector<PCB>readyqueque;
QVector<PCB>runqueque;
QVector<PCB>hangupqueque;
QVector<PCB>donequeque;
3)使用timer计时器用于刷新各个显示器中的内容以及进行运行队列中运行时间的刷新。
//添加timer用于实时刷新各个显示器中的内容
timer = new QTimer(this); //动态刷新表格
connect(timer,&QTimer::timeout,this,&MainWindow::process_update);
timer->start(1000);//时间片设为100ms
timer1=new QTimer(this);
connect(timer,&QTimer::timeout,this,&MainWindow::runqueque_update);
timer1->start(1000);
void MainWindow::runtime_update(QVector<PCB>*que)
{
if(runqueque.at(0).getRuntime()!=0)
{
int t=que->at(0).getRuntime()-1;
que->first().changeRuntime(t);
}
}
void MainWindow::runqueque_update()
{
if(runqueque.isEmpty()==0)
{
if(runqueque.begin()->getRuntime()!=0)
{
runtime_update(&runqueque);
showrunqueque();
}
if(runqueque.begin()->getRuntime()==0&&runqueque.begin()->getPriority()!=0)
{
donequeque=moveprcfromRuntoDone(&runqueque);
runqueque=moveprcfromRdytoRun(&readyqueque);
}
if(runqueque.isEmpty()==0&&runqueque.begin()->getRuntime()==0&&runqueque.begin()->getPriority()==0&&donequeque.begin()->getPriority()!=0)
{
timer->stop();
timer1->stop();
}
}
}
4)pid随机分配数字,从数组中判断,如果判断为1,则数字被占领,如果判断为0,则数字未被占领,可以被分配
int Totime::createPID()
{
for(int i=0;i<array2.size();i++)
{
if(array2[i]==0)
{
array2[i]=1;
return i;
}
}
return 0;
}
5)在各个QWidget的界面中的表格展示用QTableWidgetItem展示,通过get对应queqeu的进程情况来进行展示
void Totime::showreservequeque()
{
int n=reservequeque.size();
ui->reservequequetableWidget->setRowCount(n);
ui->reservequequetableWidget->setColumnCount(5);
ui->reservequequetableWidget->setHorizontalHeaderLabels(QStringList() <<"进程名"<< "pid"
<<"到达时间"<< "运行时间" << "还需运行时间");
ui->reservequequetableWidget->setSelectionBehavior(QAbstractItemView::SelectItems);
// 单个选中
ui->reservequequetableWidget->setSelectionMode(QAbstractItemView::ExtendedSelection);
// 可以选中多个
for(int i=0;i<reservequeque.size();i++)
{
PCB2 p=reservequeque.at(i);
//设置表格参数
QTableWidgetItem *item0 = new QTableWidgetItem(p.getName());
QTableWidgetItem *item1 = new QTableWidgetItem(QString::number(p.getPID(), 10));
QTableWidgetItem *item2 = new QTableWidgetItem(QString::number(p.getCometime(), 10));
QTableWidgetItem *item3 = new QTableWidgetItem(QString::number(p.getRuntime(),10));
QTableWidgetItem *item4 = new QTableWidgetItem(QString::number(p.getNeedtime(),10));
ui->reservequequetableWidget->setItem(i,0,item0);
ui->reservequequetableWidget->setItem(i,1,item1);
ui->reservequequetableWidget->setItem(i,2,item2);
ui->reservequequetableWidget->setItem(i,3,item3);
ui->reservequequetableWidget->setItem(i,4,item4);
}
}
6)在UI中增加一个表示挂起和解挂的按钮,可以将就绪队列中选定的进程挂起到挂起队列,也可以将挂起队列的进程解挂到就绪队列当中。
实验截图:
五、实验代码
代码地址:OS_ex1_CPU-Scheduling: 操作系统实验一
六、实验结论
从开始到完成的过程。起初,我认为这是一个难以实现的任务,但随着一点点的写下去,我逐渐发现其实是可以完成的。回头来看,我意识到自己完成的工作并不算多,但我还是感到非常兴奋,因为这是我独立完成的项目。
在进行数据传递的时候,我总是有这样那样的bug,比如运行时间和优先级都是0,比如加入进程的时候总是加入了两个进程,后来才发现我多写了个connect函数,这花费了我几乎两个小时。还有在写从就绪队列到挂起队列的时候,移动到挂起队列之后,就绪队列没有变化,这让我很震惊,最后发现是没有用指针,改的只是就绪队列的值,而不是通过地址传递。
通过这编程实验,让我对cpu调度有了更加深刻的理解,也克服了很多苦难,对qt写程序也更加印象深刻,对以后的编程工作有了很大帮助。