基於C# winform的操作系統課程設計:SPOOLING假脫機輸入輸出技術模擬。
要求設計一個SPOOLING輸出進程和兩個請求輸出的用戶進程,以及一個SPOOLING輸出服務程序。當請求輸出的用戶進程希望輸出一系列信息時,調用輸出服務程序,由輸出服務程序將該信息送入輸出井。待遇到一個輸出結束標誌時,表示進程該次的輸出文件輸出結束。之後,申請一個輸出請求塊(用來記錄請求輸出的用戶進程的名字、信息在輸出井中的位置、要輸出信息的長度等),等待SPOOLING進程進行輸出。
SPOOLING輸出進程工作時,根據請求塊記錄的各進程要輸出的信息,將其實際輸出到打印機或顯示器。這裡,SP00LING輸出進程與請求輸出的用戶進程可並發運行。
進程調度採用隨機算法,這與進程輸出信息的隨機性相一致。兩個請求輸出的用戶進程的調度概率各為45%,SPOOLING輸出進程為10%,這由隨機數發生器產生的隨機數來模擬決定。
進程基本狀態有3種,分別為可執行、等待和結束。可執行態就是進程正在運行或等待調度的狀態;等待狀態又分為等待狀態1、等待狀態2和等待狀態3。
狀態變化的條件為:
①進程執行完成時,置為“結束”態。
②服務程序在將輸出信息送輸出井時,如發現輸出井已滿,將調用進程置為“等待狀態1”。
③SPOOLING進程在進行輸出時,若輸出井空,則進入“等待狀態2”。
④SPOOLING進程輸出一個信息塊後,應立即釋放該信息塊所佔的輸出井空間,並將正在等待輸出的進程置為“可執行狀態”。
⑤服務程序在輸出信息到輸出井並形成輸出請求信息塊後,若SPOOLING進程處於等待態,則將其置為“可執行狀態”。
⑥當用戶進程申請請求輸出塊時,若沒有可用請求塊時,調用進程進入“等待狀態3”。
系統中一共有兩個請求輸出的用戶進程,兩個進程分別命名為用戶進程A和用戶進程B。用戶需要輸出的文件可能不止一個,文件之間由一個輸出結束標誌隔開,本文實驗採用的文件輸出結束標誌為#號。
用戶需要在初始化階段輸入要進行輸出的全部文件內容,然後存進一個數組中。在用戶進程被調度時,如果滿足以下三個條件:當前還有文件沒有被輸出、輸出井中剩餘空間能夠放下該文件、有可用的輸出請求塊,就將該文件送到輸出井,然後申請一個輸出請求塊,加入請求塊等待隊列,等待SPOOLING輸出進程進行調度輸出。
輪到SPOOLING輸出進程佔用CPU時,SPOOLING輸出進程首先檢查請求塊等待隊列中是否有輸出塊需要輸出,沒有就進入等待狀態。否則,進行輸出,然後釋放輸出井空間以及相應的請求輸出塊,並喚醒因為沒有可用輸出塊而陷入沉睡的用戶進程。
SPOOLING輸出進程與用戶進程可以並發執行。本文將進程的一次執行過程(執行後進程不一定結束)抽象成一個函數,每次先產生一個隨機數,根據隨機數執行某一進程,如果該進程因為某些情況被阻塞,則產生下一個隨機數,調度另外的進程。用戶進程和SPOOLING輸出進程因為各種原因被輪流調度,即為並發執行。
整個系統的功能分為以下幾個部分:初始化函數、調度函數、用戶進程函數、SPOOLING輸出函數。初始化函數用於實現用戶文件的初始輸入與保存;調度函數實現用戶進程和SPOOLING輸出進程間的切換;用戶進程函數實現進程被調度後所完成的一系列動作;SPOOLING輸出函數表示輸出操作。
系統運行總的流程圖如圖1所示:
系統首先利用初始化函數對用戶輸入的內容進行初始化,然後產生一個0到1之間的隨機數R,根據R的大小判斷當前應該執行用戶進程還是輸出進程。等用戶進程和輸出進程都執行完畢後,程序運行結束,否則繼續進行調度。
用戶需要輸入自己想要“打印”的內容,初始化函數接受用戶輸入的內容後按照文件結束符進行切割,並將切割好的文件放入一個數組中,等用戶進程被調度時再將其送往輸出井中。
進程調度採用隨機算法,兩個請求輸出的用戶進程的調度概率各為45%,SPOOLING輸出進程為10%,本文采用隨機數來實現這一要求。進行進程調度時,隨機生成一個0到1之間的小數。如果該數小於等於0.45就將用戶進程A投入運行;如果該數處於0.45到0.9之間,就將用戶進程B投入運行;如果該數大於0.9,就將SPOOLING輸出進程投入運行。
用戶進程函數首先需要檢查當前進程是否滿足三個條件:還有文件沒有被輸出、輸出井中剩餘空間能夠放下該文件、有可用的輸出請求塊,滿足三個條件就將該文件送到輸出井併申請相應請求塊。
用戶進程函數執行的流程圖如圖2所示:
用戶進程執行時,如果發現文件已經輸出完畢,則進程運行結束。否則判斷輸出井是否有剩餘空間,無進入等待狀態1。輸出井有剩餘空間繼續判斷是否有可用輸出塊,有就將文件送往輸出井並請求一個輸出塊,並喚醒可能沉睡的輸出進程,否則進入等待狀態3。
SPOOLING輸出函數檢查是否有可輸出的請求塊,有則進行輸出並釋放相關資源,否則SPOOLING輸出進程等待。
SPOOLING輸出函數的流程圖如圖3所示:
PCB的定義如下:
class PCB {
/*
* 进程描述
*/
public int id ; //序号
public int status ; //状态,0表示可执行,123表示三个等待状态,4表示结束
public string [ ] contents = new string [ MaxFileCount ] ; //要输出的内容
public int [ ] flags = new int [ MaxFileCount ] ; //为1表示该文件已经被输出,初始全部为0
public int fileCount ; //用户真实输入的文件个数
}用戶進程中包括序號id、進程狀態status、要輸出的內容contents、文件輸出標誌flags以及真實文件個數fileCount。
其中,用戶進程可能存在的進程狀態有:0表示可執行狀態、1表示等待狀態1、3表示等待狀態3、4表示進程結束。
OutPutReqBlock定義如下:
class OutputReqBlock {
/*
* 输出请求块
*/
public int id ; //要求进行输出的进程的id
public int start ; //文件在输出井中的起始位置
public int length ; //文件长度
public int fileIndex ; //要输出文件的序号
public OutputReqBlock ( int id , int start , int length , int fileIndex ) {
this . id = id ;
this . start = start ;
this . length = length ;
this . fileIndex = fileIndex ;
}
}請求輸出塊中包括:請求該請求塊的進程id,文件在輸出井中的起始位置start、文件長度length、要輸出的文件在用戶所有文件中的序號。
OutputWell的定義如下:
class OutputWell {
/*
* 输出井
*/
public char [ ] buffer = new char [ MaxWellLen ] ; //输出缓冲区
public int begin = 0 ; //当前可用位置
public int restSize = MaxWellLen ; //剩余容量
}輸出井的參數有:緩衝區buffer,用於存放用戶放入的數據;當前可用位置begin,文件在輸出井中按順序存放,begin始終指向當前可用緩衝區的起始位置;剩餘容量restSize,緩衝區中剩餘的容量,初始時為緩衝區長度MaxWellLen。
用戶在文本框中輸入要“打印”的信息,然後選擇輸出內容屬於哪一個進程(A or B),最後點擊初始化按鈕,即可啟動初始化函數。初始化函數首先利用一個string對象存儲用戶輸入的內容。隨後,檢查用戶輸入的內容是否以#號結尾,不合法則提示用戶重新輸入。輸入合法後,將用戶輸入的內容按#號進行切割,切割形成多個字符串,最後利用生成的各項信息初始化一個PCB對象,放入等待隊列waitQueue中。
由於用戶可能多次點擊初始化按鈕,所以每次點擊前需要判斷當前進程是否已經初始化完成,如果已經初始化完成用戶卻再次點擊初始化按鈕,則會覆蓋原來的內容。
輸出井在系統界面進行加載時自動初始化。
初始化函數代碼略!
為了實現隨機性,每次要進行調度時,就利用C#的Random函數生成一個0到1之間的隨機數。如果該隨機數小於等於0.45表示接下來要調度用戶進程A;如果該隨機數處於0.45到0.9之間表示接下來要調度用戶進程B;如果該隨機數大於0.9表示接下來要調度SPOOLING輸出進程。
調度函數的實現如下:
private int dispatch ( ) {
/*
* 进程调度
*/
double res = rd . NextDouble ( ) ; //产生一个01之间的小数
if ( res <= 0.45 ) {
return 0 ;
} else if ( res <= 0.9 ) {
return 1 ;
} else {
return 2 ; //012分别表示两个进程和SPOOLing输出进程
}
} 用於實現用戶進程運行時所進行的一系列操作。
用戶進程被調度時,首先檢查是否還有文件未送到輸出井,沒有則置當前用戶進程為結束狀態,函數返回。
用戶進程尚未結束,說明還有文件未被送入輸出井。循環查找一個尚未輸出的文件塊(相應flag標誌為1),接著查詢輸出井中的剩餘空間是否還能放下此文件塊,如果不能,將進程狀態置為等待狀態1,函數返回。若還有剩餘空間,接著檢查是否還有可用的請求輸出塊,如果沒有將進程置為等待狀態3,函數返回。否則將文件塊送入輸出井並修改輸出井相關參數,然後申請一個請求輸出塊,放入輸出隊列printQueue中,等待SPOOLING輸出進程被調度時進行打印輸出。最後,如果SPOOLING輸出進程處於等待狀態,該用戶進程需要將其喚醒。
用戶進程函數運行時的各種情況通過一個列表進行保存,用於最後的結果展示。列表內容包括:當前調度序號、進程號、進程狀態、輸出井狀態、可用請求塊個數、文件序號、文件長度。
用戶進程函數代碼略!
輸出函數的功能是選擇一個請求輸出塊,然後對其中的內容進行輸出,最後釋放掉相應資源。
首先檢查輸出井是否為空,空就置輸出進程為等待狀態2,函數返回。否則檢查請求輸出隊列中是否有需要輸出的請求輸出塊,沒有函數返回。否則從請求輸出隊列中取出隊首的請求輸出塊,然後輸出請求塊,並釋放相應的輸出井空間和請求塊。
輸出函數進行輸出時,要將輸出內容顯示到文件輸出區域。
輸出函數代碼略!
用戶點擊“程序運行”按鈕後開始運行主函數,主函數中根據當前情況來動態調整運行的進程。
主函數首先判斷兩個用戶進程是否都已經初始化完畢,初始化完畢才能運行,否則提示出錯。
初始化完畢後再次點擊“程序運行”按鈕,只要有一個進程未處於結束狀態,或者有一個請求塊尚未被輸出,則繼續調度。調度時要判斷當前進程是否已經結束,結束就輸出相關狀態。
主函數代碼略!
實驗中用到的各種參數說明如表1所示:
| 參數名稱 | MaxWellLen | MaxFileCount | blockCount |
|---|---|---|---|
| 參數說明 | 輸出井長度 | 一個用戶可以輸出的最大文件數 | 請求塊個數 |
| 參數值 | 15 | 10 | 3 |
系統界面如圖4所示:
系統界面分為三個版塊:初始化、調度過程以及文件輸出區。初始化版塊包含一個文本框、一個選擇框、一個按鈕,用戶在文本框中輸入需要打印的文件,然後進行初始化。調度過程版塊主要為一個表格,用於展示進程調度的詳細過程。文件輸出區版塊用於展示所有文件的打印過程。
用戶首先選擇一個進程,初始默認為A,隨後將要輸出的文件放入初始化版塊的文本框中,然後點擊初始化按鈕,初始化成功,如圖5所示:
對於進程B同進行上述操作,如圖6所示:
兩個用戶進程初始化完成後,點擊程序運行按鈕,結果展示如圖7和圖8所示:
下面對圖7內容做簡單分析。如圖9所示:
第1次調度了輸出進程,因為此時輸出井空,所以輸出進程狀態為等待狀態2,此時可用請求塊個數為3。第2次調度進程A,進程A狀態為可執行,可用請求塊個數為3,A將文件0送到輸出井,文件0(“abcd”)長度為4。第3次調度輸出進程,輸出井可用空間為15-4=11,可用請求塊個數變為2,輸出A進程的文件0,如文件輸出區所示,並釋放相關空間。