实验要求:
1.对N个进程采用动态优先权算法的进程调度。
2.每个用来表示进程的PCB包含一下字符段:
(1) 进程标识数ID,
(2) 进程优先数PRIORITY
(3) 进程已占用CPU的时间CPUTIME
(4) 进程还需要的CPU时间ALLTIME
(5) 进程状态STATE
3.优先数改变规则:进程在就绪队列中每呆一个时间片,优先数加1,进程每运行一个时间片优先度减3。
4.设置调度前的初始状态。
5.将每个时间片的进程情况显示出来。
这个实验说是进程调度问题,其实就是算法比赛中的模拟题,先总结一下题目要求的操作。
拆分一下其实就两个操作:
1.运行当前优先度最高的程序,让他的优先度-3,其他的程序优先度+1。
2.将一个进程放到链表上它该在的位置上。
第一个操作如果是不加任何优化去做的话每次都要O(|LIST|)去扫一遍链表修改优先度,其实可以把这个操作改变一下,增加一个全局变量Lazy,每次让Lazy+1,当一个新的进程进来的时候把它的PRIORITY-Lazy,相当于把原先的PRIORITY都做了一个偏移。这样子就不用扫描一遍链表让每个程序的优先度都+1了,复杂度可以降到O(1),当然这个做法可能有溢出的风险。这里暂且不考虑这个问题。
第二个操作如果在简单的单链表结构中需要O(|LIST|)的复杂度来完成这一个操作。
一些优化:
因为这个模拟其实只是需要实现从一堆数据中找到某一维最大的那个就可以了,这个操作可以使用二叉堆来完美在O(log|N|)的时间复杂度下完成。但数组版本在操作系统中显然是不可接受的(浪费太多空间了)。而链表版本的二叉堆其实是很愚蠢的,但为了强行降低一下时间复杂度以及强化锻炼一下编码能力,敲一个链表版本的二叉堆还是有些许必要的。
链表版本:
#include <stdio.h>
#include <malloc.h>
#define INT_MAX 2147483647
typedef struct PCB{
int ID;
int PRIORITY;
int CPUTIME;
int ALLTIME;
int STATE; // 0表示在就绪状态,1表示在运行状态。
struct PCB* NEXT;
}PCB;
PCB *head;
int Lazy;
void build(){
head = (PCB *)malloc(sizeof(PCB));
head->NEXT = NULL;
head->PRIORITY = INT_MAX; // 设置一个哨兵
}
void show(){ // 展示所有进程的状态
printf("----------------------------当前程序的状态----------------------------\n");
printf("ID PRIORITY CPUTIME ALLTIME STATE\n");
PCB *tmp = head->NEXT;
while(tmp != NULL){
printf("%-12d", tmp->ID);
printf("%-18d", tmp->PRIORITY);
printf("%-17d", tmp->CPUTIME);
printf("%-17d", tmp->ALLTIME);
printf("%d\n", tmp->STATE);
tmp = tmp->NEXT;
}
}
PCB* Newnode(){
PCB *newnode = (PCB *)malloc(sizeof(PCB));
printf("ID: ");
scanf("%d",&newnode->ID);
printf("\n");
printf("PRIORITY: ");
scanf("%d",&newnode->PRIORITY);
newnode->PRIORITY -= Lazy;
printf("\n");
printf("CPUTIME: ");
scanf("%d",&newnode->CPUTIME);
printf("\n");
printf("ALLTIME: ");
scanf("%d",&newnode->ALLTIME);
printf("\n");
newnode->STATE = 0;
return newnode;
}
void add(PCB *newnode){
PCB *find = head;
while(find->NEXT != NULL && find->NEXT->PRIORITY > newnode->PRIORITY)
find = find->NEXT;
newnode->NEXT = find->NEXT;
find->NEXT = newnode;
}
void run(){ //将当前进程运行一个时间片
PCB *runnode = head->NEXT;
runnode->STATE = 1;
runnode->CPUTIME++;
runnode->PRIORITY-=4;
show();
Lazy++;
if(runnode->ALLTIME == runnode->CPUTIME){ // 如果已经跑完了的话
printf("ID:%d done\n", runnode->ID);
head->NEXT = runnode->NEXT;
free(runnode);
show();
return;
}
runnode->STATE = 0;
head->NEXT = runnode->NEXT;
add(runnode); // 直接用写好的add函数修改运行进程的位置就OK了。
}
int main(){
build(); // 搭建好头结点。
for(int i = 1;i <= 5;++ i)
add(Newnode());
show();
while(head->NEXT != NULL){
run();
}
return 0;
}
这个版本的代码过于简单,没有解释的必要。
二叉堆版本:
#include <stdio.h>
#include <malloc.h>
#define INT_MAX 2147483647
#define debug printf("?????????????????\n")
/*
heap version
*/
/*
data:
1 3 0 5
2 4 0 6
3 5 2 4
4 1 2 4
5 2 3 7
*/
typedef struct PCB{
int ID;
int PRIORITY;
int CPUTIME;
int ALLTIME;
int STATE; // 0表示在就绪状态,1表示在运行状态。
struct PCB *ls,*rs,*fa;
}PCB;
PCB *top;
int Lazy,tot; // tot 用于储存目前有多少个节点。
int num = 0;
void dfs(PCB *cur){
if(cur == NULL || (++num > 20)) return;
printf("%-12d", cur->ID);
printf("%-18d", cur->PRIORITY);
printf("%-17d", cur->CPUTIME);
printf("%-17d", cur->ALLTIME);
printf("%d\n", cur->STATE);
dfs(cur->ls);
dfs(cur->rs);
}
void show(){ // 展示所有进程的状态
num = 0;
printf("----------------------------当前程序的状态----------------------------\n");
printf("ID PRIORITY CPUTIME ALLTIME STATE\n");
dfs(top);
}
void myswap(PCB *fa,PCB *son){
PCB *tmp1 = son->ls,*tmp2 = son->rs;
if(fa->ls == son){
if(fa->fa != NULL){
if(fa == fa->fa->ls)
fa->fa->ls = son;
else
fa->fa->rs = son;
}
son->ls = fa;
son->rs = fa->rs;
son->fa = fa->fa;
fa->fa = son;
fa->ls = tmp1;
fa->rs = tmp2;
}
else if(fa->rs == son){
if(fa->fa != NULL){
if(fa == fa->fa->ls)
fa->fa->ls = son;
else
fa->fa->rs = son;
}
son->rs = fa;
son->ls = fa->ls;
son->fa = fa->fa;
fa->fa = son;
fa->ls = tmp1;
fa->rs = tmp2;
}
else{
if(son->fa->ls == son){
son->fa->ls = fa;
}
else{
son->fa->rs = fa;
}
son->ls = fa->ls;
son->rs = fa->rs;
fa->ls = tmp1;
fa->rs = tmp2;
fa->fa = son->fa;
son->fa = NULL;
}
}
PCB* Newnode(int id,int priority,int alltime,int cputime,int flag){
PCB *newnode = (PCB *)malloc(sizeof(PCB));
newnode->ID = id;
if(flag)
newnode->PRIORITY = priority - Lazy;
else
newnode->PRIORITY = priority;
newnode->ALLTIME = alltime;
newnode->CPUTIME = cputime;
newnode->STATE = 0;
newnode->ls = newnode->rs = newnode->fa = NULL;
return newnode;
}
PCB *getlast(int tot,int id,int priority,int alltime,int cputime,int flag){ // 为了方便把加入和删除操作统一到一个函数中,多几个参数。
int save[32];
int tmp = tot,length = 0;
while(tmp){
save[length++] = (tmp%2);
tmp/=2;
}
PCB *cur = top;
for(int i = length-2; i >= 0; -- i){
if(!save[i]){
if(cur->ls == NULL){
cur->ls = Newnode(id,priority,alltime,cputime,flag);
cur->ls->fa = cur;
}
cur = cur->ls;
}
else{
if(cur->rs == NULL){
cur->rs = Newnode(id,priority,alltime,cputime,flag);
cur->rs->fa = cur;
}
cur = cur->rs;
}
}
return cur;
}
void ins(int id,int priority,int alltime,int cputime,int flag){
if(top == NULL){
top = Newnode(id,priority,alltime,cputime,flag);
++tot;
return;
}
PCB *cur = getlast(tot+1,id,priority,alltime,cputime,flag);
++tot;
while(cur->fa != NULL && cur->fa->PRIORITY < cur->PRIORITY){
if(top == cur->fa)
top = cur;
myswap(cur->fa,cur);
}
}
void down(){
PCB *cur = top,*lson = cur->ls,*rson = cur->rs;
while((lson != NULL && lson->PRIORITY > cur->PRIORITY) || (rson != NULL && rson->PRIORITY > cur->PRIORITY)){
if(rson == NULL || (lson->PRIORITY > rson->PRIORITY)){
myswap(cur,lson);
if(cur == top){
top = lson;
}
}
else{
myswap(cur,rson);
if(cur == top){
top = rson;
}
}
lson = cur->ls,rson = cur->rs;
}
}
int time;
void del(){
PCB *last = getlast(tot,0,0,0,0,0),*fa = last->fa;
if(fa == NULL){
free(last);
top = NULL;
return;
}
myswap(top,last);
if(top->fa != NULL){
if(top->fa->ls == top){
top->fa->ls = NULL;
}
else{
top->fa->rs = NULL;
}
}
free(top);
top = last;
down();
--tot;
}
void run(){ //将当前进程运行一个时间片
top->STATE = 1;
show();
top->CPUTIME++;
top->PRIORITY-=4;
Lazy++;
if(top->ALLTIME == top->CPUTIME){ // 如果已经跑完了的话
printf("ID:%d done\n", top->ID);
del();
return;
}
int id = top->ID;
int cputime = top->CPUTIME;
int priority = top->PRIORITY;
int alltime = top->ALLTIME;
del();
ins(id,priority,alltime,cputime,0); // 直接用写好的add函数修改运行进程的位置就OK了。
show();
}
/*
*/
int main(){
ins(1,3,5,0,1);
ins(2,4,6,0,1);
ins(3,5,4,2,1);
ins(4,1,4,2,1);
ins(5,2,7,3,1);
while(top != NULL)
run();
return 0;
}
二叉堆版本就比较考验代码能力了,尤其是对指针的应用。
数组版本的二叉堆是这样去进行push和pop操作的。
push:将新的元素设置为最后一个元素然后进行up操作
pop:将顶部元素于最后一个元素交换位置,然后删除最后一个元素(即让size–),再让top元素进行down操作
在数组版本中,这两个操作都很好进行,因为数组版本运用了完全二叉树编码的性质,所以指针不需要我们自己去控制,会自动更改,但到了指针版本就没有那么简单了,所有的指针都需要我们自行控制。
为了找到最后一个元素的位置,我们需要一个函数来实现这个功能。在代码中是getlast函数,当push操作的时候除了找到它还需要新建一个节点,在pop中不需要新建节点,为了方便统一这两个操作所以多增加了几个参数。
这个getlast是如何在log级别的复杂度下找到最后一个元素位置的呢?可以观察一下完全二叉树的性质,从1开始编码节点,用二进制表示一个节点,比如7就是111,那么从1出发,0表示向左走,1表示向右走,走到7的步骤就是11,恰好是后面两位数字。这个性质在任何一个数字上都是符合的,具体的证明可能需要一点数学知识,这里只需要知道可以这么使用就可以了。这个寻找的过程复杂度是O(log)级别的,所以所有的操作都可以控制在log的级别上。
在代码中另一个比较关键的是myswap函数
void myswap(PCB *fa,PCB *son){
PCB *tmp1 = son->ls,*tmp2 = son->rs;
if(fa->ls == son){
if(fa->fa != NULL){
if(fa == fa->fa->ls)
fa->fa->ls = son;
else
fa->fa->rs = son;
}
son->ls = fa;
son->rs = fa->rs;
son->fa = fa->fa;
fa->fa = son;
fa->ls = tmp1;
fa->rs = tmp2;
}
else if(fa->rs == son){
if(fa->fa != NULL){
if(fa == fa->fa->ls)
fa->fa->ls = son;
else
fa->fa->rs = son;
}
son->rs = fa;
son->ls = fa->ls;
son->fa = fa->fa;
fa->fa = son;
fa->ls = tmp1;
fa->rs = tmp2;
}
else{
if(son->fa->ls == son){
son->fa->ls = fa;
}
else{
son->fa->rs = fa;
}
son->ls = fa->ls;
son->rs = fa->rs;
fa->ls = tmp1;
fa->rs = tmp2;
fa->fa = son->fa;
son->fa = NULL;
}
}
前面说到过指针版本的二叉树中指针需要我们自己进行控制,所以这个swap函数就格外的难写。什么时候会出现交换两个节点的操作呢?up和down中交换父子节点,pop操作中交换top和last节点。所以myswap中把两者划分了三个情况:
1.子节点是父节点的左儿子
2.子节点是父节点的右儿子
3.其他(即pop操作的情况)
分清楚情况之后其实代码就容易写出来了,在纸上模拟一下这三种情况就可以了,但要特别注意的是我们虽然只对两个节点进行了交换,但会有第三个节点中指针发生了改变。
1.父子节点交换的时候父亲的父亲左右儿子中其中一个指针会发生改变。
2.toplast节点交换的时候last的父亲左右儿子中其中一个指针会发生改变。
这两点是比较容易忽略的,把它们加上myswap函数就完成了。
还有一点需要注意的是show函数也需要更改,这是一个对二叉堆dfs的问题,不细表。如果需要输出的时候也像链表一样从小到大输出,在不牺牲复杂度的条件下只能改用平衡树去解决这个问题了,至于链表版本的平衡树怎么敲,这又是另外一个严肃的问题。
最后说一点,这两个程序都没有实现一个功能:当前运行的程序结束后如果出现了和其他程序优先度相同且是最大的情况应该先运行其他程序这个公平性的功能。