談談作業系統的多程式

ZhiboZhao發表於2021-08-06

作業系統的多程式影像

作業系統主要控制計算機的硬體,而其中最重要的就是CPU,因此作業系統的最主要工作就是控制CPU更好地執行命令,那麼在介紹程式之前,我們首先來了解一下CPU的工作原理是怎樣的。

一、CPU的工作模式

首先,CPU取出程式指標PC,然後到對應的暫存器中取出地址為PC的指令,通過譯碼來分析指令的內容,移動相應暫存器的內容來實現指令,最後程式指標PC指向下一條指令,CPU重複上述工作過程。總的來說,CPU的工作模式就是 “取址-譯碼-執行”。比如CPU執行如下所示的指令:

50: mov ax, [100]
51: mov bx, [101]
52: add ax, bx
......
100: 1
101: 2

首先,PC 指標被設定為 50,CPU取出指令 mov ax, [100] 執行,將記憶體地址為 100 中的資料移動到暫存器 ax 中。當 50 中的指令完成後,PC指標指向 51,CPU執行 mov bx, [101],最後 PC 指向 52,將 axbx 的資料相加,此時 ax=3

這種工作模式在只能等待某一段程式執行完成之後才能執行另外一段,那麼當正在執行的程式卡在某一條指令時(比如讀取磁碟上的資料,呼叫IO等),CPU就會停在這裡等待該指令完成,因此CPU的利用率很低。

二、多道程式系統

多道程式系統是在計算機記憶體中同時存放幾道相互獨立的程式,使它們在管理程式控制之下,相互穿插的執行 (系統由一個程式轉而執行另一個程式時需要使用中斷機構中斷正在執行的程式) ,以使系統中的各種資源儘可能地滿負荷工作,從而提高整個計算機系統的使用效率。 兩個或兩個以上程式在計算機系統中同處於開始和結束之間的狀態,這就稱為多道程式系統。其技術執行的特徵:多道、巨集觀上並行、微觀上序列。如下圖所示:

如上圖所示:

  1. 單道程式的 CPU 和 DEV 的執行效率為:\(CPU = (10+5+10+10+5)/80 = 0.5, DEV = (5+10+10+5+10)/80 = 0.5\)
  2. 多道程式的 CPU 和 DEV 的執行效率為:\(CPU = (10+10+5+5+10)/45 = 0.89, DEV = (10+5+5+10+10)/45 = 0.89\)

因此,多道程式系統能夠顯著提高 CPU 和 DEV 的利用率。在程式執行的時候,每個程式都需要 PC 以及各種暫存器來描述程式的執行狀態,那麼這些執行著的程式就叫做程式。通過上述分析,我們可以將程式的特徵總結如下:

  1. 動態性:程式是程式的一次執行,它有著建立、活動、暫停、終止等過程,具有一定的生命週期,是動態地產生、變化和消亡的。動態性是程式最基本的特徵。
  2. 併發性:指多個程式實體同時存於記憶體中,能在一段時間內同時執行。併發性是程式的重要特徵,同時也是作業系統的重要特徵。引入程式的目的就是為了使程式能與其他程式的程式併發執行,以提高資源利用率。
  3. 獨立性:指程式實體是一個能獨立執行、獨立獲得資源和獨立接受排程的基本單元。凡未建立PCB的程式都不能作為一個獨立的單元參與執行。
  4. 非同步性:由於程式的相互制約,使得程式具有執行的間斷性,即程式按各自獨立的、不可預知的速度向前推進。非同步性會導致執行結果的不可再現性,為此在作業系統中必須配置相應的程式同步機制。
  5. 結構性:每個程式都配置一個PCB對其進行描述。從結構上看,程式實體是由程式段、資料段和程式控制塊三部分組成的。

三、多程式影像

3.1程式的狀態轉換

根據程式的執行狀態,可以把程式簡單地分為新建態,就緒態,執行態,阻塞態和終止態五種狀態。比如,你去食堂打飯,那麼排隊的人組成了一個佇列,1)每個處在佇列的人的狀態就是就緒態;2)同一時刻視窗只能處理一個人的請求,因此正在處理的那個人被稱為執行態;3)但是當準備打飯的時候忘了帶飯卡,這個人就從執行態變為阻塞態,直到取回飯卡又變成就緒態。程式之間的狀態轉換如下圖所示:

3.2 程式的切換

在同一時刻,記憶體中存在多個程式,可以根據使用者的操作而進行切換。那麼程式切換的具體過程是怎樣的呢?如下圖所示,假設記憶體中有兩個程式,在程式1執行過程中切換到程式2 ,此時:1)記錄下程式1中的PC指標,否則無法切換回來; 2)將PC指標切換為程式2的程式開始為止,開始執行程式2。

但是,由於在執行程式2的過程中,更改了暫存器 ax, bx 的值,因此再次切回到程式1時,52號命令 'add ax, bx' 執行完畢後 ax 中的值為 50,而非 1。因此在程式切換時,除了要記錄下程式指標 PC 之外,還要記錄一些與程式1 密切相關的暫存器的值,這個存放資訊的結構被稱為程式控制塊(Progress Control Block, PCB)。當再次切換回來時,首先載入程式1 的PCB中的資料,然後執行相應的指令。因此,根據上述分析,程式之間的切換可以描述為:

switch_to(pCur, pNew) //pCur 表示當前程式的PCB,pNew 表示待切換程式的PCB
{
	// 將當前執行程式的狀態儲存在PCB中
	pCur.ax = CPU.ax;
	pCur.bx = CPU.bx;
	...
	pCur.cs = CPU.cs;
	pCur.pc = CPU.pc;
	// 載入新的程式的PCB,執行新的程式
	CPU.ax = pNew.ax;
	CPU.bx = pNew.bx;
	...
	CPU.cs = pNew.cs;
	CPU.pc = pNew.pc;
}

3.3 程式的排程策略

由於在就緒佇列中存在多個就緒態的程式,那麼在進行程式排程時也必須遵守一定的原則,最常見的是:

  1. 高階排程: 又稱作業排程、長程排程。從輸入系統的一批作業(job, 使用者提交給作業系統計算的一個獨立任務)中按照預定的排程策略挑選若干作業進入記憶體,為其分配所需資源並建立對應作業的使用者程式。
  2. 中級排程: 又稱平衡排程,中程排程。根據記憶體資源情況決定記憶體所能容納的程式數目,並完成外存和記憶體中程式對換工作。
  3. 低階排程:又稱程式排程/執行緒排程,短程排程。根據某種原則決定就緒佇列中那個程式/執行緒先獲得處理器,並將處理器出讓給它使用。

其中常見的低階排程演算法主要包括:

  1. 先來先服務(First Come First Server, FCFS)演算法。
  2. 最短作業優先(Shortest Job First, SJF)演算法。
  3. 最短剩餘時間優先(Shortest Remaining Time First, SRTF)演算法: 假設當前某程式/執行緒正在執行,如果有新程式/執行緒移入就緒佇列,若它所需的CPU執行時間比當前執行的程式/執行緒所需的剩餘CPU時間還短,搶佔式最短作業優先演算法強行剝奪當前執行者的控制權,排程新程式/執行緒執行。
  4. 最高響應比優先(Highest Response Ratio First, HRRF)演算法:非剝奪式演算法。其中,響應比 = (作業已等待時間 + 作業處理時間) / 作業處理時間。
  5. 優先順序排程演算法:優先順序高的選擇程式/執行緒優先選擇。
  6. 輪轉排程(Round-Robin, RR)演算法: 也稱時間片排程。就緒佇列的程式輪流執行一個時間片。
  7. 多級反饋佇列(Multi-Level Feedback Queue, MLFQ)演算法。

3.4 多程式之間相互影響

想象一下,如果程式1的程式中有 mov [100], ax 而恰好在這個時刻CPU需要切換到程式2,程式2的開始指令剛好存在100的記憶體地址中。由於程式1中干擾了記憶體100中的資料,造成程式2損壞。為了使多個程式能夠在記憶體中很好地共存,作業系統給每個程式單獨分配一段記憶體空間,通過對映表將程式中的記憶體對映為該記憶體空間的一塊區域。如此,雖然在程式碼中所使用的邏輯地址是一樣的,但是通過對映表所得到的實際實體地址是不同的。如下圖所示:

3.5 程式之間的通訊

有的時候,多個程式之間需要進行合作,共享同一塊緩衝區,比如列印(PPT,PDF,Word都可以列印),首先每個程式需要往列印佇列中存入資料,由於程式是交替執行的,很有可能出現PPT還沒存完,Word又往佇列中儲存,造成混亂。因此需要設計一種機制來控制程式切換的時機,即:當第一個程式完成時才能切換下一個程式。從上述可以抽象出來一個“生產者-消費者”模型,如下:

while(true){	//生產者程式
	while(counter == BUFFER_SIZE){;}	//BUFFER_SIZE代表緩衝區大小
	buffer[in] = item;	//存入資料
	in = (in+1)%BUFFER_SIZE;
	counter++;	//計數器加1
}
while(true){	//消費者程式
	while(counter == 0){;}	//當緩衝區不為空時,讀出資料
	item = buffer[out];
	out = (out+1)%BUFFER_SIZE;
	counter--;	//計數器減1
}
//其中,counter++ 在彙編中的實際過程為:
register = counter;
register = register+1;
counter = register;

假如初始狀態:counter = 5,那麼該執行過程描述為下圖。理論上來講,生產者使得 counter+1,而消費者使得 counter-1,因此完整執行一遍生產者和消費者之後 counter 應該不變,但由於二者交替執行,因此導致了 counter的混亂。

一種解決的辦法是:在生產者執行的時候給 counter 上鎖,如果程式想要切換到消費者,首先檢查是否上鎖,如果上鎖則該時刻不能切換,否則可以切換。優化後的執行過程如下:

參考資料:

https://zhuanlan.zhihu.com/p/164842096
https://www.cnblogs.com/jin-xin/articles/10078845.html
https://www.bilibili.com/video/BV1d4411v7u7?p=9

相關文章