計算機操作系統實驗之進程調度(一)輪轉調度法(C語言)
進程調度有多種方法,前一週的實驗只寫了兩個,打算每種方法都分開整理一下,就是不知道什麼時候騰出時間寫完了。今天就先說輪轉調度法吧。
實驗目的
1、理解進程調度的任務、機制和方式
2、瞭解輪轉調度算法的基本原理
實驗內容與基本要求
用C,C++等語言編寫程序,模擬使用輪轉調度算法實現進程調度
輪轉調度法的基本原理
在輪轉調度法中,系統根據FCFS(先來先服務)原則,把所有的就緒進程排成一個就緒隊列,並可設置每隔一定時間間隔產生一次中斷,噹噹前運行進程結束或者時間用完時,執行一次進程調度,將CPU分配給新的隊首進程。這樣可以保證所有進程在一個確定的時間段內,都可以獲得一次CPU執行。
在這個算法中,隱含着一個基本假設:所有進程的緊迫性是一致的,這樣才能按照其到達的先後順序來執行。顯然這在實際當中並不存在,因此纔有了後面要說的優先級調度算法。
附:優先級調度法計算機操作系統實驗之進程調度(二)優先級調度法(C語言)
輪轉調度法中,需要考慮的核心問題是對時間片大小的確定。假如時間片選擇過小,那麼將會頻繁地進行進程調度和切換,這樣就增大了系統開銷;而時間片選擇過大,所有進程都能在一個時間片內完成,輪轉法實際上就退化成了FCFS算法,無法滿足短作業和交互式用戶的需求。當然,我們實驗中不考慮這個問題,只要設置好一個固定的時間片就行了。
實現思路及功能分析
要進行進程調度,依然要模擬出PCB的結構。在本次實驗中,要求展示時間片、cpu時間等信息,因此設計PCB的屬性包括:1.進程名字2.進程優先級3.進程的狀態4.時間片5.總共需要的運行時間6.cpu時間(已運行時間)7.還需要運行的時間8.計數器9.下一個要執行的進程指針。
這樣,我們使用帶頭結點的單鏈表來存儲進程隊列。按理來說,當進程運行結束後,應該將它移出隊列,但是爲了統一展示進程調度的過程、信息,已完成的進程不移出隊列,而是沉到隊列底部,只利用進程的狀態屬性來區分進程的完成情況。
實現輪轉調度算法本質就是按插入進程隊列的順序依次運行,已完成的進程沉到隊列最低端,直到所有進程都完成爲止。因此進程隊列邏輯上可以分成兩個部分,前一部分是未完成的進程,後一部分是已完成的進程,當我們執行完一個進程,要將它調到隊列尾部時,實際上調到的是未完成部分的尾部,已完成的部分不需要考慮。
因此執行完某個進程後,遍歷鏈表尋找進程的插入位置時,停止遍歷的條件有兩個,第一個條件是結點的next值爲空(此時該結點是最後一個結點,進程要插入的是鏈表最末尾),第二個條件是結點的next結點的狀態爲已完成(此時該結點往後均是已完成的結點,不需要考慮)
同時,因爲原來的首元節點被調到了後方,它的下一個結點成爲新的首元結點,我們遍歷鏈表執行調度時只需要讓指針保持指向首元結點即可。判斷調度是否結束只需要判斷首元結點的狀態是否爲已完成。若首元結點已完成,則說明它後面的結點也完成了。
對進程執行結束後的插入操作而言,並不關心進程是否已經完成,若它已經完成,則下次遍歷到它前一個就會停止;沒有完成,就會繼續拿來執行。假設其他進程都完成後,還有一個進程需要運行多次,顯然該進程開始所在的位置時首元結點,當運行完一個時間片後要爲它尋找插入位置,由於首元結點往後的結點均爲已完成,該結點被插入的爲止仍是首元結點,然後被再次調出來執行,直到完成爲止。
如圖所示,當p1運行結束後,需要將p1移到隊尾,首先讓頭指針指向p1的下一個結點,這樣p1就脫離了整個鏈表,所以需要一個s指針記錄它
遍歷整個鏈表爲p1尋找插入位置,在p指針指向的結點之後進行插入
要插入的位置有兩種情況:第一個是隊列的最末尾,這說明目前還沒有已經完成的進程;第二個是隊列中間,p結點之後都是已完成的進程。因爲鏈表中在尾部插入和在兩個結點之間插入涉及的指針操作有區別,所以要區分對待。
這樣,p1就被移到了隊列最尾部,在最後還需要將inedx指針指回首元結點,運行進程時,運行的始終是index指針指向的進程。
之所以說不需要考慮目前p1是否已經完成,是因爲這樣:當p2運行完後,用p指針遍歷鏈表爲p2尋找插入位置,假如p1沒有完成,p指針仍會指到p1的位置,p2會插入到p1之後;假如p1已經完成,p指針只會指到p1的前一個結點,p2會插入到p1之前。以此類推,最先完成的進程會被沉到最底。
算法流程圖
全部代碼
工程圖
ProcessScheduling.h
//
// ProcessScheduling.h
// ProcessSchedulingTest
//
// Created by Apple on 2019/10/21.
// Copyright © 2019 Yao YongXin. All rights reserved.
//
#ifndef ProcessScheduling_h
#define ProcessScheduling_h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>
#define TIME_SLICE 2
//線程狀態:就緒 等待 完成
enum process_type{
process_type_waitting = 'W',
process_type_ready = 'R',
process_type_finish = 'F'
};
//進程控制塊結構體
typedef struct PCB_Type{
//進程的名字
char *name;
//進程的優先級
int priority;
//仍需運行時間
int need_time;
//進程的狀態 就緒 等待
char state;
//時間片
int time_slice;
//cpu時間 ->已運行時間
int cpu_time;
//計數器
int time_count;
//總共需要的運行時間
int total_time;
//下一個要執行的進程
struct PCB_Type *next;
}PCB;
//創建新的進程
void create_process(PCB *running_list,char *name,int need_time);
//展示當前就緒隊列狀態
void show(PCB *running_list);
//輪轉法
void round_robin(PCB *running_list);
//找到當前隊列中第一個進程,將它狀態變爲就緒
void set_readyR(PCB *running_list);
#endif /* ProcessScheduling_h */
ProcessScheduling.c
//
// ProcessScheduling.c
// ProcessSchedulingTest
//
// Created by Apple on 2019/10/21.
// Copyright © 2019 Yao YongXin. All rights reserved.
//
#include "ProcessScheduling.h"
//創建新的進程
void create_process(PCB *running_list,char *name,int need_time){
//申請一個內存控制塊的空間
PCB *p = (PCB *)malloc(sizeof(PCB));
assert(p != NULL);
//設置該控制塊的值
p->name = name;
p->need_time = need_time;
//狀態
p->state = process_type_waitting;
//時間片
p->time_slice = 0;
//cpu時間
p->cpu_time = 0;
//計數器
p->time_count = 0;
//總需用時
p->total_time = need_time;
//默認優先級一致
p->priority = 1;
//下個進程
p->next = NULL;
//放入運行隊列中
PCB *s = running_list;
while (s->next != NULL) {
s = s->next;
}
s->next = p;
}
//展示當前就緒隊列狀態
void show(PCB *running_list){
PCB *p = running_list->next;
if (p == NULL) {
printf("當前隊列中無進程\n");
return;
}
printf("進程名 優先級 時間片 cpu時間 需要時間 進程狀態 計數器\n");
while (p != NULL) {
printf("%s %4d %4d %4d %4d %c %4d\n",p->name,p->priority,p->time_slice,p->cpu_time,p->need_time,p->state,p->time_count);
p = p->next;
}
printf("\n");
}
//輪轉法
void round_robin(PCB *running_list){
/*
每次運行完進程後,會將該進程從隊首調到隊尾
即下一次運行的仍是鏈表第一個結點的進程
最先完成的進程處於最末尾
*/
//記錄第一個結點的位置
PCB *index = running_list->next;
if (index == NULL) {
printf("當前隊列已空\n");
return;
}
while (index != NULL && index->state != process_type_finish) {
//按時間片運行該進程,即修改進程的cpu時間和需要時間、計數器
PCB *s = index;
s->time_slice = TIME_SLICE;
//cpu時間(即已運行時間) = 總需時間 -(當前cpu時間+時間片)
//若已完成則直接顯示總需時間
s->cpu_time = (s->cpu_time + TIME_SLICE)<s->total_time?s->cpu_time + TIME_SLICE:s->total_time;
//若當前仍需時間減時間片小於等於零,則說明進程已完成
s->need_time = (s->need_time - TIME_SLICE)>0?s->need_time - TIME_SLICE:0;
//計數器+1
s->time_count += 1;
//判斷該進程是否結束
if (s->need_time == 0) {
//修改進程狀態
s->state = process_type_finish;
}else{
s->state = process_type_waitting;
}
//將該進程調到隊尾
//1.頭指針指向首元結點的下一個結點
running_list->next = s->next;
//2.遍歷整個鏈表,將其插入到未完成隊列的最尾端(其後是已完成的隊列)
PCB *p = running_list;
//2.1尋找插入位置
while (p->next != NULL && p->next->state != process_type_finish) {
p = p->next;
}
//2.2判斷插入位置
if (p->next == NULL) {
//最後一個
p->next = s;
p->next->next = NULL;
}else{
//不是最後一個,其後仍有結點
s->next = p->next;
p->next = s;
}
//重新指向首元結點
index = running_list->next;
set_readyR(running_list);
//展示當前隊列狀況
show(running_list);
}
}
//找到當前隊列中第一個進程,將它狀態變爲就緒
void set_readyR(PCB *running_list){
PCB *s = running_list->next;
if (s == NULL) {
printf("當前隊列已空\n");
return;
}
if (s->state != process_type_finish) {
s->state = process_type_ready;
return;
}
}
Main.c
//
// main.c
// ProcessSchedulingTest
//
// Created by Apple on 2019/10/21.
// Copyright © 2019 Yao YongXin. All rights reserved.
//
#include "ProcessScheduling.h"
int main(int argc, const char * argv[]) {
//運行(就緒)隊列(頭結點不儲存信息)
PCB *running_list = (PCB *)malloc(sizeof(PCB));
running_list->next = NULL;
int p_number;
printf("請輸入要創建的進程數目:\n");
scanf("%d",&p_number);
printf("請輸入進程名字和所需時間:\n");
for (int i = 0; i < p_number; i++) {
//create(running_list);
char *name = (char *)malloc(sizeof(char));
int time;
scanf("%s %d",name,&time);
create_process(running_list, name, time);
}
//輪轉法
set_readyR(running_list);
printf("調度前:\n");
show(running_list);
printf("調度後:\n");
round_robin(running_list);
return 0;
}
結果截圖