1. 程式人生 > >Linux之程序與同步

Linux之程序與同步

程序/核心模式

CPU既可以執行在使用者態又可以執行在核心態。當一個程式執行在使用者態時,它不能直接訪問核心資料結構以及底層硬體互動。當CPU處於核心態時則不再有限制。每種CPU模型都為從使用者態到核心態提供了特殊的指令。

程序是動態的實體,在系統內通常只有有限的生存期。建立,撤銷及同步現有程序的任務都委託給核心中的一組例程來完成。

核心是程序的管理者。程序/核心模式假定:請求核心服務的程序使用系統呼叫的特殊程式設計機制。每個系統呼叫都設定了一組識別程序請求的引數,然後執行與硬體相關的CPU指令完成從使用者態到核心態的轉換。

程序實現

為了方便讓核心管理程序。每個程序由一個程序描述符表示,這個描述符包含有關程序當前狀態的相關資訊。

當核心暫停一個程序的執行時,就把相干暫存器的資料儲存在程序描述符中。

  • PC=程式計數器
  • SP=棧指標暫存器
  • 通用暫存器
  • PSW=狀態暫存器
  • 浮點暫存器
  • 用來跟蹤程序對RAM訪問的記憶體管理暫存器

可重入核心

所有的Unix的核心都是可重入的,這意味著若干個程序可以同時在核心態下執行。提供可重入的一種方式是編寫函式,以便這些函式只能修改區域性變數,而不能改變全域性的資料結構,這樣的函式叫可重入函式。但是可重入核心不僅僅侷限於可重入函式,可重入核心可以包含非可重入函式,並且利用鎖機制保證一次只有一個程序執行一個非重入函式。

核心控制路徑:表示核心處理系統呼叫,異常或中斷所執行的指令序列。在最簡單的情況下,CPU從第一條指令到最後一條指令順序地執行核心控制路徑。當特定事件發生時,CPU交錯執行核心控制路徑。

程序地址空間

每個程序都有獨立的地址空間。在使用者態下執行的程序涉及到私有棧,資料區,和程式碼區。但在核心態下執行時,程序訪問核心的資料區和程式碼區,但使用另外的私有棧。程序間有時也能共享地址空間,以實現程序間的通訊,這叫做“共享記憶體”技術。同時,Linux支援mmap()系統呼叫,該系統呼叫允許存放在塊裝置上的檔案或資訊一部分對映到程序的地址空間。如果同一檔案有幾個程序共享,那麼共享它的程序都包含有記憶體對映。

因為核心是可重入的,因此幾個核心控制路徑輪流執行。在這種情況下,每個核心控制路徑都有自己的私有核心棧。

同步與臨界區

實現可重入核心需要利用同步機制:如果某個核心控制路徑對某個核心資料結構操作時被掛起,則其他的核心控制路徑,不被允許操作此核心資料結構。除非被設定為一致性狀態。當某個計算結果取決於多個程序時,這段程式碼就是存在風險的。一般來說,對全域性變數的訪問通過原子操作來保證。如果兩個或以上程序修改某個變數的過程是單獨的,不可中斷的操作,那麼就不會出現資料錯誤。臨界區是這樣一段程式碼,進入這段程式碼的程序必須完成,之後的程序才能進入。

禁止中斷

單處理器系統的另一種實現同步機制的方式就是禁止中斷。在進入臨界區之前禁止所有硬體中斷,程序完成後再開啟硬體中斷。

訊號量

廣泛使用的一種同步機制是訊號量。訊號量僅僅是一個與資料結構相關的計數器。所有核心執行緒在訪問這個資料結構前都要檢查這個訊號量。

訊號量組成:

  • 一個整數變數
  • 一個等待程序的連結串列
  • 兩個原子方法 up() 和 down()

down方法對訊號量-1,如果訊號量<0,則將正在執行的程序加入到這個等待程序的連結串列中,然後阻塞該程序。up方法對訊號量+1,如果訊號量>0,則在等待程序的連結串列中啟用一個或多個程序。每個要保護的資料結構要有一個訊號量。訊號量預設為1,當核心控制路徑希望訪問這個資料結構時,執行down方法,如果訊號量非負,則允許訪問。當另一個程序在訊號量上執行up時,訊號量增加,啟用連結串列中的程序。

自旋鎖

在多處理器系統中,訊號線並不是解決同步機制的最佳方案。系統不允許在不同CPU上執行的核心控制路徑同時訪問某些核心資料結構。因此,使用自旋鎖來解決。自旋鎖和訊號量相似,但沒有程序連結串列。當一個程序發現鎖被另一個程序鎖著時,就會不停地“旋轉”,執行緊湊的迴圈指令來等待。

自旋鎖在單處理器系統中是無效的,比如當一個核心控制路徑試圖訪問一個被鎖住的資料結構,此時開始迴圈,此時試圖修改資料結構的核心控制路徑被中止,這樣就陷入了死迴圈。最終系統只能掛起。

避免死鎖

與其他核心控制路徑同步的程序或路徑很容易陷入了死鎖的狀態。如,程序a獲得資料結構b的訪問許可權,程序c獲得數劇結構d的訪問許可權,a在等待d,c在等待b,這就陷入了死鎖。

訊號與程序間通訊

Unix訊號提供了把系統事件報告給程序的一種機制。每個程序都有自己的訊號編號,通常用一個符號常量來表示,例如SIGTERM.

兩種系統事件:

  • 非同步通告=SIGINT
  • 同步錯誤或異常,如程序訪問到錯誤記憶體的非法地址,核心向這個程序傳送SIGSEGV訊號。

程序管理

Unix在程序和它正在執行的程式之間做出一個清晰的劃分。fork()和exit()系統呼叫分別用來建立一個新程序和終止一個程序。而呼叫exec()類系統呼叫則是裝入一個新程式。呼叫fork()的是父程序,而新建立的程序為子程序。父子程序能互相找到對方,因為描述每個程序的資料結構中包含兩個指標,一個指向它的父程序,另一個指向它的子程序。