1. 程式人生 > >現代作業系統:第二章 程序和執行緒

現代作業系統:第二章 程序和執行緒

作業系統中最核心的概念就是程序,這是對正在執行的程式的抽象。

程序是作業系統資源分配的基本單位,而執行緒是任務排程和執行的基本單位。

2.1 程序

作業系統最核心的概念就是程序,它是對正在執行的程式的一個抽象,也可以理解為對處理器的抽象。即使可用的CPU可用,但是依然可以支援多程序(偽)併發操作。

2.1.1 程序模型

在程序模型中,計算機上所有的可執行的軟體,通常也包括作業系統,被組織成若干順序程序,簡稱程序。一個程序就是一個正在執行程式的例項。

在這裡插入圖片描述

一個程序就是某種型別的活動,它有程式、輸入、輸出以及狀態。單個處理器可以被若干程序共享,它使用某種排程演算法決定何時停止一個程序的工作,並轉而為另一個程序提供服務。

2.1.2 程序的建立

四種主要事件會導致程序建立:

  1. 系統初始化
  2. 正在執行的程式執行了建立程序的系統呼叫
  3. 使用者請求建立一個程序
  4. 一個屁處理作業的初始化

在UNIX系統中,只有一個系統呼叫可以用來建立新的程序:fork。這個系統呼叫會建立一個與呼叫程序相同的副本。程序建立之後,父程序和子程序有各自不同的地址空間。如果其中某個程序在地址空間中修改了一個字,這個修改對其他程序而言是不可見的。

2.1.3 程序的終止

四種主要事件會導致程序的終止:

  1. 正常退出(自願的)
  2. 出錯退出(自願的)
  3. 嚴重錯誤(非自願)
  4. 被其他程序殺死(非自願)

2.1.4 程序的層次結構

UNIX系統中,程序 建立另一個程序後,父程序和子程序就以某種形式繼續保持聯絡,程序只有一個父程序,但可以有多個子程序。

Windows中沒有程序層次的概念,所有程序都是地位相等的,但父程序建立愛你子程序後會得到一個令牌(稱為控制代碼),該控制代碼可以用來控制子程序,但父程序有權把這個令牌傳遞給其他程序,這樣,就不存在層次概念了。

2.1.5 程序的狀態

程序有三種狀態:

  1. 執行態(該時刻程序實際佔用CPU)
  2. 就緒態(可執行,但是因為其他程序而執行而暫時停止)
  3. 阻塞態(除非某種外部事件發生,否則程序不能執行)

程序總是在這三種狀態見切換:

在這裡插入圖片描述

程序準備好後就可以運行了,但是這個時候其他程式在執行,因此進入就緒狀態,當排程選擇了這個程序時,它就可以運行了。而正在執行的程序,由於CPU給的時間用完了,但還沒執行完畢,這時就會進入就緒轉態,等待下次排程進入CPU。如果正在執行的程序由於需要I/O操作或者其他因素,需要等待一段時間才可以繼續執行,就會進入阻塞狀態,如果I/O執行完畢,就會進入就緒狀態,再次等待執行。

作業系統的最底層是排程程式,在它上面有許多程序。所有關於中斷處理、啟動程序和停止程序的所有具體細節都隱藏在排程程式中。

2.1.6 程序的實現

為了實現程序模型,作業系統維護一張表格,即程序表,每個程序佔用一個程序表項。該表項包含了程序狀態的重要資訊,包括程式計數器、堆疊指標、記憶體分配狀況、所在檔案的狀態、賬號和排程資訊。

在這裡插入圖片描述

與每個I/O類關聯的是一個稱為中斷向量的位置(靠近記憶體底部的固定區域)。它包含中斷服程式的入口地址。程序的切換都是一次中斷,所有的中斷都是從儲存暫存器開始,對於當前程序而言,通常是儲存在程序表項中, 中斷的處理和排程過程如圖:

在這裡插入圖片描述

2.1.7 多道程式的設計模型

CPU利用率 = 1 - p^n. p為一個程序等待I/O操作的時間與其在記憶體中時間的比值,n為程式的數量。

2.2 執行緒

在傳統的作業系統中,每個程序有一個地址空間和一個控制執行緒。

2.2.1 執行緒的使用

執行緒:執行緒是一種並行實體擁有共享一個地址空間和所有可用資料的能力。

多執行緒是在多程序的基礎上繼續的演化來的, 都是為了提升CPU的利用率,因為CPU是一種很寶貴的資源,因減少浪費。假設一個程序要執行三道數學題,如果沒有執行緒的概念,那這個程序只能一道一道的去完成,執行第一道產生I/O操作時,就會阻塞,知道阻塞完成了才會繼續執行。這樣效率就比較低,而如果有了多個執行緒,每個執行緒執行一道數學題,互不干擾,這個執行緒就可以一直處於執行狀態,搶到時間片的機率就大了,就可不斷執行。並且,如果當前執行程序少或者其他程序也在阻塞,那沒人用CPU,就是一種浪費,所以多執行緒可以儘量減少程序的阻塞,從而可以讓CPU一直有事可做。當然,這只是一種比喻。 還有就是,程序切換的開銷遠大於執行緒切換的開銷,畢竟,執行緒切換都是在同一個程序中。

2.2. 經典的執行緒模型

程序模型有兩種獨立的概念:資源分組處理和執行

程序擁有一個執行的執行緒,通常稱為執行緒。線上程中有一個程式計數器,用來記錄接著要執行哪條指令。執行緒擁有暫存器,用來儲存執行緒當前的工作變數。執行緒還有一個堆疊,用來記錄執行歷史,其中每個幀儲存了一個已經呼叫的但是還沒有從中返回的過程。

用一句簡單的話說:程序用於把資源集中在一起,而執行緒則是在CPU上被排程執行的實體

在這裡插入圖片描述

程序中的執行緒不同執行緒不像不同程序之間有很大的獨立性。所有執行緒有完全一樣的地址空間,共享同樣的全域性變數。

在這裡插入圖片描述

執行緒概念試圖實現的是:共享一組資源的多個執行緒的執行能力,以便這些執行緒可以為完成某一個共同任務而相同工作

和傳統程序一樣,執行緒也可以處於若干狀態中的任何一個:執行、阻塞、就緒。

  1. 正在執行的執行緒擁有CPU並且是活躍的。
  2. 被阻塞的執行緒正在等待某個釋放它的事件。
  3. 就緒的執行緒是可以被排程執行的。
  • 有兩種方式可以實現執行緒包:在使用者空間和在核心中

2.2.4 在使用者空間中實現執行緒

執行緒在一個執行時系統的上層執行,該執行時系統是一個管理執行緒的過程的集合。在使用者管理執行緒時,每個程序需要有其專用的執行緒表(TCB),用來跟蹤該程序中的執行緒。

在這裡插入圖片描述

在這裡插入圖片描述

優點

在這裡插入圖片描述

缺點

在這裡插入圖片描述

2.2.5 在核心空間中實現執行緒

此時不需要執行時系統了,另外,每個程序中也沒有執行緒表(TCB)。相反在核心中有用來記錄系統中的所有執行緒的表。當某個執行緒希望建立或者撤銷一個已有的執行緒的時候,它進行一個系統呼叫,這個系統呼叫通過對執行緒表的更新完成執行緒的建立或者撤銷工作。

核心的執行緒儲存了每個執行緒的暫存器、狀態和其他資訊。

在這裡插入圖片描述

在這裡插入圖片描述

優點

在這裡插入圖片描述

缺點:切換執行緒需要從使用者態切換到核心狀態。

2.2.6 混合實現

核心只識別核心級別的執行緒,並對其進行排程。其中一些核心級別的執行緒會被多個使用者級別的執行緒多路複用。如果同在沒有多執行緒能力作業系統中某個程序中的使用者級別執行緒一樣,可以建立、撤銷和排程這些使用者級別的執行緒。

在這裡插入圖片描述

2.3 程序間通訊

程序間通訊是很重要也很常用的的一個概念,主要圍繞三個問題:

  1. 如何把資訊傳遞給另一個程序
  2. 如何確保兩個或多個程序不會交叉(例如多個使用者在飛機訂票系統同時取買票,該給誰)
  3. 如何確保按正確的順序執行:例如B程序要列印A程序的結果,那麼肯定是先執行完A,才能執行B。

2.3.1 競爭條件

類似這樣的情況,即兩個或者多個程序讀寫某個共享資料,而最後的結果取決於程序執行的精確時序,這就稱為競爭條件。

2.3.2 臨界區

互斥就是指通過某種手段確保當一個程序在使用一個共享變數或者檔案的時候,其他程序不能做同樣的操作。

臨界區域在某些時候可能需要訪問共享記憶體或共享檔案,或執行另外一些會導致競爭的操作時,我們把對共享區域進行訪問的程式片段稱為臨界區域。

為了避免競爭條件,引入互斥的概念,設計的方案應該滿足以下四個條件:

  1. 任何兩個程序不能同時處於臨界區
  2. 與CPU的速度和數量無關
  3. 臨界區外的程序不得阻塞其他程序
  4. 程序不能無限期等待

使臨界區保持互斥的效果圖如下:

在這裡插入圖片描述

2.3.3 忙等待的互斥

1. 記憶體屏障

在單處理器系統中,最簡單的方法就是使每個程序在剛剛進入臨界區後立即遮蔽所有的中斷,並將在要離開的時候再開啟中斷。遮蔽中斷後,時鐘也會被遮蔽。這樣,在遮蔽中斷之後CPU將不會被切換到其他程序。

2. 鎖變數

設想有一個共享變數,將其初始值設定為0。當一個程序要進入其臨界區的時候,它首先測試有沒有這把鎖,如果該鎖的值為0,則將其設定為1並進入臨界區。若這把鎖的值已經為1,則該程序將等待其值變為0。於是,0就表示該臨界區沒有程序,1就表示已經有某個程序進入到臨界區。

3. 嚴格輪換法

需要用到忙等待,非常浪費CPU。導致其他程序可能被阻塞。 連續測試一個值直到某個值出現,稱為忙等待, java示例程式碼如下:

while(true) {
    while(a != 0) {
        break;
    }
}

4. Peterson解法

一個不需要嚴格輪換的軟體互斥演算法。

5. TSL指令

硬體支援的方案,需要用到TSL指令:

TSL RX, LOCK

TSL指令將 記憶體自LOCK讀到暫存器RX中,然後在該記憶體地址上存在一個非零值。讀字和寫字由作業系統保證是不可分割的(原子性),CPU將鎖住記憶體匯流排,以禁止其他CPU在本指令結束前訪問記憶體。

一個可替代TSL的指令是XCHG,本質和TSL解決辦法一致。

2.3.4 睡眠和喚醒

前面的解決方法都是採用忙等待的做法。這些做法的本質是:當一個程序想進入臨界區,先檢查是否允許進入,若不允許則原地等待,直到允許為止。 但很浪費時間。引入生產者消費者,但沒從根本解決。這種做法不僅浪費了CPU資源,而且還可能產生意想不到的結果。

2.3.5 訊號量 (Semaphore)

訊號量有兩個操作,一個是up操作一個是down操作。對一個訊號量執行down操作,則是檢查其值是否大於0,若該值大於0,則將其值減1(即用掉一個儲存的喚醒訊號)並繼續。若改值為0,則將程序睡眠,並且此時的down操作還沒有結束。將檢查值、修改值以及可能發生的睡眠操作作為一個單一的、不可分割的原子操作完成,原子性由作業系統保證。在完成前,其他程序不允許訪問訊號量。

2.3.6 互斥量(mutex)

訊號量的簡化版本,不需要計數能力,只需要兩種狀態:解鎖和加鎖。一個二進位制位就可表示它,不過通常用整型。

互斥量使用兩個過程。當一個執行緒需要訪問臨界區的時候,他呼叫mutex_lock。如果該互斥量當前是解鎖的(即臨界區是可用),此呼叫成功,呼叫執行緒可以自由進入該臨界區。另一方面,如果該互斥量是已經加鎖的,呼叫執行緒被阻塞,知道臨界區中的執行緒完成並呼叫mutex_unlock。如果多個執行緒被阻塞在互斥量上,將隨機選擇一個執行緒並允許它獲得鎖。

2.3.7 管程

使用訊號量的問題:一處很小的錯誤將會導致很大的麻煩。這就像用匯編語言一樣,甚至更糟,因為這裡出現的錯誤都是競爭條件、死鎖、以及其他一些不可預測和不可再出現的錯誤。

產生目的:為了更加易於編寫正確的程式,我們提出了一種更加高階的同步原語,稱為管程。

一個管程是由過程、變數以及資料結構等組成的一個集合,他們組成了一個特殊的模組或者軟體包。

管程是程式語言的組成部分,編譯器知道他們的特殊性。任意時刻管程中只有一個活躍程序。 管程可以看做一個軟體模組,它是將共享的變數和對於這些共享變數的操作封裝起來,形成一個具有一定介面的功能模組,程序可以呼叫管程來實現程序級別的併發控制。管程具有面向物件程式設計的特點。

額外概念,同步原語:保證同步執行的程式碼語句。大致理解為只有將同步的程式碼執行完畢,才能順序執行下一段程式碼。

2.3.8 訊息傳遞

訊息傳遞:這種程序間通訊的方法採用兩條原語:send和receive

send(destinction,& message)
receive(source,& message)

send呼叫向另一個給定的目標傳送一條訊息。receive呼叫從一個給定的源(或者是任意的源)接受一條訊息。如果沒有訊息可用,則接受者可能被阻塞。

通常在並行程式設計系統中使用訊息傳遞。如著名的訊息傳遞通訊框架:Message Passing Interface,MPI

2.3.9 屏障

通常用於程序組。在有些應用中劃分了若干的階段,並且規定,除非所有的程序都就緒準備著手進入到下一個階段,否則任何程序都不能進入到下一個階段。屏障把他們的執行劃分了不同階段,每個階段末尾設定一個屏障,只有所有程序都到達屏障,才能繼續執行下一個階段。

在這裡插入圖片描述

2.4 排程

在作業系統中,完成選擇工作的這一部分稱為排程程式,該程式使用的演算法稱為排程演算法。

2.4.1 排程演算法簡介

1. 程序行為

幾乎所有程序的I/O請求和計算都是交替突發的。

在這裡插入圖片描述

某些程序花費了絕大多數時間在計算上,而其他程序在等待I/O上花費了絕大多數時間。前者稱為計算密集性,後者稱為I/O密集型。

2. 何時排程

  1. 在建立一個新的程序後,需要決定是執行父程序還是執行子程序。
  2. 在一個程序退出的時候必須做出排程決策。
  3. 當一個程序阻塞在I/O和訊號量上或者由於其他原因阻塞的時候,必須選擇另一個程序執行。
  4. 在一個I/O中斷髮生的時候,必須做出排程決策。

根據如何處理時鐘中斷,可以把排程演算法分為兩類。非搶佔式排程演算法和搶佔式排程演算法。

  • 非搶佔式系統:排程演算法挑一個去執行,直到該程序阻塞或自動釋放CPU。
  • 搶佔式系統:雕塑演算法挑一個程序,讓其執行固定的最大時間週期,如果時間到了還在執行,則掛起等待下一次執行,然後切換下一個程序。

3. 排程演算法的分類

  1. 批處理
  2. 互動式
  3. 實時

4. 排程演算法的目標

在這裡插入圖片描述

2.4.2 批處理系統中的排程

  1. 先來先服務:按照請求CPU的順序使用CPU。
  2. 最短作業優先:誰的執行時間短,誰先執行,好處是每個作業的平均等待時間短。如兩個程序,A需要執行20分鐘,B兩分鐘,如果先執行A,則B等待20分鐘,總的等待20分鐘,平均等待10分鐘;如果先執行B,在執行A,則A等待2分鐘,總的等待2分鐘,平均等待一分鐘。
  3. 最短剩餘時間優先:也是搶佔式的,每次找到剩餘執行最短的程式執行,給其固定的執行時間,如果到期還沒執行完畢,則進入就緒佇列等待。

2.4.3 互動式系統的排程演算法

  1. 輪轉排程:也就是大家輪流來,一個程序分配固定時間片,時間到了還沒執行完,則移動到就緒佇列隊尾,下一個程序接著來。
  2. 優先順序排程:優先順序高的程序先執行,統一優先順序的則按照輪轉排程。如圖所示:

在這裡插入圖片描述

  1. 多級佇列:舉個例子,高優先順序的程序先執行一個時間片,然後是次高階佇列每個程序執行2個時間片,然後再次一級執行四個時間片。每個程序執行一次後,優先順序降低一級。
  2. 最短程序優先
  3. 保證排程
  4. 彩票排程
  5. 公平分享排程

2.4.4 實時系統中的排程

硬實時排程:在絕對截止時間前完成,軟實時排程:在某個時間前後完成排程。

2.4.5 策略與機制

將排程演算法以某種形式引數化,具體引數由使用者程序寫入。排程機制位於核心,排程策略可由使用者程序決定。

2.4.6 執行緒排程

在這裡插入圖片描述