生產者-消費者實例
當生產者發現 counter == BUFFER_SIZE
生產者等待(阻塞)
當消費者發現 counter == 0
消費者等待(阻塞)
存在問題:
只用counter
做判斷,在圖中情況下會導致P2不會被喚醒。
應該需要另一量(信號量)考慮記錄有多少進程阻塞等信息,並進行判斷。
信號量
當 sem < 0
表示 sem
有多個進程在等待
當 sem > 0
表示有空閒
B,有2個資源可以使用
信號量的定義
queue
代表阻塞隊列
V()
的模型:
V(semaphore s)
{
s.value++;
if(s.value <= 0)
{
wakeup(s.queue);
}
}
用信號量解決生產者-消費者問題
empty
表示空閒緩衝區,當緩衝區空時,生產者等待。full
表示緩衝區使用的數量,與empty
相反。
Producer()
中,作爲生產者,P(empty)
,V(full)
,test 有沒有空閒緩衝區。
Consumer()
中,作爲消費者,P(full)
,V(empty)
,test 緩衝區是否爲空
生產者什麼時候會停?
當緩衝區爲滿時,空閒緩衝區爲空時。
消費者什麼時候會停?
當緩衝區爲空時,緩衝區全部空閒時。
mutex
互斥信號量,初值爲1
....
P(mutex) //申請鎖 mutex --
{
......
}
V(mutex)//釋放鎖 mutex ++;
....
由於mutex
初值爲1
,當進程A申請鎖時(P(mutex)
),mutex --
,此時爲0
。
若此時進程B也申請鎖,由於此時進程A已上鎖,P(mutex)
將會使mutex
由0
改爲 -1
,此時進程將被sleep()
。
當進程A執行完畢,釋放鎖(V(mutex)
),會使mutex
由-1
改爲0
,將喚醒進程B,進程B取得鎖。
進程B執行完畢,釋放鎖(V(mutex)
),將會使mutex
恢復至1
.
信號量臨界區保護
**什麼是信號量?**通過對這個量的訪問和修改,讓大家有序推進。
爲什麼要保護信號量?
既然要對這個量訪問和修改,那麼一定會出現 競爭條件。
競爭條件:和調度有關的共享數據語義錯誤。
臨界區:
讀寫信號量的代碼一定是臨界區,加以保護。
保護原則:
臨界區保護的方法
輪換法:
標記法:
非對稱標記:
標記與輪換的綜合:Peterson算法
flag[i]
可以理解爲:當前要執行i
進程
turn = j
可以理解爲: 接下來執行j
進程
多個進程:麪包店算法
硬件法:關中斷
調度由中斷產生,控制中斷可以組織調度,cli()
關中斷,sli()
開中斷。
多CPU(多核)無效的原因:cli()
和sli()
會使當前CPU忽略INTR寄存器上的中斷標記,但無法控制其他CPU。
硬件法:原子指令法
信號量代碼實現
(1)信號量數據結構以及應該在內核態,用戶態應定義系統調用:sem_open()
,調用sem_open()
進入內核態調用sys_sem_open()
,sys_sem_open()
中創建信號量。
(2)此生產者要在文件中寫入5個數:寫之前判斷是否有空閒緩衝區:sem_wait()
,sys_sem_wait
中實現P(empty)
的功能:
....
cli();//保護信號量
if(semtable[sd].value -- < 0)
sleep_on(current);
(semtable[sd].queue).push(current); //加入隊列,具體實現略
schedule();
sti();
....
Linux 0.11 中:
- 獲取空閒緩存
- 啓動讀入(上鎖)
sleep_on 的實現:
在 四:進程運行軌跡的跟蹤與統計中提到了sleep_on
的實現,這個隊列的實現及其巧妙。
wake_up
會將等待隊列全部喚醒,然後根據優先級獲取資源。
再回頭看lock_buffer()
:因爲全部喚醒,優先級最高的會上鎖,其他的再次睡眠。