*******************此篇博客用于记录学习历程,仅供交流参考
一、课程设计题目及内容
题目:设计一个按照时间片轮转法实现处理机调度的程序
时间片轮转法实现处理机调度的程序设计提示如下:
(1)假设系统有n个进程,每个进程用一个进程控制块(PCB)来代表。进程控制块的格式如下表所示,且参数意义也相同。
进程名
链接指针
到达时间
估计运行时间
进程状态
(2)按照进程到达的先后顺序排成一个循环队列,设一个队首指针指向第一个到达进程的首址。另外再设一个当前运行进程指针,指向当前正运行的进程。
(3)执行处理机调度时,首先选择队首的第一个进程运行。
(4)由于本题目是模拟实验,所以对被选中的进程并不实际启动运行,而只是执行如下操作:1)估计运行时间减1;
2)输出当前运行进程的名字。
用这两个操作来模拟进程的一次运行。
(5)进程运行一次后,以后的调度则将当前指针依次下移一个位置,指向下一个进程,即调整当前运行指针指向该进程的链接指针所指进程,以指示应运行进程,同时还应判断该进程的剩余运行时间是否为0,若不为0,则等待下一轮的运行,若该进程的剩余运行时间为0,则将该进程的状态置为完成状态“C”,并退出循环队列。
(6)若就绪队列不为空,则重复上述的步骤(4)和(5)直到所有进程都运行完为止。
(7)在所设计的调度程序中,应包含显示或打印语句,以便显示或打印每次选中进程的名称及运行一次后队列的变化情况。
二、程序中使用的数据结构及主要符号说明
mtx: 用来进行上锁跟解锁的信号量。
sem[]: 用来实现睡眠,发送信号唤醒线程的信号量。
PCB: 表示进程控制块,具有name, id, arrtime, runningtime, isdone属性,分别表示的含义在代码注释上。
n: 进程数量。
cnt: 用来计数目前完成的进程数量。
nowtime: 时间计数器,表示目前时间。
nowrunning: 用来表示目前正在轮转的进程。
que: 用来存放请求进程的队列。
show: 由于queue没有遍历操作,所以在输出队列元素的时候需要一个临时队列来存储数据。
三、程序流程图和带有注释的源程序
所用数据:
队列输出函数:
线程运行函数:
(当线程运行时间为0的时候不退出,一直运行模拟进程的操作,通过sem_wait函数来使线程休眠,直到收到信号才唤醒。模拟进程操作为:估计时间减一以及输出该进程名称,当估计运行时间为0的时候,使目前完成进程数加一,并改变状态,再退出队列,而若估计时间不为0,则重新返回队尾,再次等待唤醒)
初始化函数:
时间片轮转法函数:
main函数:
四、测试运行结果截图:
(中间部分太长跳过)
四、实验结果分析,实验收获和体会
实验结果分析,实验收获:
时间片轮转法,他的主要思路就是,让目前在队列队首的进程出队,然后运行一个时间片的时间,如果这个时候进程还在运行,就把他所占用的资源全部剥夺,让他进入阻塞状态,然后进入队尾。将这些资源再给予下一个队首元素。如果运行一个时间片时间之后,这个进程完成了,就出队。这种方法很公平。在课设中,要求我们实现的时间片为1,也就是每次进程出队,估计时间减一。完成了课设之后,我对这种处理机调度法更加熟悉了,因为老师要求我们用多线程实现,所以完成代码之后,我也对多线程编程更加熟悉了。
实验中遇到的困难:
1)、我的笔记本电脑在虚拟机上运行Linux系统会很卡,而且经常会出现死机的状况,这个时候如果实现没有拍摄快照,那么就会前功尽弃(已经深有体会好几次了),因此我一开始是在windows上编写代码(windows上的codeblocks能使用不少linux独有的东西(例如信号量pthread, sem等等),非常强,非常神奇),然后在windows上编写完成之后,因为老师要求一定要使用linux演示,于是我把代码转移到linux上之后,发现编译运行之后没有结果输出,就让我近乎崩溃,找了十几个小时的时间才找到bug(有些东西在windows上能够运行,在linux却有问题)。
2)、第一点所说到的bug是ios::sync_with_stdio(false); ,这句代码原本是用来关闭cin跟cout同步,来节省时间的,(因为我现役比赛,所以一般在main函数里都会放上这句话),没想到这句话成为我差点崩溃的源泉,在windows上使用之后,能够很快看到输出,但是在linux编译之后却无法输出,这就让我当时找bug百思不得其解,幸好最后注释之后尝试编译了一下,才发现是这里的问题。
3)、第二个bug是在linux的vscode无法使用pthread的地方,原因是因为pthread不是linux自带的库,需要连接外部静态库libpthread.a才能够使用,但是我在网上找了许久,都没有找到在vscode中连接这个静态库的方法,最后只能通过linux本地终端使用命令:
g++ [xxx].cpp -o [xxx] -lpthread的方法来编译代码运行。(-lpthread是连接该静态库的方法)
4)、第三个bug是,当时编写完线程运行代码之后,在时间片轮转法中,线程睡眠的地方老是不会被唤醒。于是我当时非常头疼,只能随手加了个sleep()想找bug,却歪打正着,唤醒了进程。在windows编写的时候,sleep()无论是加在锁里面,还是锁外面,都能够成功唤醒线程,但是在linux中却失败了。再次寻找了半天bug,最后将sleep()放在锁的外面,才成功了。我猜测可能是因为在锁里线程互斥,所以没法实现等待子线程完成,再来运行主线程的操作。但是在锁外,就能通过sleep()来等待子线程完成。
最后附上源代码:
#pragma GCC optimize(3)
#include
#include
#include
#include
#define ull unsigned long long
#define ll long long
#define pi 3.1415926
#define pll pair
#define pii pair
#define endl '\n'
#define eps 1e-6
using namespace std;
const int inf=0x3f3f3f;
const int maxn=30;
const int mod=998244353;
pthread_mutex_t mtx; //互斥信号量
sem_t sem[maxn]; //linux信号量,用来进行唤醒
//代表进程控制块的数据结构
struct PCB {
string name; //名称
int id; //PCB的id
int arrtime; //到达时间
int runningtime; //预计运行时间
bool isdone; //状态 0:未完成 1:完成
}Pb[maxn];
int n; //进程数
int cnt=0; //目前完成的进程数
int nowtime=0; //目前时间计数
PCB nowrunning;
queue<PCB>que; //队列
queue<PCB>show; //输出队列
//队列输出
void showque() {
cout<<"目前队列(右进左出)的情况为: ";
while(!que.empty()) {
show.push(que.front());
cout<<que.front().name<<" ";
que.pop();
}
while(!show.empty()) {
que.push(show.front());
show.pop();
}
cout<<endl<<endl;
return;
}
//线程运行函数
void *thyx(void *arg) {
int id=*(int*)arg; //获取线程id
//当该线程运行时间不为0
while(Pb[id].runningtime>0) {
sem_wait(&sem[id]); //在这里休眠,等待该线程的id信号来唤醒
pthread_mutex_lock(&mtx); //加锁
//执行模拟进程操作
Pb[id].runningtime--; //估计运行时间--
cout<<"目前轮转的线程为 : "<<Pb[id].name<<endl<<endl;
sleep(1);
//若估计运行时间为零
if(Pb[id].runningtime==0) {
cnt++; //目前完成进程数++
Pb[id].isdone=1; //表示该完成
cout<<"进程 "<<Pb[id].name<<" 剩余估计时间为: "<<Pb[id].runningtime<<" 运行完成,退出队列"<<endl<<endl;
que.pop();//不论是否完成,进行轮转的进程都要出队
}
else {
//未完成则返回队尾
que.push(Pb[id]);
que.pop();//不论是否完成,进行轮转的进程都要出队
cout<<"进程 "<<Pb[id].name<<" 剩余估计时间为: "<<Pb[id].runningtime<<" ,返回队尾"<<endl<<endl;
}
pthread_mutex_unlock(&mtx); //解锁
}
void (*fun);
return fun;
}
//初始化
void init() {
cout<<endl<<"随机生成进程数量: ";
n=rand()%8+3; //n个进程 (3
cout<<n<<endl<<endl;
nowtime=0;
for(int i=0;i<n;i++) {
string tname="";
switch(i) {
case 0:
tname='a';
break;
case 1:
tname='b';
break;
case 2:
tname='c';
break;
case 3:
tname='d';
break;
case 4:
tname='e';
break;
case 5:
tname='f';
break;
case 6:
tname='g';
break;
case 7:
tname='h';
break;
case 8:
tname='i';
break;
case 9:
tname='j';
break;
default:
tname='z';
break;
}
Pb[i].name=tname;
}
//随机生成到达时间以及运行时间
for(int i=0;i<n;i++) {
//将所有信号量初始化
sem_init(&sem[i],0,0);
Pb[i].arrtime=rand()%8+1;
Pb[i].runningtime=rand()%8+1;
Pb[i].id=i;
cout<<"进程 "<<Pb[i].name<<" 到达时间: "<<Pb[i].arrtime<<" 估计运行时间: "<<Pb[i].runningtime<<endl;
}
cout<<endl;
cout<<"***************时间片轮转法****************"<<endl<<endl;
cout<<"*****************开始运行******************"<<endl<<endl;
}
//时间片轮转
void timemachine() {
while(true) {
//加锁
pthread_mutex_lock(&mtx);
//如果所有进程数都完成了
if(cnt==n) break;
cout<<"当前时间为 : "<<nowtime<<endl;
for(int i=0;i<n;i++) {
if(Pb[i].arrtime==nowtime) {
//将当前时间到达的线程入队
que.push(Pb[i]);
cout<<endl<<"进程 "<<Pb[i].name<<" 到达"<<",";
cout<<"估计运行时间 : "<<Pb[i].runningtime<<endl;
}
}
//如果队列非空,则选择队首进程唤醒,来运行时间片轮转法
if(!que.empty()) {
nowrunning=que.front();
//cout<<"1*** "<
sem_post(&sem[nowrunning.id]);
}
//输出队列
cout<<endl;
showque();
nowtime++;//目前时间++
pthread_mutex_unlock(&mtx); //解锁
//***一定要在解锁之后sleep(),在锁里sleep()或者不sleep()的话,线程有时候会无法唤醒***
//(原因未知,可能是因为sleep()要等待子线进程完成再执行主线程
sleep(1);
}
}
int main() {
//Linux里使用关闭cin cout同步会出现无法输出的情况
//原因未知(windows运行是ok的但是搬到Linux就不行,这个bug找了几天,差点崩溃
//ios::sync_with_stdio(false);
//srand((ull)time(NULL));
pthread_t td[maxn]; //线程
pthread_mutex_init(&mtx,NULL); //互斥锁初始化
init(); //初始化/生成随机数据
for(int i=0;i<n;i++) {
pthread_create(&td[i],NULL,thyx,&Pb[i].id);
//创建线程,(指向线程标识符的指针,设置线程属性,运行函数,运行函数的参数)
}
//时间片轮转
timemachine();
for(int i=0;i<n;i++) {
pthread_join(td[i],NULL);
//一直阻塞主线程,直至所有线程终止
}
cout<<"*******************************************"<<endl;
cout<<endl<<" 所有进程运行完成 ! "<<endl;
return 0;
}