1.計算機硬件簡單介紹
1.1 基本硬件組成
處理器
一般是CPU,計算機用來完成運算的主要組成,其中一般還包含多級緩存和支持的指令集.存儲器
RAM,運行中的程序計數器(指向下一條CPU指令的指針)及其堆棧和變量等一切相關信息會放在緩存中,方便CPU調取,速度比起CPU的內部緩存會慢,又快于磁盤等大容量存儲設備.磁盤
計算機用于存儲數據的設備,速度很慢,但是掉電后非易失,存儲容量大,廉價等特性讓其成為現代計算機系統不可缺少的設備.I/O設備
輸入輸出設備.總線
用于將上述的所有設備鏈接起來的抽象概念,現在計算機系統一般會有多種總線,用橋(bridge)再連接各級總線.
1.2 硬件間的通訊
以一個最普通的作業為例,假設它的運行包括如下步驟
1. 運算
2. 等待I/O設備輸入
3. 從磁盤讀取數據
4. 運算
5. 結束
- 假設在沒有操作系統,在最簡單的情況下.CPU會通過一個循環忙等待將會從步驟2中輸入的數據,然后通過總線通知磁盤讀取數據,然后又會以相同的方法等待步驟3返回的數據.
細心的你可能會看到這個沒有操作系統的計算機存在巨大的資源浪費.
-
在裝有操作系統的計算機上,在步驟2和步驟3中,CPU不會進入一個忙等待過程,而是會將當前作業的程序計數器,變量,堆棧等信息存儲在內存中的一個特殊表中,然后調取下一個作業開始執行.
當I/O設備完成輸入,或者磁盤完成讀取操作后,這些設備會通過總線,向中斷控制器發出中斷信號,再由中斷控制器通知CPU.
CPU決定是將當前任務暫存(掛起),代而執行之前掛起的作業,還是等待合適的時候再執行先前程序,取決于操作系統調度算法,總之,這樣就通過操作系統完成了比較高效的作業執行.
上述模型在交互較少的情況下有著良好的效率,但是在交互式系統中,多個用戶可能希望多個作業在感官上同時執行,而不是等待上一個作業執行完畢或等待I/O或讀寫磁盤時再執行下一個作業.這時就要求操作系統必須實現作業的并行,起碼是偽并行,因為真正的并行在單CPU計算機上是不可能的.
2.單CPU計算機實現偽并行
2.1 為什么需要并行
現代計算機系統中,在一般情況下,同一個作業中,CPU的運算時間遠遠低于I/O或讀寫磁盤存儲器這類耗時操作所需要的時間.
在沒有并行的時候,CPU在完成運算后就需要等待I/O,這是個巨大的資源浪費-
在交互式系統中,一般要求多個用戶的進程在感官上同時執行,或者單個用戶的多個進程間也要求感官上的同時運行,如果做不到這一點,該系統的用戶體驗也是極其惡劣的.
所以,尤其在交互式系統中,(偽)并行將是一個合格的操作系統不得不考慮的問題.
2.2 操作系統的作用
個人認為操作系統有以下職責:
- 封裝硬件,提供簡單接口
- 提供諸如進程,線程,地址空間,文件等的抽象
2.3 操作系統如何實現偽并行
首先需要了解什么是進程,什么是線程
1. 進程:是操作系統對一個正在運行的程序的抽象.其中包括該程序的程序計數器,寄存器,變量的當前值等.
2. 線程:是一種輕量化的進程,與進程不同的是它與其他同屬一個進程的線程間是共享寄存器的.
一句話解釋系統實現(偽)并行的方法:
CPU在所有進程間快速切換.如何觸發切換
這種切換的出發機制有兩種可能,一種是時鐘中斷,一種是I/O中斷.具體的實現細節在不同的操作系統中是不同的,但是大體上是類似的,CPU在經歷了N個時鐘中斷后將當前的進程掛起,轉而運行下一個進程(具體運行哪一個程序,這取決于操作系統的調度算法).或者當進程在等待I/O操作,或有耗時操作,需要CPU忙等待時,CPU就會運行下一進程.-
如何切換
進程切換過程如下:- 時鐘中斷,或I/O中斷,或讀寫數據到達,發生進程切換
- 原有進程的程序計數器,變量當前值,堆棧等被保存在寄存器的一個特殊表中
- 調取下一個進程的程序計數器,變量當前值,堆棧等
- 運行該進程
對于用戶而言,相當于操作系統為其進程或線程提供了一個單獨的CPU和寄存器.
3.互斥問題
在實現了(偽)并行后,馬上就會有一個嚴重的問題擺在面前"競爭條件"
舉例說明:(假設有兩個進程A,B)
1. A將變量a寫入共享寄存器,準備使用顯示器顯示到屏幕上.
2. 就在A準備但還沒有顯示a時,發生了中斷.
3. CPU將A掛起,轉而運行B.
4. B將變量b寫入相同位置,準備顯示.
5. 再次發生中斷,在b沒有顯示的時候,CPU切換回A進程.
6. A錯誤的顯示了變量b.
這樣A的變量a在未被A察覺的情況下永遠的丟失了.
這就是"競爭條件,"為了解決這一問題,A在訪問共享內存時,B必須被阻止訪問到該寄存器.即該寄存器必須被"互斥"的訪問.
3.1 單CPU計算機如何解決互斥問題
由此可見,競爭條件問題形成的充分條件有兩個,一是要有共享資源,二是要在特定的時間發生中斷.只要打破了其中的一條,就能完成互斥.實現資源的不共享是不可能的,所以我們必須能夠讓CPU屏蔽中斷信號.
在實現互斥的問題上,我們必須引入一個概念"鎖變量".這個變量為0時,表示共享資源可以被訪問,為1時表示共享資源已被鎖,不能訪問.
為每個共享資源加入鎖變量后,在進程想要訪問共享資源時直接讀取鎖變量就知道資源是否可用.
具體實現如下:
1. 進程A想要訪問共享資源,通知CPU屏蔽所有中斷.
2. A判斷資源是否可用,可用則將鎖變量置1.不可用則伺機再從第一步重新執行.
3. CPU開始接收中斷正常運行.
4. A完成操作,將鎖變量置0.
這樣就實現了單CPU計算機系統上的互斥,同樣還有很多算法也可以實現互斥,如Peterson算法.但是包括上述方法,在具體使用場景中,用的很少.一般使用TSL指令來實現互斥.
3.2 多CPU(或多核)計算機解決互斥問題
在多CPU(多核心CPU)計算機系統中,通過屏蔽中斷,是無法實現互斥的.因為就算讓進程所在的CPU屏蔽了中斷,也不能保證運行在其他CPU上的進程不會訪問到共享資源.所以需要一種全新的互斥方法.
TSL(Test and Set Lock)指令.這是一種硬件級別的指令,其核心方法是直接鎖定內存總線.
TSL的匯編代碼進行了如下操作:
1. 將鎖變量復制到程序寄存器中,并將原來的鎖變量置1.
2. 將寄存器中的值與0做對比,等于則表示可以訪問共享資源,不等于則伺機再執行TSL.
3. 操作結束后,講鎖變量置0.
有了TSL指令,就可以在多CPU計算機系統上實現互斥.
在Pthread中有關互斥量的操作,都是這個原理.
3.3 信號量和互斥量
用TSL可以原子的實現信號量和互斥量,也可以理解為信號量和互斥量是對TSL指令的封裝.
信號量是記錄未被執行的鎖和釋放的次數,在解決界緩沖問題的時候,通過多個信號量來標記每個進程的狀態,從而決定進程的行為.過程較為復雜,再次不做詳述.
互斥量是簡化的信號量,因為互斥量是一個二元信號量,一般是0和其他.用于標記兩種狀態.
值得注意的是不管是在信號量還是互斥量操作中,未獲得鎖后是直接放棄CPU使用(thread_yeild調用),而不是忙等待.
4.哲學家就餐
現在嘗試用以上互斥方法解決哲學家就餐問題.
4.1 問題描述
- 有N個哲學家,哲學家狀態有兩種,就餐和思考,并隨機發生.
- 每個哲學家面前有一碗面,左右各一把叉子.共有N碗面,N個叉子(兩個相鄰的哲學家之間只有一把叉子).
- 哲學家就餐需要有兩把叉子,并且只能在自己左右取.規定,哲學家先拿左叉后拿右叉.
- 讓每個哲學家順利完成就餐和思考.
4.2 問題分析
- 如果N位哲學家同時就餐,拿起身邊的左叉,等待右叉就會發生死鎖.
- 如果在一位哲學家就餐時用互斥量鎖住所有的叉子,不讓其他哲學家就餐,這樣是可以的,但是會造成資源的浪費.
- 所以為了最大化的使用資源,得想辦法鎖定與就餐的哲學家有關的叉子,而不是鎖定全部.
4.3 嘗試解決
該哲學家左右都是處于非就餐狀態的哲學家時,這個哲學家就可以就餐,這樣就解決了資源的最大化利用.
使用三個主要變量
1. 每個哲學家的狀態數組state[],狀態有THINKING,HUNGERY,EATING.
2. 互斥量mutex,用于獲取兩把叉子.
3. 數組s[],由于哲學家在想就餐的時候不一定能立即實現,所以需要信號量來記錄未實現的就餐需求.
步驟:
1. 原子性檢查左右哲學家的狀態
2. 如果左右的哲學家都不是處于EATING狀態,則表示可以就餐.
3. 如果不是,則將自己掛起(對信號量s[i]執行down操作,以記錄這次就餐請求),等待2中描述的狀態.
4. 完成就餐放下所有叉子.
互斥:
1. 哲學家必須原子性的檢查自己左右的哲學家是否處于EATING狀態.使用mutex.
2. 在未達到預期狀態(左右都是非EATING狀態的哲學家)的時候,用一個信號量s[i]記錄用餐請求,等待情況出現.完成就餐后up這個信號量.
來自博客:http://newlooc.com