操作系統-並發控制原理及其實現


  首先我們要明白“皮之不存,毛將焉附”的道理,計算機系統是硬件與系統軟件完美結合的一個有機整體。因此在學習這一部分時,特別是學習中斷控制原理和系統凋用等內容時,要聯系計算機組成原理的知識,這樣才能對整個系統了解。

一、程序和進程

  進程(process)這一術語 ,最初是在麻省理工學院(MIT)開發的MULTICS;系統以及IBM公司開發的CTSS/360系統中提出,時間是60年代初期。自那時起,進程已成為現代操作系統中最核心的概念之一。因為,操作系統的某本控制原理都是圍繞進程展開的。但是,進程控制的復雜性是由操作系統的並發機制引起的。 而目前經常使用的幾乎所有操作系統都支持並行或並發機制,我們先介紹並發控制,並以多道程序作為問題的切人點來引出進程的定義。

  注:這里讀者可能還不太理解進程到底是怎么回事?我們先把它記在心里,回過頭再來看遍。

1.1  並發控制

  在支持多道程序環境的通用操作系統中 ,允許一個或若十個進程在系統中並發執行, 但由於系統硬件資源的有限性, 使得並發執行的若干進程之間會出現競爭系統有限軟硬件資源的現象。 這些資源包括處理器內存I/0設備以及數據庫等, 這就需要操作系統來協調和優化分配系統共享資源。 特別是在單處理器系統中,任一時刻 CPU 只能運行一個進程, 而其他進程只能是等待 CPU 或其他資源。為了公平合理地對待所有進程, 內核為每個進程分配一小段時間一小段時間被稱為時間片。 一旦正在被運行進程的時間片用完, CPU 就立即被切換去執行另一個等待運行的就緒進程。由於 CPU 的運行速度很快, 造成若干個進程同時在運行的一 種虛擬假像。 這就是多道程序的並發控制。

1.1.1  多道程序設計與分時共享

  如上所述 ,操作系統的功能是管理系統資源,以便為用戶程序(應用程序)提供一個可執行環境。然而,對於初學者來說。操作系統卻是以一種復雜的令人難以琢磨的方式運行。 追根溯底 ,還要從計算機系統的硬件資源講起。

  在操作系統管理的硬件資源中,根據其工作速度可分為兩大類。一類是組成計算機主要功能部件的處理器CPU、高速緩存、內存以及I/O接口;而另一類則是外圍設備,又叫I/O設備。包括:鍵盤顯示器打印機鼠標軟驅硬驅光驅網絡通信設備等。 在這兩類硬件資源中, 由於構成的原材料及工作性質的不同,其運行速度有着天壤之別,處理器 CPU 、高速緩存以及內存等是由晶體半導體材料所組成;因此工作時, 其電信號的傳播速度幾乎為光速。而外圍設備大都含有機械裝置,顯然依賴按鍵、齒輪以及杠桿傳遞信息,其運行速度無法與接近光速的電子器件相比。

  多道程序設計就是將內存分成若干個部分,每—部分存放不同程序的執行代碼。當其中—個進程需要等待外設操作完成時,CPU可以保存當前進程的所有信息,選擇另—個進程來運行,但是如果多道程序中的一道是大型科學計算,在運行的數小時里不需要外設操作,那么內存中的其他若干道程序將只能等待,而擁有這些程序的用戶卻希望能及時得到響應。 這種需求導致了分時系統的出現。在分時系統中,系統為內存中的每一個進程分配一個時間片,當正在執行程序的時間片用完后,操作系統將把處理機分配給另一個就緒進程。對於單機系統(一個 CPU), 在某一時刻只能運行一個進程的一條指令,但是由於CPU的工作速度比人的反應快幾百萬倍甚至幾億倍。因此,雖然CPU在進程之間快速切換,而人的感覺卻是機器在同時執行多個程序。 這就是所謂的多道程序的分時共享,這種工作方式叫做並發機制。 當然,並發控制並非不用付出代價。當操作系統從一個進程切換到另一個進程時,也要使用CPU。因此並發機制首先要保證並發設計所帶來的效率要抵過由於進程切換所帶來的額外開銷。

  並發是指在同一時間間隔內對資源的共享。即內存中的多個進程分時共享CPU、內存以及 I/O設備。顯然,並發機制可以高效地使用CPU,協調高速CPU與慢速外設的矛盾。但是處理並發並不容易。在內存中同時駐留多個進程需要特殊的硬件以及軟件的配合對其進行保護,以免各個進程的信息被竊取並遭到攻擊。因此,現代操作系統的一個重要內容就是管理計算機的並發操作。

  操作系統中的許多迫切需要解決與研究的問題都是由並發機制而引起的。例如:圍繞若 “競爭條件”而引入了臨界區、原子操作、同步與互斥、鎖變量,等等。因此,並發控制向程序設計人員提出了新的重要的學習目標。因為並發程序並不總是按照預期的結果運行。因此調試並發程序是一件棘手的事,而且並發程序典型的不易解決的毛病是,一個並發程序編譯運行后,大多數情況下運行結果都很好,但是極少數的情況下它會莫名其妙地失敗,而且無法找到原因。

1.1.2  並發控制的硬件支持

  操作系統要實現並發控制的設計目標,離不開硬件平台的支持。因為內核必須使用中斷機制和硬件上下文切換機制,才能實現多道程序的分時共享,否則CPU無法知道某個進程的時間片已經耗盡,需要調用另一個進程運行。另外,還要考慮CPU執行進程切換的時間開銷要盡可能的小,如果CPU執行進程切換的時間開銷太大,則多道程序設計將失去意義。

  現代計算機都有一個叫做定時器和計數器的硬時鍾設備,而用於定時中斷的時間測量設備,叫做可編程間隔定時器。在IBM代機上一般使用8254CMOS芯片作為硬時鍾設備,操作系統可對其進行編程,編程后它可以按照人的意願以一定的時間間隔向CPU發出定時中斷。也就是當間隔定時器到時時,它便產生一個中斷請求,請求CPU去執行定時器中斷服務例程。該中斷服務例程將控制器交給操作系統,由操作系統確認當前進程在CPU上已運行的時間,如果分配給它的時間片已經用完,操作系統將剝奪該進程的執行權,並按照一定的算法把它放置到就緒進程隊列中,等待下一個運行的機會。

  為了實現分時共享,內核必須將時間片已經耗盡的當前進程掛起,然后從就緒隊列中選擇,一個具有較高優先權的進程投入運行這個過程稱為進程切換,或叫任務切換、上下文切換

  在多道程序環境中雖然每個進程可以擁有屬於自己的地址空間,但所有進程必須共享使用,CPU的各種寄存器。因此在恢復一個進程執行之前.內核必須將該進程在掛起時的寄存器數據再裝人CPU的各個寄存器中才能繼續運行。我們把進程恢復執行前必須裝入寄存器的一組數據稱為硬件上下文。這樣可以節省進程切換為系統帶來的額外時間開銷,當然在具體實現進程切換時如果系統指標允許,也可以使用軟件的方法進行上下文切換。Linux2.2以上版本就是如此。

1.2  進程的定義和特征

  在並發系統中,進程(process)是在由內核定義的數據結構上進行操作的一個計算活動。它是系統進行資源分配和調度的一個獨立單位。進程是動態的、有生命周期的。內核可以創建一個進程,並由調度程序“調度”而運行。在請求I/O操作時被阻塞,當它完成自己的歷史使命后,將由內核終止該進程使其消亡。

1.2.1  程序與進程

  程序這個術語是非常形象,非常具體的;它就是人們常說的“源程序”和“源代碼”。例如C語言經典程序:

1 #include <stdio.h>
2 
3 int main(void)
4 {
5    printf("Hello World!\n");      
6    return 0;  
7 }

  用匯編語言編制的程序就是完成某一特定任務的一組指令的集合。 而C語言程序是面向過程的語言 , 組成該程序的語句比匯編指令容易理解 , 因為它和人類所習慣的表達方式比較接近。 只是運算符號以及語法有所不同。但無論用什么語言編制的程序, 都是為讓計算機完成某一特定功能而編寫的文本文件。 這些文本文件是不能直接在機器上運行的, 它們必須經過系統軟件(包括編輯器和編譯器)的輸入並編譯或匯編后,轉換成二進制的可執行代碼, 才是計算機可以識別的機器語言。 此時。程序就是一個包含二進制可執行代碼文件的模塊。 當內核把二進制的可執行代碼裝入內存后, 它由三部分組成:代碼段、 數據段和堆棧段,線性地址的低地址字段是代碼段 , 存放程序經編譯后的可執行代碼(程序文本)。 

  在操作系統中,代碼段是只讀的、不能修改,所以,代碼段的長度是不會改變的。 在程序文本的上方是數據段,用來存放程序的變量 、字符串和其他數據。它分為初始化靜態數據和未初始化靜態數據 (BSS)。數據段的長度是可以改變的。程序可以修改其中的變量。許多程序在運行時需要動態分配內存。在UNIX中可以使用系統調用 malloc ()函數來獲得。在數據區上方是堆棧段。 一般情況下, 堆棧段起始於虛擬地址空間的高端,棧向下方增長。當系統啟動程序運行時,堆棧中存放有環境變量和命令行參數。程序的可執行代碼調人內存后,這三部分統稱為程序映像。 在虛擬地址空間 ,代碼段 、數據段和堆棧段的地址空間是連續的。而在物理內存中,代碼段、數據段和堆棧段的地址空間不一定是連續的。

  在內存中的程序映像運行之前,操作系統還要為其在內核的進程表項中分配一個被稱為進程控制塊的內核數據結構。同時要在該內核數據結構中加入程序運行時所需要的相應信息,其中最為重要的信息有:

• 指令指針:用以指示該進程正在執行的那條程序代碼指令的地址。
• 進程的用戶標識符。
• 進程的程序文本和數據區的存儲器位置。
• 進程的狀態。

  由此可以看出,進程映像使程序完全具備了在多道程序環境中運行的基本條件。因此,內核為程序映像分配了進程控制塊后 ,程序映像便轉化成為進程映像當調度程序調度該進程映像執行時,就是所謂的進程。進程控制塊是形成進程的最為重要的內核數據結構, 它是內核控制進程的數據結構。因為系統內核是根據進程控制塊中的信息對正在運行的程序實施控制,以便完成程序所要求的操作可以用類比的方法來說明程序與進程之間的區別:
  程序是一個項目在行動之前的計划書(或叫行動方案),而進程就是對該計划書的實施過程。 顯然,計划書和計划書的實施過程存在着很大的差別。 因為,雖然計划書是計划實施過程中一個不可缺少的組成部分,但不是全部,在計划書的實施過程中, 除了計划書這個文本文件(程序)外 。還需要人力資源和物力資源以及相應的信息資源。比方說,蓋一幢大樓。 光有人樓的設計方案(圖紙)是不行的, 還必須要有施工辦公室(或稱為指揮控制部門,相當於CPU和操作系統)、負責調配人力資源和物力資源以及保證施工方案順利進行的各種措施(信息資源)。 如果沒有人力資源 、物力資源以及運行機制去實施設計方案,那么計划書上的大樓將永遠是紙上的大樓。 但是計划書的實施過程也永遠離不開計划書。 另外,在計划書實施過程的不同階段,工程可能處於不同的狀態,例如:由於等待建築材料或者是由於缺乏勞動力而小得不停工,此時工程將處於等待狀態;當各項條件具備時再繼續施工,此時工程處於運行態,下面給出進程的抽象定義:

  定義 2.1 (1)   進程是一個多元組,P = (p, h, f, e, s) ,其中: P 代表進程; p是可執行程序代碼程序數據堆棧;h和f分別是執行程序代碼時所需要的硬件軟件資源;硬件資源包括CPU特殊功能寄存器內存磁盤等;軟件資源包括內核數據結構以及相關的所有信息資源; s代表進程運行期間的各種狀態;e是程序運行期間需要的控制信息

  (2)  進程能夠動態產生、運行,也有生命周期,能夠被內核終止。

1.2.2  從並發機制理解進程

  以上是從進程的數據結構描述了進程與程序的區別。 程序映像變成進程映像的時刻也就意味着程序將被調度執行,這樣一個新的進程使產生了。下面從並發機制來理解進程和程序的區別,在一個並發處理系統中,當代碼裝入內存之后,內核允許多個或多個進程來執行它。也就是說, 允許多個進程並發執行同一代碼段。 這就相當於,在同時啟動的全國各地的香格里拉大酒店的建築下地上,可以使用完全相同的酒店建築設計圖紙 ,這個建築設計圖紙就是可以共享的程序代碼。
  如果多個進程並發執行同一代碼段,那么要允許各個進程按照其各自的情況在任何時間開始執行和結束執行。因此,在同一時刻, 毋個進程處在同一代碼段的不同機器指令處執行(相當於幾個建築工地的進度不同)。為了使執行同一代碼的多個進程之間的數據和進度相互獨立,必須為每個進程設置一個指令指針,指示該進程下一步將執行哪條指令。同時,每個進程要有自己的數據段和堆棧段用來保存程序運行過程中用到的變量值。

  下面我們分析一個簡短的循環打印的C語言程序,就可以進一步得明白程序與進程的差別:

1 int i;
2 for(i = 5; i > 0; i--)
3 {
4   printf("Value of i is %d:\n",i);
5 }

  這是C語言的for循環語句,其中變量 i 的取值根據循環次數的不同而不同。假設有一個進程執行這個代碼段的第三次循環(i= 3)時,另有一個進程開始執行這段代碼的第一次循環 (i = 1),雖然兩個進程執行的是同一段代碼,但是它們的變量值卻不相同。因此內核要為每個進程分配數據段和堆棧段,保留自己的變量值。否則就會產生矛盾,系統將不能正常工作。
  由此可以看到,程序代碼一旦被加載到內存,內核可以允許一個或多個進程來執行它,因此進程只代表一個計算活動的執行過程。而不是代碼本身,這就是程序與進程之間的區別。

1.2.2   進程的特征

  進程的基本特征是動態性和並發性。同時進程還具有順序性、獨立性和異步性等特征。
  在進程的定義中,已強調了進程的動態性。即進程是有生命期的,具體表現在:它是由 “創建”而產生,因“調度程序” 調度而運行,由於“I/O操作”而阻塞,最終,因“退出”而 消亡。

  進程的並發性前面已經描述,這里不再贅述。

  進程的順序性是指在單個順序處理機上,機器指令的執行以及硬件操作是嚴格按照程序指令的順序進行的。例如,i386處理器工作時,必須等—條指令執行結束后,硬件才檢查是否有硬中斷誚求;如果沒有請求,再接着執行下一條指令;而如果有請求,CPU允許中斷,並且該中斷請求排隊在前面(優先級高),CPU將響應這個中斷請求,從而轉去執行中斷服務例程。 也就是說,進程的順序性是指,系統中正在運行的進程不管是發生同步事件還是異步事件,只有當其中的一個操作結束后,才開始其后續的操作。

  進程的獨立性是指每一個進程是系統中一個獨立的實體,它有自己的程序計數器和內部狀態。因此進程是系統進行資源分配和調度的獨立單位。進程的異步性是指系統中的進程是按照各自獨立的、不可確定的時間發生的。

  在講解線程前,我們先深入理解下進程的內存分配機制是怎樣的。

1.2.3  進程地址空間和虛擬地址空間

  (1)、早期的內存分配機制

  在早期的計算機中,要運行一個程序,會把這些程序全都裝入內存,程序都是直接運行在內存上的,也就是說程序中訪問的內存地址都是實際的物理內存地址。當計算機同時運行多個程序時,必須保證這些程序用到的內存總量要小於計算機實際物理內存的大小。那當程序同時運行多個程序時,操作系統是如何為這些程序分配內存 的呢?下面通過實例來說明當時的內存分配方法:

  某台計算機總的內存大小是 128M ,現在同時運行兩個程序 A 和 B , A 需占用內存 10M , B 需占用內存 110 。計算機在給程序分配內存時會采取這樣的方法:先將內存中的前 10M 分配給程序 A ,接着再從內存中剩余的 118M 中划分出 110M 分配給程序 B 。這種分配方法可以保證程序 A 和程序 B 都能運行,但是這種簡單的內存分配策略問題很多。
  

問題 1 :進程地址空間不隔離。由於程序都是直接訪問物理內存,所以惡意程序可以隨意修改別的進程的內存數據,以達到破壞的目的。有些非惡意的,但是有 bug 的程序也可能不小心修改了其它程序的內存數據,就會導致其它程序的運行出現異常。這種情況對用戶來說是無法容忍的,因為用戶希望使用計算機的時候,其中一個任務失敗了,至少不能影響其它的任務。

問題 2 :內存使用效率低。在 A 和 B 都運行的情況下,如果用戶又運行了程序 C ,而程序 C 需要 20M 大小的內存才能運行,而此時系統只剩下 8M 的空間可供使用,所以此時系統必須在已運行的程序中選擇一個將該程序的數據暫時拷貝到硬盤上,釋放出部分空間來供程序 C 使用,然后再將程序 C 的數據全部裝入內存中運行。可以想象得到,在這個過程中,有大量的數據在裝入裝出,導致效率十分低下。

問題 3 :程序運行的地址不確定。當內存中的剩余空間可以滿足程序 C 的要求后,操作系統會在剩余空間中隨機分配一段連續的 20M 大小的空間給程序 C 使用,因為是隨機分配的,所以程序運行的地址是不確定的。  

  (2)、分段

  為了解決上述問題,人們想到了一種變通的方法,就是增加一個中間層,利用一種間接的地址訪問方法訪問物理內存。按照這種方法,程序中訪問的內存地址不再是實 際的物理內存地址,而是一個虛擬地址,然后由操作系統將這個虛擬地址映射到適當的物理內存地址上。這樣,只要操作系統處理好虛擬地址到物理內存地址的映 射,就可以保證不同的程序最終訪問的內存地址位於不同的區域,彼此沒有重疊,就可以達到內存地址空間隔離的效果。

  當創建一個進程時,操作系統會為該進程分配一個 4GB 大小的虛擬 進程地址空間。之所以是 4GB ,是因為在 32 位的操作系統中,一個指針長度是 4 字節,而 4 字節指針的尋址能力是從 0x00000000~0xFFFFFFFF ,最大值 0xFFFFFFFF 表示的即為 4GB 大小的容量。與虛擬地址空間相對的,還有一個物理地址空間,這個地址空間對應的是真實的物理內存。如果你的計算機上安裝了 512M 大小的內存,那么這個物理地址空間表示的范圍是 0x00000000~0x1FFFFFFF 。當操作系統做虛擬地址到物理地址映射時,只能映射到這一范圍,操作系統也只會映射到這一范圍。當進程創建時,每個進程都會有一個自己的 4GB 虛擬地址空間。要注意的是這個 4GB 的地址空間是“虛擬”的,並不是真實存在的,而且每個進程只能訪問自己虛擬地址空間中的數據,無法訪問別的進程中的數據,通過這種方法實現了進程間的地址隔離。那是不是這 4GB 的虛擬地址空間應用程序可以隨意使用呢?很遺憾,在 Windows 系統下,這個虛擬地址空間被分成了 4 部分: NULL 指針區、用戶區、 64KB 禁入區、內核區。應用程序能使用的只是用戶區而已,大約 2GB 左右 ( 最大可以調整到 3GB) 。內核區為 2GB ,內核區保存的是系統線程調度、內存管理、設備驅動等數據,這部分數據供所有的進程共享,但應用程序是不能直接訪問的。

  人們之所以要創建一個虛擬地址空間,目的是為了解決進程地址空間隔離的問題。但程序要想執行,必須運行在真實的內存上,所以,必須在虛擬地址與物理地址間建 立一種映射關系。這樣,通過映射機制,當程序訪問虛擬地址空間上的某個地址值時,就相當於訪問了物理地址空間中的另一個值。人們想到了一種分段 (Sagmentation) 的方法,它的思想是在虛擬地址空間和物理地址空間之間做一一映射。比如說虛擬地址空間中某個 10M 大小的空間映射到物理地址空間中某個 10M 大小的空間。這種思想理解起來並不難,操作系統保證不同進程的地址空間被映射到物理地址空間中不同的區域上,這樣每個進程最終訪問到的物理地址空間都是彼此分開的。通過這種方式,就實現了進程間的地址隔離。

  還是以實例說明,假設有兩個進程 A 和 B ,進程 A 所需內存大小為 10M ,其虛擬地址空間分布在 0x00000000 到 0x00A00000 ,進程 B 所需內存為 100M ,其虛擬地址空間分布為 0x00000000 到 0x06400000 。那么按照分段的映射方法,進程 A 在物理內存上映射區域為 0x00100000 到 0x00B00000 ,進程 B 在物理內存上映射區域為 0x00C00000 到 0x07000000 。於是進程 A 和進程 B 分別被映射到了不同的內存區間,彼此互不重疊,實現了地址隔離。從應用程序的角度看來,進程 A 的地址空間就是分布在 0x00000000 到 0x00A00000 ,在做開發時,開發人員只需訪問這段區間上的地址即可。應用程序並不關心進程 A 究竟被映射到物理內存的那塊區域上了,所以程序的運行地址也就是相當於說是確定的了。 圖二顯示的是分段方式的內存映射方法。

  這種分段的映射方法雖然解決了上述中的問題一和問題三,但並沒能解決問題二,即內存的使用效率問題。在分段的映射方法中,每次換入換出內存的都是整個程序, 這樣會造成大量的磁盤訪問操作,導致效率低下。所以這種映射方法還是稍顯粗糙,粒度比較大。實際上,程序的運行有局部性特點,在某個時間段內,程序只是訪 問程序的一小部分數據,也就是說,程序的大部分數據在一個時間段內都不會被用到。基於這種情況,人們想到了粒度更小的內存分割和映射方法,這種方法就是分頁 (Paging) 。  
  (3)、分頁

  分頁的基本方法是,將地址空間分成許多的頁。每頁的大小由 CPU 決定,然后由操作系統選擇頁的大小。目前 Inter 系列的 CPU 支持 4KB 或 4MB 的頁大小,而 PC 上目前都選擇使用 4KB 。按這種選擇, 4GB 虛擬地址空間共可以分成 1048576 個頁, 512M 的物理內存可以分為 131072 個頁。顯然虛擬空間的頁數要比物理空間的頁數多得多。

  在分段的方法中,每次程序運行時總是把程序全部裝入內存,而分頁的方法則有所不同。分頁的思想是程序運行時用到哪頁就為哪頁分配內存,沒用到的頁暫時保留在 硬盤上。當用到這些頁時再在物理地址空間中為這些頁分配內存,然后建立虛擬地址空間中的頁和剛分配的物理內存頁間的映射。

  下面通過介紹一個可執行文件的裝載過程來說明分頁機制的實現方法。一個可執行文件 (PE 文件 ) 其實就是一些編譯鏈接好的數據和指令的集合,它也會被分成很多頁,在 PE 文件執行的過程中,它往內存中裝載的單位就是頁。當一個 PE 文件被執行時,操作系統會先為該程序創建一個 4GB 的進程虛擬地址空間。前面介紹過,虛擬地址空間只是一個中間層而已,它的功能是利用一種映射機制將虛擬地址空間映射到物理地址空間,所以,創建 4GB 虛擬地址空間其實並不是要真的創建空間,只是要創建那種映射機制所需要的數據結構而已,這種數據結構就是頁目和頁表。

  當創建完虛擬地址空間所需要的數據結構后,進程開始讀取 PE 文件的第一頁。在 PE 文件的第一頁包含了 PE 文件頭和段表等信息,進程根據文件頭和段表等信息,將 PE 文件中所有的段一一映射到虛擬地址空間中相應的頁 (PE 文件中的段的長度都是頁長的整數倍 ) 。這時 PE 文件的真正指令和數據還沒有被裝入內存中,操作系統只是根據 PE 文件的頭部等信息建立了 PE 文件和進程虛擬地址空間中頁的映射關系而已。當 CPU 要訪問程序中用到的某個虛擬地址時,當 CPU 發現該地址並沒有相相關聯的物理地址時, CPU 認為該虛擬地址所在的頁面是個空頁面, CPU 會認為這是個頁錯誤 (Page Fault) , CPU 也就知道了操作系統還未給該 PE 頁面分配內存, CPU 會將控制權交還給操作系統。操作系統於是為該 PE 頁面在物理空間中分配一個頁面,然后再將這個物理頁面與虛擬空間中的虛擬頁面映射起來,然后將控制權再還給進程,進程從剛才發生頁錯誤的位置重新開始執行。由於此時已為 PE 文件的那個頁面分配了內存,所以就不會發生頁錯誤了。隨着程序的執行,頁錯誤會不斷地產生,操作系統也會為進程分配相應的物理頁面來滿足進程執行的需求。

  分頁方法的核心思想就是當可執行文件執行到第 x 頁時,就為第 x 頁分配一個內存頁 y ,然后再將這個內存頁添加到進程虛擬地址空間的映射表中,這個映射表就相當於一個 y=f(x) 函數。應用程序通過這個映射表就可以訪問到 x 頁關聯的 y 頁了。

  32 位的CPU 的尋址空間是4G,所以虛擬內存的最大值為4G ,而windows 操作系統把這4G 分成2 部分,即2G 的用戶空間和2G 的系統空間,系統空間是各個進程所共享的, 他存放的是操作系統及一些內核對象等, 而用戶空間是分配給各個進程使用的,用戶空間包括用: 程序代碼和數據,堆,共享庫,棧。

通過上面的講解,我們了解了並發機制的實現,了解了程序與進程的區別,了解了進程空間的內存分配機制,了解了進程的特征,下面我們看看線程是如何的呢?

二、線程

  實際上,一個進程是由一個PCB數據結構和一個可執行代碼的指令序列所組成。如果單純從CPU執行程序所必須的硬件上下文去考慮,PCB中的絕大部分成員與CPU執行程序代碼是沒有關系的。因為CPU執行程序所必須的、並且對程序員可見的硬件資源只包含:程序計數器執行堆棧通用寄存器組狀態標志。CPU執行程序是根據程序計數器中的地址來取下一條指令代碼,因此在程序代碼運行期間,CPU的執行流程由“經過“程序計數器的指令地址序列來描述,這個地址序列就是指令的執行軌跡,叫做執行線程,簡稱線程。它是進程的控制流程。

  雖然線程的概念出現得比較晚,但是客觀上它早已存在,只是后來隨着並行編程的研發和進步才逐漸明確起來,傳統的用戶進程中只有一個執行流程,所以傳統進程都是單線程的

  有了線程的概念以后,進程模型得到了有效的擴展,因為在一個進程中完全可以設置多個執行流程,對應多個執行線程。即在—個進程中可以同時運行多個不同的線程來執行不同的任務, 例如: 瀏覽器就是一個很好的多線程的例子, 使用瀏覽器你即可以在下載圖片的同時滾動頁面;在訪問新頁面時、播放動畫和聲音、 打印文件等。

2.1  多線程

  在一個進程中允許有多個線程時 , 它們都共享該進程的狀態和資源, 也就是說它們駐留在同一個用戶地址空間中 、可以訪問相同的數據。 當一個線程改變了所屬進程的變量時,其他線程在下次訪問該變量時就會看到這種改變。

  由千每個線程具有自己的執行堆棧 、 程序計數器 、 寄存器集和狀態標志, 所以同樣要為每 個線程定義一個抽象數據類型來包含這些數據成員, 以實現線程之間的切換。 由於同一進程的多個線程共享同一地址空間, 因此引入多線程后為系統管理帶來了許多好處:

• 線程之間的切換減輕了內存管理的負擔, 另外線程之間的切換所花時間也比進程間切換少。
• 系統創建或終止一個線程的開銷要比創建或終止—個進程的開銷少得多。
• 線程之間通信的效率要高於進程之間的通信效率。 進程間通信需要內核介入, 而同一進程中的多線程由於共享同一地址空間, 所以通信時無須內核介入。

  但是, 引入線程后也需要解決一些問題:

• 需要增加CPU開銷, 以便跟蹤線程。
• 線程之間也存在爭用共享資源的問題。
  所以,當編制程序時,如果要求完成一組相關任務的應用程序或函數,應該首選使用多線程的組織方式。

  由於傳統的原因,線程可分為內核級線程和用戶級線程,也簡稱為內核線程和用戶線程。

2.2  內核線程和用戶線程

  內核中的線程是一個調度的實體。 它的創建與撤銷和用戶進程沒有聯系, 而是根據核心的內部管理需求來確定的,例如為了執行一個指定的函數,它們沒有虛擬地址空間,可共享內核正文段和全局數據,並有自己的內核堆棧,它可以被單獨地調度執行,也能使用內核同步機制。

  Linux操作系統支持內核線程, 它的頁交換進程kswapd就是一個內核線程。
  用戶線程運行在操作系統核心之上, 但進程中的線程對內核是透明的, 或者說內核無須知道它們的存在。 這些線程將競爭分配給進程的資源。 當內核不支持用戶級線程時, 可以通過使用 POSIX. 1C 提供的標准線程庫來實現用戶級線程, 其線程包括線程的創建、 刪除、 互斥和條件變量的同步橾作以及調度和管理線程的標准函數,而無須內核的幫助。

  在Linux中,當使用系統調用Clonde()時,它創建的新進程與被調用者共享同一個用 戶地址空間,從原理講,這個新創建的進程是調用者進程中的一個線程。但是Linux內核沒有專門定義供線程使用的數據結構,所以它的線程和進程在結構上沒有任何區別。

  參考文獻:

  操作系統原理技術與編程-蔣靜(04)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM