作業系統學習筆記:程序同步
互相協作的程序之間有共享的資料,於是這裡就有一個併發情況下,如何確保有序操作這些資料、維護一致性的問題,即程序同步。
從底層到高階應用,同步機制依次有臨界區、訊號量、管程、原子事務。
1、臨界區
每個程序有一個程式碼段稱為臨界區,共享資料在此進行操作。沒有兩個程序同時在臨界區執行。
臨界區方案是一種協議,即每個程序進入臨界區操作都需要請求。實現這一請求的程式碼稱為進入區,從臨界區退出的善後工作由退出區,之後是剩餘區。
臨界區方案必須滿足三項要求:
1)互斥
兩個程序不能同時在臨界區操作
2)前進
臨界區空閒,如果有程序需要,且不在剩餘區,則可參加選擇
3)有限等待
程序只要有意願,總有一天會進入臨界區,因為程序進入臨界區的次數有上限。
作業系統內部的臨界區問題中,非搶佔式比較容易,因為程序沒有競爭條件;而搶佔式則困難得多,因為程序可能會執行在不同處理器上。但搶佔式核心更適合實時程式設計。
Peterson演算法是一種臨界區問題演算法。
對於臨界區問題,除了軟體上進行設計,也可以在硬體層面來解決。現代計算機系統提供了一些特殊硬體指令,可以原子地執行。
2、訊號量
臨界區方案比較複雜,可以使用訊號量這個同步工具。
訊號量是一個整數變數,除了初始化,只能通過兩個標準原子操作:wait()和signal()來訪問。
wait(s){ while(s <= 0) ;//當s<=0時,迴圈等待,直到S變為正數。如果將這個S看做可用資源,就很好理解了。S<=0,代表沒有資源 s--;//可用資源減一 } signal(s){ s++;//可用資源加一 }
//使用訊號量實現臨界區問題方案
do{
wait(mutex);
//臨界區
signal(mutex);
//剩餘區
}while(true);
上述例子中,有迴圈等待,又叫忙等待。忙等待浪費了CPU時鐘,這在多道程式系統中,顯然是個問題,因為本可以讓給其他程序執行。
不過,這種依靠忙等待實現的訊號量又稱為自旋鎖(spinlock)。自旋鎖有一定的優越性,因為無須進行上下文切換,有時上下文切換相比之下更浪費時間)。通常,等待時間如果比較短,就適合用自旋鎖。自旋鎖常用在多處理器系統中,因為多執行緒可以用於多處理器,一個執行緒自旋,另一個執行緒可以在另一個處理器上執行。
不過,為了克服忙等的缺點,可以修改wait()和signal()的定義,採用程序堵塞來替代忙等:
typedef struct {
int value;//記錄了這個訊號量的值
struct process *list;//儲存正在等待這個訊號量的程序
} semaphore;
wait(semaphore *S) {
S->value--;
if(S->value < 0) {//沒有資源了
add this process to S->list;//進入等待佇列
block();//堵塞
}
}
signal(semaphore *S) {
S->value++;
if(S->value <= 0) {//上面++後,S仍然還<=0,說明資源供不應求,等待者眾,於是喚醒等待佇列中的一個,意思是說,我做完了,你好自為之。至於是否可以獲得資源,看造化。。。就此別過,青山綠水,後會有期,good bye!
remove a process P from S->list;
wakeup(P);//切換到就緒狀態
}
}
3、管程
訊號量比臨界區方便,但如果使用不正確,比如順序不當,仍然會導致一些錯誤。
管程用高階語言封裝了訊號量,方便程式設計師呼叫。
管程結構確保一次只有一個程序能在管程內活動。但是,程序在管程內 應該怎麼理解?難道是程序在管程裡面執行?但看上去,是程序呼叫了管程,依管程的返回訊號而行事?
管程通常是用於管理資源的,因此管程中有程序等待佇列和相應的等待和喚醒操作。在管程入口有一個等待佇列,稱為入口等待佇列。當一個已進入管程的程序等待時,就釋放管程的互斥使用權;當已進入管程的一個程序喚醒另一個程序時,兩者必須有一個退出或停止使用管程。在管程內部,由於執行喚醒操作,可能存在多個等待程序(等待使用管程),稱為緊急等待佇列,它的優先順序高於入口等待佇列。
因此,一個程序進入管程之前要先申請,一般由管程提供一個enter過程;離開時釋放使用權,如果緊急等待佇列不空,則喚醒第一個等待者,一般也由管程提供外部過程leave。
管程內部有自己的等待機制。管程可以說明一種特殊的條件型變數:var c:condition;實際上是一個指標,指向一個等待該條件的PCB(程序控制塊)佇列。對條件型變數可執行wait和signal操作
wait(c):若緊急等待佇列不空,喚醒第一個等待者,否則釋放管程使用權。執行本操作的程序進入C佇列尾部;
signal(c):若C佇列為空,繼續原程序,否則喚醒佇列第一個等待者,自己進入緊急等待佇列尾部。
(額,從上述描述看,管程可以控制程序等待、喚醒等,從這點來說,程序在管程內是說得過去的)
生產者-消費者問題(有buffer)
問題描述:(一個倉庫可以存放K件物品。生產者每生產一件產品,將產品放入倉庫,倉庫滿了就停止生產。消費者每次從倉庫中去一件物品,然後進行消費,倉庫空時就停止消費。
解答:
管程:buffer=MODULE;
(假設已實現一基本管程monitor,提供enter,leave,signal,wait等操作)
notfull,notempty:condition; // notfull控制緩衝區不滿,notempty控制緩衝區不空;
count,in,out: integer; // count記錄共有幾件物品,in記錄第一個空緩衝區,out記錄第一個不空的緩衝區
buf:array [0..k-1] of item_type;
define deposit,fetch;
use monitor.enter,monitor.leave,monitor.wait,monitor.signal;
procedure deposit(item);
{
if(count=k) monitor.wait(notfull);
buf[in]=item;
in:=(in+1) mod k;
count++;
monitor.signal(notempty);
}
procedure fetch:Item_type;
{
if(count=0) monitor.wait(notempty);
item=buf[out];
in:=(in+1) mod k;
count--;
monitor.signal(notfull);
return(item);
}
{
count=0;
in=0;
out=0;
}
程序:producer,consumer;
producer(生產者程序):
Item_Type item;
{
while (true)
{
produce(&item);
buffer.enter();
buffer.deposit(item);
buffer.leave();
}
}
consumer(消費者程序):
Item_Type item;
{
while (true)
{
buffer.enter();
item=buffer.fetch();
buffer.leave();
consume(&item);
}
}
4、原子事務
有一些操作裡面的步驟必須一口氣全部執行完,不可分割,結果是要麼全部成功,要麼就失敗。
這點在資料庫技術上體現得淋漓盡致:事務。近來(什麼時候的事了?)有將資料庫技術應用於作業系統的熱潮。
1)日誌
資料庫的資料為什麼能儲存得那麼好?很大程度上是歸功於日誌。
最常用的方法是操作資料的時候,先記錄日誌,再操作資料。
每條日誌記錄:
(1)事務名稱
(2)資料項名稱
(3)舊值
(4)新值
事務開始前,記錄<t_start>記入日誌;
當事務提交時,記錄<t_commit>記入日誌;
如果事務失敗,或者系統故障,系統就會檢查日誌(這一步也許在系統重啟之時),凡有<t_start>記錄而無<t_commit>的,系統做回滾操作;兩條記錄都有的,系統則將資料重新寫一遍。(有些重寫可能是不必要的,但也不會引起錯誤)
但這種做法很浪費,因為絕大多數的事務都是成功的。於是引入檢查點(checkpoint):
當系統將資料從記憶體寫入硬碟或穩定儲存裝置時,記錄一個<checkpoint>。以後系統重啟時只處理這個checkpoint之後的日誌記錄。
2)鎖及時間戳
在併發的情況下,多個事務同時執行,由於事務是原子性的,所以事務併發,其實相當於讓一個個事務序列化執行。這裡就牽扯到序列排程和非序列排程。
非序列排程不一定會引起錯誤,因為事務之間,裡面的步驟不一定會相關。將這些步驟打散、組合,可能效率會更高。
序列處理可以依靠:
(1)鎖
(2)時間戳
方案是資料讀寫時記錄時間值:
W-timestamp(Q)
R-timestamp(Q)
Q是資料項,只要操作Q,即記錄時間。
在一個事務中,如果發出read(Q)
(1)事務開始時間 < W-timestamp(Q),表明值正在被改寫,read被拒絕,事務回滾;
(2)事務開始時間 >= W-timestamp(Q),read,R-timestamp(Q) = MAX(R-timestamp(Q),事務時間);
如果事務發出write(Q)
(1)事務開始時間 < R-timestamp(Q),表明值正在被讀取,write被拒絕,事務回滾;
(2)事務開始時間 < W-timestamp(Q),表明值正在被修改,write被拒絕,事務回滾;;
(3)否則,write
參考文章:
http://www.cnblogs.com/sonic4x/archive/2011/07/05/2098036.html