目录
1.实验目的
(1)加深对进程概念的理解,明确进程和程序的区别。
(2)深入理解系统如何组织进程。
(3)理解常用进程调度算法的具体实现。
2.实验内容
编写 C 程序,模拟实现单处理器系统中的进程调度算法,实现对多个进程的模拟调度,要求采用常见的进程调度算法(动态优先级,时间片轮转)进行模拟调度。
流程图如下
3.实验过程
3.1Linux下编写调试程序
//动态优先级调度
#include "stdio.h"
#include <stdlib.h>
#define getpch(type) (type*)malloc(sizeof(type))
struct pcb { /* 定义进程控制块PCB */
char name[10]; //进程名
char state; //进程状态:"W"-就绪态,"R"-运行态
int nice; //进程优先级
int ntime; //需要运行时间
int rtime; //已经运行的时间
struct pcb* link;
}*ready=NULL,*p;
typedef struct pcb PCB;
char sort() /* 建立对进程进行优先级排列函数,优先数大者优先*/
{
PCB *first, *second;
int insert=0;
if((ready==NULL)||((p->nice)>(ready->nice)))/*优先级最大者,插入队首*/
{
p->link=ready;
ready=p;
}
else /* 进程比较优先级,插入适当的位置中*/
{
first=ready;
second=first->link;
while(second!=NULL)
{
if((p->nice)>(second->nice)) /*若插入进程比当前进程优先数大,*/
{ /*插入到当前进程前面*/
p->link=second;
first->link=p;
second=NULL;
insert=1;
}
else /* 插入进程优先数最低,则插入到队尾*/
{
first=first->link;
second=second->link;
}
}
if(insert==0) first->link=p;
}
}
char input() /* 建立进程控制块函数*/
{
int i,num;
printf("\n 请输入被调度的进程数目:");
scanf("%d",&num);
for(i=0;i<num;i++)
{
printf("\n 进程号No.%d:",i);
p=getpch(PCB);
printf("\n 输入进程名:");
scanf("%s",p->name);
printf(" 输入进程优先数:");
scanf("%d",&p->nice);
printf(" 输入进程运行时间:");
scanf("%d",&p->ntime);
printf("\n");
p->rtime=0;
p->state='W';
p->link=NULL;
sort(); /* 调用sort函数*/
}
}
int space()
{
int l=0; PCB* pr=ready;
while(pr!=NULL)
{
l++;
pr=pr->link;
}
return(l);
}
char disp(PCB * pr) /*建立进程显示函数,用于显示当前进程*/
{
printf("\n qname \t state \t nice \tndtime\truntime \n");
printf("%s\t",pr->name);
printf("%c\t",pr->state);
printf("%d\t",pr->nice);
printf("%d\t",pr->ntime);
printf("%d\t",pr->rtime);
printf("\n");
}
char check() /* 建立进程查看函数 */
{
PCB* pr;
printf("\n **** 当前正在运行的进程是:%s",p->name); /*显示当前运行进程*/
disp(p);
pr=ready;
if (pr!=NULL)
printf("\n ****当前就绪队列状态为:"); /*显示就绪队列状态*/
else
printf("\n ****当前就绪队列状态为: 空\n"); /*显示就绪队列状态为空*/
while(pr!=NULL)
{
disp(pr);
pr=pr->link;
}
}
char destroy() /*建立进程撤消函数(进程运行结束,撤消进程)*/
{
printf(" 进程 [%s] 已完成.\n",p->name);
free(p);
}
char running() /* 建立进程就绪函数(进程运行时间到,置就绪状态*/
{
(p->rtime)++;
if(p->rtime==p->ntime)
destroy(); /* 调用destroy函数*/
else
{
(p->nice)--;
p->state='W';
sort(); /*调用sort函数*/
}
}
int main() /*主函数*/
{
int len,h=0;
char ch;
input();
len=space();
while((len!=0)&&(ready!=NULL))
{
ch=getchar();
h++;
printf("\n The execute number:%d \n",h);
p=ready;
ready=p->link;
p->link=NULL;
p->state='R';
check();
running();
printf("\n按任一键继续......");
ch=getchar();
}
printf("\n\n 所有进程已经运行完成!\n");
ch=getchar();
return 0;
}
创建schedule.c文件
Linux下编写代码
3.2编译
使用gcc命令编译
3.3实验结果
后面同样的道理
4.实验详细过程
4.1实验代码整体思路介绍
(1)算法过程
我们实验的算法主要涉及的是动态优先级进程调度的原理,其原理大概如下。
(2)针对此过程,使用了如下函数
-
input()主要作为初始化
-
sort()用于进程入就绪队列
-
space()求取就绪队列中元素的个数
-
check()显示正在运行和处于就绪队列进程的一个情况
-
disp()显示单个进程状态
-
running()模拟进程运行过程
-
destory()销毁进程(销毁节点)
4.2实验代码详细介绍
(1)基本头文件的引入和结构体的声明
#include "stdio.h" //引入头文件用于标准输入输出
#include <stdlib.h> //动态内存管理的库函数
#define getpch(type) (type*)malloc(sizeof(type))//这句代码的意思是
//我将动态分配内存的代码(类型*)malloc(sizeof(类型))用getch(类型)来代替,
//目的是为了以下代码简写,比如在后面代码中出现的:p=getpch(PCB);
/*这一块的作用就是定义一个数据结构用于存放进程*/
struct pcb { /* 定义进程控制块PCB */
char name[10]; //进程名
char state; //进程状态:"W"-就绪态,"R"-运行态
int nice; //进程优先级
int ntime; //需要运行时间
int rtime; //已经运行的时间
struct pcb* link; //指向下一个进程控制块,用于构建就绪队列。
}*ready=NULL,*p; //ready 是一个全局指针,它作用是始终指向就绪队列的头部
//p 是一个临时指针,通常用于局部操作(如遍历链表、分配新节点等)。
(2)main函数中首先介绍基本的变量,比如len用于记录进程个数,h用于记录进程运行次数,具体的函数调用将在后面详细解释
/*主函数最大的作用就是while循环部分了
1)我首先定义一个len和h,len用于记录进程个数,h用于记录进程运行的次数
2)我需要一个char类型的字符ch来接受getchar()函数的返回值,这样做的目的是为了用户进行交互
3)我完成这个模拟第一步就是调用input()函数进行初始化操作,该操作的具体完成在后面(3)中讲。
4)之后我们调用space()函数先计算出就绪队列中进程个数并把值存在len中,它的作用就是为了当while循环的判断条件
5)接下来核心就是while循环了
1.1首先看条件(len!=0)&&(ready!=NULL)
len:表示就绪队列中的进程数量,通过 space() 函数计算。
如果 len 不为 0,表示就绪队列中还有进程等待调度。
如果 len 为 0,表示就绪队列为空,所有进程已经完成。
len != 0 从数量上判断是否还有进程。
ready != NULL 从指针上判断是否还有进程。
两者结合,确保循环的正确性,这个没有什么可争议的。
1.2其次while中核心代码有三部分,其余都是修饰可有可无,没必要解释
第一部分:
p=ready;
ready=p->link;
p->link=NULL;
p->state='R';
意思就是我从就绪队列中拿出头节点(第一个进程)让他
处于一个运行状态(当然此进程状态也修改为运行态R),
那么既然此进程运行了我就让他不要在就绪队列中,
此时我使用临时变量p完成队列的头删法:
p=ready;
ready=p->link;
p->link=NULL;这三句便是头删的核心代码。
第二部分
check();
调用该函数打印以上操作过后进程运行和就绪状态情况
第三部分:
running()
该函数就是来事实更改进程运行的信息的,也就是模拟进程的过程
他的一个思想就是:(注意:此思想在(8)中有解释)
1.你当前进程运行了我就让你已运行时间rtime+1
2.并且我判断从始至终这个进程已运行时间rtime是否==此进程完成需要的时间ntime,
如果相等就调用destory()将他销毁。
3.否则我就让此进程优先级nice-1并将状态改为w,然后让他进入就绪队列
*/
int main() /*主函数*/
{
int len,h=0; //len用于记录进程个数,h用于记录进程运行的次数
char ch;
input();
len=space();
while((len!=0)&&(ready!=NULL))
{
ch=getchar(); //用于用户交互不用在意
h++; //进程运行次数加1
printf("\n The execute number:%d \n",h); //输出进程运行次数
p=ready;
ready=p->link;
p->link=NULL;
p->state='R';
check();
running();
printf("\n按任一键继续......");
ch=getchar();
}
printf("\n\n 所有进程已经运行完成!\n");
ch=getchar();
return 0;
}
(3)input函数,此函数主要就是起着一个创建进程并初始化的作用,他需要用户自己输入所要创建的进程数并且此函数还调用sort()函数对进程进行优先级排列
(4)sort()函数主要目的就是你这个进程的优先级来从小到大的入队列,那么也因此有四种情况:
-
首进程入队(首元素入队列)
-
头插
-
合适位置插入队列
-
尾插
这四种情况将会在如下图示中体现
(5)sapce()求取就绪队列中的进程个数用于判断进程是否已经结束了
(6)此时回到主函数的while部分进行程序调度,将正在执行的进程从就绪队列中剔除,然后调用check()输出信息,以及调用running()进行判断进程是否结束和是否进入就绪队列,当然这一点也是刚刚在main方法中的说过思想,这里只是为了提供数据结构。
(6)check()和disp()配合使用用于形如以下格式的显示,这两个函数中的代码就单纯是做输出的操作全是printf函数,没什么可说的
(8)running()他的作用就是一个模拟进程的运行过程。
在此期间:
- 已运行rtime加1;
- 并且如果已运行时间等于需要运行时间说明该进程已经完成了,此时就应该调用destory()将他销毁,说白了就是if(p->rtime==p->ntime){destory();}
- 否则,就优先级减1,然后调用sort()函数再重新排到就绪队列中,说白了就是else{(p->nice)--;p->state='w';sort();}
(9)destory()这个函数就是对我们创建的进程进行销毁,防止占用资源
(10)至此动态优先级的代码解释完毕,总结就是,本算法主要是实现动态优先级的模拟,但是也有时间片轮转的调度,比如running()中的p->rtime==p->ntime就有体现。
5.实验结论
- 基本思想:为每个进程设置一个优先级,在调度的时候,选取优先级最大的任务去执行。
- 优先级可以是静态赋予:创建任务的时候,就指定优先级大小,并且保持不变,直到任务结束。
- 也可以是动态赋予:优先级在执行任务中动态改变。
- 为了防止高优先级进程无休止地运行下去,调度程序可以在每个时钟中断降低当前进程的优先级。
- 在平时中,我们可以把不同的任务划分为不同的优先级,然后在相同优先级的任务中使用时间片轮转。