Linux內核設計與實現 總結筆記(第九章)內核同步介紹

在使用共享內存的應用程序中,程序員必須特別留意保護共享資源,防止共享資源併發訪問。程序員

1、臨界區和競爭條件

1.1 臨界區和競爭條件

所謂臨界區就是訪問和操做共享數據代碼段。多個執行線程併發訪問同一個資源一般是不安全的,爲了不在臨界區中併發訪問,編程者必須保證這些代碼原子地執行。編程

若是兩個執行線程有可能處於同一個臨界區中同時執行,若是這種狀況確實發生了,咱們就稱它是競爭條件。由於競爭引發的錯誤很是不易重現,因此調試這種錯誤纔會很是困難。安全

避免併發和防止競爭條件稱爲同步。數據結構

 

爲何要保護?併發

有些事務必須完整地發生,要麼乾脆不發生,可是毫不能打斷。異步

2、加鎖

鎖提供的就是這種機制:它就如同一把門鎖,門後的房間可想象成要給臨界區。在一個指定的時間內,房間裏只能有一個執行線程存在,當一個線程進入房間後,它會鎖住身後的房門。當它結束對共享數據的操做後,就會走出房間,打開門鎖。若是另外一個線程在房門上鎖時來了,那麼它就必須等待房間內的線程出來併發開門鎖後,才能進入房間。函數

線程持有鎖,鎖保護了數據。工具

2.1 形成併發執行的緣由

內核中有相似可能形成併發執行的緣由,它們是:線程

  • 中斷----中斷幾乎能夠在任什麼時候刻異步發生,也就可能隨時打斷當前正在執行的代碼
  • 軟中斷和tasklet----內核能在任什麼時候刻喚醒或調度軟中斷和tasklet,打斷當前正在執行的代碼
  • 內核搶佔----由於內核具備搶佔型,因此內核中的任務可能會被另外一任務搶佔
  • 睡眠及與用戶空間的同步----在內核執行的進程可能會睡眠,這就會喚醒調度程序,從而致使調度一個新的用戶進程執行
  • 對稱多處理----兩個或多個處理器能夠同時執行代碼

真正困難的就是發現上述的潛在併發執行的可能,並有意識地採起某些措施來防止併發執行。設計

其實,真正用鎖來保護共享資源並不困難,尤爲是在設計代碼的早期就這麼作了,事情就更簡單了。

辨認出真正須要共享的數據和響應的臨界區,纔是真正有挑戰性的地方。

 

2.2 瞭解要保護些什麼

找出哪些數據須要保護是關鍵所在。

線程中的局部數據僅僅被它訪問,顯然不須要保護。還有動態分配的數據結構,其地址在堆棧中。也不須要

大多數內核數據結構都須要加鎖,

若是有其餘執行線程能夠訪問這些數據,那麼就給這些數據加上某種形式的鎖。

若是任何其餘什麼東西都能看到它,那麼就要鎖住它。記住:要給數據而不是給代碼加鎖。

在編寫代碼時,你要問本身下面這些問題:

  • 這個數據是否是全局的?除了當前線程外,其餘線程能不能訪問它。
  • 這個數據會不會在進程上下文和中斷上下文中共享?它是否是要在兩個不一樣的中斷處理程序中共享?
  • 進程在訪問數據時可不可能被搶佔?被調度的新程序會不會訪問同一數據?
  • 當前進程是否是會睡眠(阻塞)在某些資源上,若是是,它會讓共享數據處於核中狀態?
  • 怎樣防止數據失控?
  • 若是這個函數又在另外一個處理器上被調度將會發生什麼呢?
  • 如何確保代碼原理併發威脅呢?

簡而言之,全部內核全局變量和共享數據都須要某種形式的同步方法。

 

3、死鎖

死鎖產生須要必定條件:要有一個或多個執行線程和一個或多個資源,每一個線程都在等待其中的一個資源,但全部資源都已經被佔用了。

自死鎖:一個執行線程試圖去得到要給本身已經持有的鎖,它將不得不等待鎖被釋放。最後智能死鎖

一些簡單的規則對避免死鎖大有幫助:

  • 按順序加鎖,使用嵌套的鎖時必須保證以相同的順序獲取鎖,這樣能夠阻止致命擁抱類型的死鎖。最好能記錄下鎖的順序,以便其餘人也能照此順序使用
  • 防止發飢餓,這個代碼是否是必定能執行結束?若是張不發生,王是否要一直等待下去
  • 不要重複請求同一個鎖
  • 設計應力求簡單----越複雜的加鎖方案越有可能死鎖

防止死鎖很重要,內核提供了一些簡單易用的調試工具,能夠在運行時檢測死鎖。

4、爭用和擴展性

鎖的爭用,簡稱爭用,指當鎖正在被佔用時,有其餘線程試圖得到該鎖。

當一個鎖處於高度爭用狀態,會成爲系統的瓶頸。儘管如此,也比多個進程搶奪資源要好。

鎖的粒度,須要合理的安排。不少鎖的設計在開始階段都很粗,可是當鎖爭用問題嚴重時,設計就向更加精細的加鎖方向進化。

相關文章
相關標籤/搜索