【操作系統】如何實現互斥?

共享數據的併發進程能夠正確和高效地進行協作。對於一個好的解決方案,需要滿足以下4個條件:

  1. 任何兩個進程不能同時處於其臨界區。

  2. 不應對CPU的速度和數量做任何假設。

  3. 臨界區外運行的進程不得阻塞其他進程。

  4. 不得使進程無限期等待進入臨界區。

屏蔽中斷

在單處理器系統中,最簡單的方法是使每個進程在剛剛進入臨界區後立即屏蔽所有中斷,並在就要離開之前再打開中斷。屏蔽中斷後,時鐘中斷也被屏蔽。CPU只有發生時鐘中斷或其他中斷時纔會進行進程切換,這樣,在屏蔽中斷之後CPU將不會被切換到其他進程。於是,一旦某個進程屏蔽中斷之後,它就可以檢查和修改共享內存,而不必擔心其他進程介入。

這個方案並不好,因爲把屏蔽中斷的權力交給用戶進程是不明智的。設想一下,若一個進程屏蔽中斷後不再打開中斷,其結果將會如何?整個系統可能會因此終止。而且,如果系統是多處理器(有兩個或可能更多的處理器),則屏蔽中斷僅僅對執行disable指令的那個CPU有效。其他CPU仍將繼續運行,並可以訪問共享內存。

另一方面,對內核來說,當它在更新變量或列表的幾條指令期間將中斷屏蔽是很方便的。當就緒進程隊列之類的數據狀態不一致時發生中斷,則將導致競爭條件。所以結論是:屏蔽中斷對於操作系統本身而言是一項很有用的技術,但對於用戶進程則不是一種合適的通用互斥機制。

由於多核芯片的數量越來越多,即使在低端PC上也是如此。因此,通過屏蔽中斷來達到互斥的可能性——甚至在內核中——變得日益減少了。雙核現在已經相當普遍,四核當前在高端機器中存在,而且我們離八或十六(核)也不久遠了。在一個多核系統中(例如,多處理器系統),屏蔽一個CPU的中斷不會阻止其他CPU干預第一個CPU所做的操作。結果是人們需要更加複雜的計劃。

鎖變量

作爲第二種嘗試,可以尋找一種軟件解決方案。設想有一個共享(鎖)變量,其初始值爲0。當一個進程想進入其臨界區時,它首先測試這把鎖。如果該鎖的值爲0,則該進程將其設置爲1並進入臨界區。若這把鎖的值已經爲1,則該進程將等待直到其值變爲0。於是,0就表示臨界區內沒有進程,1表示已經有某個進程進入臨界區。

但是,這種想法也包含了與假脫機目錄一樣的疏漏。假設一個進程讀出鎖變量的值並發現它爲0,而恰好在它將其值設置爲1之前,另一個進程被調度運行,將該鎖變量設置爲1。當第一個進程再次能運行時,它同樣也將該鎖設置爲1,則此時同時有兩個進程進入臨界區中。

可能讀者會想,先讀出鎖變量,緊接着在改變其值之前再檢查一遍它的值,這樣便可以解決問題。但這實際上無濟於事,如果第二個進程恰好在第一個進程完成第二次檢查之後修改了鎖變量的值,則同樣還會發生競爭條件。

嚴格輪換法

第三種互斥的方法如圖所示。幾乎與本書中所有其他程序一樣,這裏的程序段用C語言編寫。之所以選擇C語言是由於實際的操作系統普遍用C語言編寫(或偶爾用C++),而基本上不用像Java、Modula3或Pascal這樣的語言。對於編寫操作系統而言,C語言是強大、有效、可預知和有特性的語言。而對於Java,它就不是可預知的,因爲它在關鍵時刻會用完存儲器,而在不合適的時候會調用垃圾收集程序回收內存。在C語言中,這種情形就不可能發生,因爲C語言中不需要進行空間回收。

在圖中,整型變量turn,初始值爲0,用於記錄輪到哪個進程進入臨界區,並檢查或更新共享內存。開始時,進程0檢查turn,發現其值爲0,於是進入臨界區。進程1也發現其值爲0,所以在一個等待循環中不停地測試turn,看其值何時變爲1。連續測試一個變量直到某個值出現爲止,稱爲忙等待(busy waiting)。由於這種方式浪費CPU時間,所以通常應該避免。

在這裏插入圖片描述
只有在有理由認爲等待時間是非常短的情形下,才使用忙等待。用於忙等待的鎖,稱爲自旋鎖(spin lock)。

進程0離開臨界區時,它將turn的值設置爲1,以便允許進程1進入其臨界區。假設進程1很快便離開了臨界區,則此時兩個進程都處於臨界區之外,turn的值又被設置爲0。現在進程0很快就執行完其整個循環,它退出臨界區,並將turn的值設置爲1。此時,turn的值爲1,兩個進程都在其臨界區外執行。

突然,進程0結束了非臨界區的操作並且返回到循環的開始。但是,這時它不能進入臨界區,因爲turn的當前值爲1,而此時進程1還在忙於非臨界區的操作,進程0只有繼續while循環,直到進程1把turn的值改爲0。這說明,在一個進程比另一個慢了很多的情況下,輪流進入臨界區並不是一個好辦法。

這種情況違反了前面敘述的條件3:進程0被一個臨界區之外的進程阻塞。再回到前面假脫機目錄的問題,如果我們現在將臨界區與讀寫假脫機目錄相聯繫,則進程0有可能因爲進程1在做其他事情而被禁止打印另一個文件。

實際上,該方案要求兩個進程嚴格地輪流進入它們的臨界區,如假脫機文件等。任何一個進程都不可能在一輪中打印兩個文件。儘管該算法的確避免了所有的競爭條件,但由於它違反了條件3,所以不能作爲一個很好的備選方案。

Peterson解法

在這裏插入圖片描述
在使用共享變量(即進入其臨界區)之前,各個進程使用其進程號0或1作爲參數來調用enter_region。該調用在需要時將使進程等待,直到能安全地進入臨界區。在完成對共享變量的操作之後,進程將調用leave_region,表示操作已完成,若其他的進程希望進入臨界區,則現在就可以進入。

現在來看看這個方案是如何工作的。一開始,沒有任何進程處於臨界區中,現在進程0調用enter_region。它通過設置其數組元素和將turn置爲0來標識它希望進入臨界區。由於進程1並不想進入臨界區,所以enter_region很快便返回。如果進程1現在調用enter_region,進程1將在此處掛起直到interested[0]變成FALSE,該事件只有在進程0調用leave_region退出臨界區時纔會發生。

現在考慮兩個進程幾乎同時調用enter_region的情況。它們都將自己的進程號存入turn,但只有後被保存進去的進程號纔有效,前一個因被重寫而丟失。假設進程1是後存入的,則turn爲1。當兩個進程都運行到while語句時,進程0將循環0次並進入臨界區,而進程1則將不停地循環且不能進入臨界區,直到進程0退出臨界區爲止。

mark:注意有turn,就算兩個都爲true也不會死循環。

TSL指令

現在來看需要硬件支持的一種方案。某些計算機中,特別是那些設計爲多處理器的計算機,都有下面一條指令:

TSL RX,LOCK

稱爲測試並加鎖(Test and Set Lock),它將一個內存字lock讀到寄存器RX中,然後在該內存地址上存一個非零值。讀字和寫字操作保證是不可分割的,即該指令結束之前其他處理器均不允許訪問該內存字。執行TSL指令的CPU將鎖住內存總線,以禁止其他CPU在本指令結束之前訪問內存

着重說明一下,鎖住存儲總線不同於屏蔽中斷。屏蔽中斷,然後在讀內存字之後跟着寫操作並不能阻止總線上的第二個處理器在讀操作和寫操作之間訪問該內存字。事實上,在處理器1上屏蔽中斷對處理器2根本沒有任何影響。讓處理器2遠離內存直到處理器1完成的惟一方法就是鎖住總線,這需要一個特殊的硬件設施(基本上,一根總線就可以確保總線由鎖住它的處理器使用,而其他的處理器不能用)。

爲了使用TSL指令,要使用一個共享變量lock來協調對共享內存的訪問。當lock爲0時,任何進程都可以使用TSL指令將其設置爲1,並讀寫共享內存。當操作結束時,進程用一條普通的move指令將lock的值重新設置爲0。

這條指令如何防止兩個進程同時進入臨界區呢?解決方案如圖所示。假定(但很典型)存在如下共4條指令的彙編語言子程序。第一條指令將lock原來的值複製到寄存器中並將lock設置爲1,隨後這個原來的值與0相比較。如果它非零,則說明以前已被加鎖,則程序將回到開始並再次測試。經過或長或短的一段時間後,該值將變爲0(當前處於臨界區中的進程退出臨界區時),於是過程返回,此時已加鎖。要清除這個鎖非常簡單,程序只需將0存入lock即可,不需要特殊的同步指令。

在這裏插入圖片描述
現在有一種很明確的解法了。進程在進入臨界區之前先調用enter_region,這將導致忙等待,直到鎖空閒爲止,隨後它獲得該鎖並返回。在進程從臨界區返回時它調用leave_region,這將把lock設置爲0。與基於臨界區問題的所有解法一樣,進程必須在正確的時間調用enter_region和leave_region,解法才能奏效。如果一個進程有欺詐行爲,則互斥將會失敗。

一個可替代TSL的指令是XCHG,它原子性地交換了兩個位置的內容,例如,一個寄存器與一個存儲器字。代碼如圖2-26所示,而且就像可以看到的那樣,它本質上與TSL的解決辦法一樣。所有的Intel x86 CPU在低層同步中使用XCHG指令。

在這裏插入圖片描述

總結

Peterson解法和TSL或XCHG解法都是正確的,但它們都有忙等待的缺點。這些解法在本質上是這樣的:當一個進程想進入臨界區時,先檢查是否允許進入,若不允許,則該進程將原地等待,直到允許爲止。

這種方法不僅浪費了CPU時間,而且還可能引起預想不到的結果。考慮一臺計算機有兩個進程,H優先級較高,L優先級較低。調度規則規定,只要H處於就緒態它就可以運行。在某一時刻,L處於臨界區中,此時H變到就緒態,準備運行(例如,一條I/O操作結束)。現在H開始忙等待,但由於當H就緒時L不會被調度,也就無法離開臨界區,所以H將永遠忙等待下去。這種情況有時被稱作優先級反轉問題(priority inversion problem)。

參考

Andrews Tanenbaum. 現代操作系統(原書第3版) (計算機科學叢書)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章