Linux驱动编程 step-by-step (七) 并发 竞态 (信号量与自旋锁)
并發 競態 (信號量與自旋鎖)
代碼傳至并發競態控制
前面所述的字符驅動都是沒有考慮并發竟態的情況,想象一下
一個進程去讀一個字符設備,另一個進程在同一時間向這個設備寫入(完全有這種情況)
原來設備中存有 A B C D 要想設備寫入1 2 3 4 每次讀寫一個字節
| t1 | t2 | t3 | t4 | t5 | t6 | t7 | t8 |
| R | W | W | R | W | R | R | W |
| A | 1 | 2 | 2 | 3 | 3 | D | 4 |
W:write
所以最后讀出了A23D不是原來的ABCD
而如果兩個進程同時寫入一個設備則寫入的值可能既不是A進程想要的又不是B進程想要的。
并發與競態
并發是指 多個進程同時訪問相同一段代碼(不僅限于內核空間的代碼)
競態 是對共享資源訪問的結果, 并發進程訪問了相同的數據或結構(硬件資源)等。
解決競態的思想 主要有兩種
1、所有的代碼都不使用全局變量
2、使用鎖機制確保一次只有一個進程使用共享資源
顯然第一種方式是不可能的,我們只能盡量少的使用全局變量,而不能完全避免它
下邊介紹 兩種鎖機制
信號量
這個概念對我們來說應該不是很陌生,至少聽說過,它有兩種操作:通常叫做P操作與V操作
希望進入臨界區的進程調用P操作,檢測當前信號量,如果信號量大于0,則信號量減1,進入臨界區,否則進程等待其他進程釋放此信號量,信號量的釋放通過一個V操作增加信號量的值,在某些情況下會去喚醒等待此信號量的進程。
以上說到了一個 臨界區這個名詞 簡單來講就是內部操作了共享數據的代碼
Linux 內核中信號量的實現
因為從LLD3(2.6.10)版本出來到現在(3.1)內核函數有了很多的變化,許多書上介紹的函數都已經不存在了,也能幾版新kernel之后現在我說的函數也會有變化了。
信號量結構以及相關函數的定義在<linux/semaphore.h>中
創建信號量
[cpp]?view plaincopyval :??? 初始化信號量的值(代表了資源數)【val 為1時 表示代碼在同一時間只允許一個進程訪問 稱之為互斥鎖】
[cpp]?view plaincopy
P操作 [cpp]?view plaincopy
第二個函數 在第一個操作的基礎上,如果進程因為沒有得到信號量睡眠,在別的進程釋放信號量或者發成中斷的情況下都會被喚醒,在被中斷信號喚醒時候返回-EINTR,成功返回0
第三個函數 如果沒有另外的任務會獲取此信號量,則可以調用此函數,在收到中斷信號時會返回-EINTR
第四個函數 試圖去獲取信號量,在獲取不到的時候不會睡眠,而是繼續運行,返回0值表示得到了此信號量, 返回1 表示沒能獲取到。
第五個函數 可以去設置最長睡眠時間, 但是 此函數不可中斷
V操作
[cpp]?view plaincopy
自旋鎖
自旋鎖是另一種鎖機制,信號量會引起進程的休眠,而在不能睡眠的代碼中我們就需要使用自旋鎖。
自旋鎖也是一個互斥的概念,有“鎖定”與“解鎖”兩個操作,當程序需要鎖定時候,則先檢測鎖是否可用,如果可用則獲得鎖,程序進入臨界區,否則進入忙等待重復檢測這個鎖是否可用(這就是自旋),臨界區代碼操作完成則解鎖。
信號量實現中其實也用到了自旋鎖機制(有興趣的刻一看內核源碼,這邊不展開,等以后寫內核時候再介紹)
因為自旋鎖在得不到鎖的時候會“自旋” 即不會讓出CPU ,所以我們的臨界區執行速度應該盡量的快,最好使用原子操作(不會睡眠)。這也是使用自旋鎖的核心規則,在多數情況下我們做不到這一點,所以自旋鎖在驅動程序中使用的不如信號量頻繁。
初始化
[cpp]?view plaincopy鎖定
[cpp]?view plaincopy第二個函數 會嘗試加鎖,檢測返回之判斷是否鎖定,在不能鎖定時候程序也繼續運行
第三個函數 禁止中斷,不保存保存原先的中斷狀態
第四個函數 在禁止中斷之前,保存原先的中斷狀態,
第五個函數 表示只禁止軟件中斷而保持硬件中斷的打開
因為自旋鎖本質上要不會被中斷,所以調用時候建議使用包含有禁止中斷的函數
解鎖
[cpp]?view plaincopy死鎖
上邊只提到了使用鎖的好處,以及如何使用鎖,但是引入鎖機制也會帶來風險,那就是死鎖,進程死鎖最明顯的表現就是死機。
如上圖所示,A進程占有鎖1,并在持有鎖1的時候需要得到鎖2程序才能繼續進行
B進程占有鎖2, 并在保持鎖2的同時去獲取鎖1程序才能繼續運行,這樣A, B 進程就卡在這里,互不相讓,這就導致了死鎖。
亦或 在一個進程內同時試圖獲取同樣的鎖
死鎖的名詞解釋
是指兩個或兩個以上的進程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。此時稱系統處于死鎖狀態或系統產生了死鎖
解決死鎖的方法:
1、加解鎖順序一致
在必須使用多個鎖的時候,應該始終以相同的順序獲取,也最好以獲取鎖順序逆序解鎖。
2、使用原子變量
原子變量不會被多個進程并發操作,內核提供了一種原子類型(atomic_t <asm/stomic.h>中)
在一定時間內不能獲得鎖,就放棄,并釋放已占有的鎖
總結
以上是生活随笔為你收集整理的Linux驱动编程 step-by-step (七) 并发 竞态 (信号量与自旋锁)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SD初始化过程以及Cmd解析
- 下一篇: Linux驱动编程 step-by-st