1. 程式人生 > >Windows 多程序模型 摘抄自《windows核心原理與實現》

Windows 多程序模型 摘抄自《windows核心原理與實現》

3.1.1 多程序模型

現代作業系統總是提供可併發執行多個任務的環境,比如,使用者可以一邊接收電子郵件,一邊聽音樂,還可以同時跟網路上的朋友聊天。但是,現在主流的計算機只有有限的計算資源,例如只有一個單核處理器,較新的個人計算機可能配有一個雙核或四核的處理器。讓一個系統同時做幾件不同的事情,這是現代作業系統的基本特徵。假設計算機只有一箇中央處理器,它該如何表現得可以同時處理這些任務呢?基本的做法是把時間細分,然後在某個適當的時間粒度上輪流執行這些任務,讓每個任務都有機會被執行到。只要在人所能感知的時間粒度上(差不多是100~200 ms)每個任務都有機會做一點事情,使用者就會認為這些任務是同時在執行。這種並行也被稱為偽並行,而這樣的系統被稱為分時系統。有時候,計算機可能配有多個處理器,偽並行也有可能變成真並行,因為多個任務可以在多個處理器上真正地並行執行。Windows 作業系統能有效地支援這兩種情形,它的任務排程演算法可以很好地適應多處理器和多工的情形。

我們可以簡單地把程序(process)理解成一個獨立的任務,它不僅是一個正在被執行的程式,有自己的指令流和記憶體空間,同時,還擁有一顆僅供自己使用的虛擬CPU。從整個系統的角度來看,多個程序是分時執行的,但是對於每個程序來說,它的指令流按順序執行,就好像獨佔一顆CPU 一樣。圖3.1 顯示了三個程序在同一個處理器上分時執行的效果。

 
(點選檢視大圖)圖3.1 多個程序在一個處理器上分時執行

如圖3.1 所示,每個程序都有自己的執行時間軸,感覺就像在一個單獨的處理器上執行一樣。系統負責切換這些程序,讓它們得以輪流執行。當一個程序執行了一段時間以後,系統通過某種硬體機制或軟體機制獲得控制權,保留好這個程序的環境資訊,包括記憶體空間、暫存器和指令流資訊,然後挑選下一個要被執行的程序,恢復此程序的環境資訊,並把控制權交給它,由它使用下一個時間片段。

因此,作業系統需要做的事情是:維護一個全域性的程序表,記錄下當前有哪些程序正在被執行;把時間分成適當的片段,在現代處理器結構中,這可以通過設定時鐘中斷來完成,因而每次時鐘中斷到來時系統就會獲得控制權;在程序間實施切換,即保留上一個程序的環境資訊,恢復下一個程序的執行環境。現在我們可以明白,每個程序在執行過程中實際上是經常被打斷的,但由於系統能夠準確地維護好程序的環境資訊,所以,程序並不需要考慮自身的執行被頻繁地打斷這一事實,而是簡單地認為自己是在持續地執行。一個聰明的程序通過讀取系統的時間資訊,可以估算出自己獲得了多少真正有效的處理器執行時間。對於大多數程序而言,它們無須考慮這些因素。


3.1.2 程序與程式

理解了程序分時執行的情況以後,我們現在來看一看每個程序如何維護它自己獨立的執行環境。在講述程序環境以前,首先介紹一下程式的概念及其與程序之間的關係。在許多講述C/C++程式設計的書籍或者材料中,都會提到一個程式的記憶體佈局結構,如圖3.2 所示。實際上,這裡提到的程式記憶體結構與程序有一定的聯絡。每個程式必定是為了完成特定的功能或任務而存在,所以需要有程式碼來完成這些任務,但是僅僅有程式碼還不足以完成這些任務,還需要相應的儲存單元來配合,包括記憶體和暫存器。

 
圖3.2 一個C/C++程式的典型佈局結構

在現代程式中用到的記憶體區域有三種類型:靜態資料區、動態資料區,以及維護控制流資訊和區域性狀態的呼叫棧區域。對於一個C/C++程式而言,靜態資料區存放的是全域性變數以及靜態變數等資訊,這是程式設計師在編寫程式碼時就確定的資料區域;動態資料區則是在程式執行過程中根據需要而分配或回收記憶體的區域呼叫棧存放的是程式執行過程中的函式狀態資訊,當控制流從一個函式呼叫到另一個函式中時,呼叫棧記錄了呼叫函式中的狀態資訊,包括所有的區域性變數和當前指令流經過此次函式呼叫的位置。同時呼叫棧也用於傳遞函式引數。呼叫棧是實現函式呼叫的一種有效手段。

在一個程式的佈局結構中,程式碼和靜態資料區是可以預先確定的,命令列和環境資訊也可以在啟動一個程式的時候確定下來,但動態資料區和呼叫棧是程式執行過程中動態變化的。呼叫棧區域的大小通常是在程式啟動時或者在程式的二進位制屬性中預先設定好,如果沒有預設的值,則系統使用預設的大小。這些區域確定下來以後,中間的部分全部是動態資料區,由程式在執行過程中分配和管理。

現在我們回到程序的概念上來,如果一個程式是一個完全獨立的模組,也就是說,它不依賴於任何其他應用模組就可以完成其既定的功能或任務,那麼,圖3.2 實際上也可以代表一個程序的記憶體佈局。對於一個程序來說,它的存在之所以有意義,是因為它能完成特定的功能,這起碼需要有一個對應的二進位制模組,即前面所說的程式。然而,在實際的應用中,一個程式模組往往需要依賴於其他的模組來完成它的功能,比如某個第三方的二進位制庫。在這種情況下,圖3.2 中的程式碼區和靜態資料區在一個程序的記憶體佈局結構中會有多份,每份對應於一個二進位制模組。從這一層意義上講,程式的概念相當於一個二進位制可執行模組。由於這些被依賴的模組也可以稱為程式,所以,程式的概念在不同的上下文環境中可能指代不同的事物。典型情況有兩種:

  • 程式代表了一個程序,通常用啟動該程序的二進位制可執行模組的檔名來表示程式的名稱。在Windows 平臺上往往是一個.exe 檔案。
  • 程式代表了一個二進位制可執行模組,通常用該模組的檔名來表示程式的名稱。在Windows 平臺上,既可以是.exe 檔案,也可以是DLL 檔案或者其他字尾的檔案。

當然,程式的概念還可以用於其他的情形,甚至指代原始碼,這裡不一一展開討論。要記住的一點是,程序的概念是清晰和嚴格的,相對而言,程式的概念沒那麼清晰,兩者之間既有區別也有聯絡,有時候甚至混用。所以,讀者在閱讀書籍或文章時,若看到程式一詞,須注意上下文。

現在我們知道了,一個程序包含有程式碼和資料,以及在執行過程中有關控制流狀態的資訊,當它在執行過程中需要更多的記憶體時,它可以從動態記憶體區域獲取記憶體,不用的時候再釋放這些記憶體。為了建立起一個程序,需要一個初始的二進位制可執行模組,來得到一個圖3.2 所示的基本記憶體結構。如果該程序還需要額外的二進位制模組,則這些模組的程式碼和靜態資料區也會在適當的時候被引入到程序中來,並佔據一定的記憶體空間。

作業系統只維護程序的基本資訊,以便對系統中所有的程序實施管理,包括為每個程序分配處理器時間,以及在程序之間進行切換。既然程序是作業系統完成各種任務的基本載體,那麼作業系統如何控制程序的建立和終止呢?

首先,作業系統在引導過程中,必須把系統執行所必要的程序全部建立起來。所謂建立一個程序是指建立起基本的程序執行環境,然後把它加入到系統的全域性程序表中,使它有機會得到處理器時間和其他系統資源,以後,這個程序就可以按照其自身的邏輯獨立運行了。所以,作業系統會建立一組初始的程序,這是在系統引導時完成的。

其次,在一個系統的執行過程中,一個程序在必要的時候可能會建立其他的程序。雖然普通程序無法完成建立一個程序所需要的各種步驟,但是它可以請求系統幫它建立一個程序。這實際上建立起了程序之間的父子關係或從屬關係,即父程序建立子程序。另外,一個程序也可以為了響應使用者的請求而建立一個新的程序。比如使用者通過某種方式要求編輯一個文字檔案,系統中當前正在執行的程序接收到使用者的請求以後,建立一個新的子程序來開啟並編輯指定的文字檔案。除了系統的初始程序以外,其他的程序都是在系統執行過程中根據需要而建立的

程序如何終止呢?當一個程序完成了所有預定的功能以後,它的使命便已結束,最後的職責是通知作業系統,自己要終止了。作業系統接到請求以後,清理掉這個程序所佔據的各種資源,使它們能被回收,並且將該程序從系統的全域性程序表中移除,所以以後不會再給它分配任何處理器時間。這是比較溫和、友好的程序終止做法。在其他有些情況下,即使一個程序還沒有完成其任務,它也可能被另外的程序或者系統終止。比如,當程序出錯或者狀態不正確時,系統一旦檢測到其錯誤並且認為這是不可恢復的,則會強制終止此程序。另外,由於程序之間可能存在邏輯上的關聯關係,所以,一個程序可能會要求系統終止另一個程序,即俗稱的“殺死一個程序(kill a process)”。

前面提到了,一個程序可能會包含多個二進位制可執行模組,它們的程式碼區和靜態資料區都被引入到程序的記憶體空間中,但是,動態資料區可能是系統全域性的,也可能每個模組有其特有的動態資料區,這取決於每個模組自己的實現,但從概念上講,它們的動態資料區應該是程序全域性共享的。另外一個沒有提及的重要方面是,預設情況下程序只有一個控制流,儘管這個控制流可以跨越多個模組,但是它的呼叫棧只有一個。在現代作業系統結構中,程序可以有多個控制流,每個控制流對應一個呼叫棧。作業系統在建立程序時僅建立一個控制流,但是在該控制流的執行過程中,它又可以建立額外的控制流,這樣便形成了一個程序多個控制流的情形。這裡的控制流實際上正是下一節要討論的執行緒概念。