Linux,同步方法剖析-Linux,同步方法剖析內(nèi)核原子自旋鎖和互斥鎖x_第1頁
Linux,同步方法剖析-Linux,同步方法剖析內(nèi)核原子自旋鎖和互斥鎖x_第2頁
Linux,同步方法剖析-Linux,同步方法剖析內(nèi)核原子自旋鎖和互斥鎖x_第3頁
Linux,同步方法剖析-Linux,同步方法剖析內(nèi)核原子自旋鎖和互斥鎖x_第4頁
免費預覽已結(jié)束,剩余11頁可下載查看

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領

文檔簡介

1、linux,同步方法剖析-linux,同步方法剖析內(nèi)核原子,自旋鎖和互斥鎖x linux 同步方法剖析-linux 同步方法剖析內(nèi)核原子,自旋鎖和互斥鎖 linux 同步方法剖析 -linux 同步方法剖析內(nèi)核原子,自旋鎖和互斥鎖 在學習 linux 的過程中,您也許接觸過并發(fā)(concurrency)、臨界段(critical section)和鎖定,但是如何在內(nèi)核中使用這些概念呢?本文討論了 2.6 版內(nèi)核中可用的鎖定機制,包括原子運算符(atomic operator)、自旋鎖(spinlock)、讀/寫鎖(reader/writer lock)和內(nèi)核信號量(kernel semapho

2、re)。 本文還探討了每種機制最適合應用到哪些地方,以構(gòu)建安全高效的內(nèi)核代碼。 本文討論了 linux 內(nèi)核中可用的大量同步或鎖定機制。這些機制為 2.6.23 版內(nèi)核的許多可用方法提供了應用程序接口(api)。但是在深入學習 api 之前,首先需要明白將要解決的問題。 并發(fā)和鎖定 當存在并發(fā)特性時,必須使用同步方法。當在同一時間段出現(xiàn)兩個或更多進程并且這些進程彼此交互(例如,共享相同的資源)時,就存在 并發(fā) 現(xiàn)象。 在單處理器(uniprocessor,up)主機上可能發(fā)生并發(fā),在這種主機中多個線程共享同一個 cpu 并且搶占(preemption)創(chuàng)建競態(tài)條件。 搶占 通過臨時中斷一個線程

3、以執(zhí)行另一個線程的方式來實現(xiàn) cpu 共享。 競態(tài)條件 發(fā)生在兩個或更多線程操縱一個共享數(shù)據(jù)項時,其結(jié)果取決于執(zhí)行的時間。在多處理器(mp)計算機中也存在并發(fā),其中每個處理器中共享相同數(shù)據(jù)的線程同時執(zhí)行。注意在 mp 情況下存在真正的并行(parallelism),因為線程是同時執(zhí)行的。而在 up 情形中,并行是通過搶占創(chuàng)建的。兩種模式中實現(xiàn)并發(fā)都較為困難。 linux 內(nèi)核在兩種模式中都支持并發(fā)。內(nèi)核本身是動態(tài)的,而且有許多創(chuàng)建競態(tài)條件的方法。linux 內(nèi)核也支持多處理(multiprocessing),稱為對稱多處理(smp)??梢栽诒疚暮竺娴?參考資料 部分學到更多關于 smp 的知識

4、。 臨界段概念是為解決競態(tài)條件問題而產(chǎn)生的。一個 臨界段 是一段不允許多路訪問的受保護的代碼。這段代碼可以操縱共享數(shù)據(jù)或共享服務(例如硬件外圍設備)。臨界段操作時堅持互斥鎖(mutual exclusion)原則(當一個線程處于臨界段中時,其他所有線程都不能進入臨界段)。 臨界段中需要解決的一個問題是死鎖條件??紤]兩個獨立的臨界段,各自保護不同的。每個資源擁有一個鎖,在本例中稱為 a 和 b。假設有兩個線程需要訪問這些資源,線程 x 獲取了鎖 a,線程 y 獲取了鎖 b。當這些鎖都被持有時,每個線程都試圖占有其他線程當前持有的鎖(線程 x 想要鎖 b,線程 y 想要鎖 a)。這時候線程就被死鎖

5、了,因為它們都持有一個鎖而且還想要其他鎖。一個簡單的解決方案就是總是按相同次序獲取鎖,從而使其中一個線程得以完成。還需要其他解決方案檢測這種情形。表 1 定義了此處用到的一些重要的并發(fā)術語。 表 1. 并發(fā)中的重要定義 術語 定義 競態(tài)條件 兩個或更多線程同時操作資源時將會導致不一致的結(jié)果。 臨界段 用于協(xié)調(diào)對共享資源的訪問的代碼段。 互斥鎖 確保對共享資源進行排他訪問的軟件特性。 死鎖 由兩個或更多進程和資源鎖導致的一種特殊情形,將會降低進程的工作效率。 回頁首 linux 同步方法 如果您了解了一些基本理論并且明白了需要解決的問題,接下來將學習 linux 支持并發(fā)和互斥鎖的各種方法。在以

6、前,互斥鎖是通過禁用中斷來提供的,但是這種形式的鎖定效率比較低(現(xiàn)在在內(nèi)核中仍然存在這種用法)。這種方法也不能進行擴展,而且不能保證其他處理器上的互斥鎖。 在以下關于鎖定機制的討論中,我們首先看一下原子運算符,它可以保護簡單變量(計數(shù)器和位掩碼(bitmask)。然后介紹簡單的自旋鎖和讀/寫鎖,它們構(gòu)成了一個 smp 架構(gòu)的忙等待鎖(busy-wait lock)覆蓋。最后,我們討論構(gòu)建在原子 api 上的內(nèi)核互斥鎖。 回頁首 原子操作 linux 中最簡單的同步方法就是原子操作。 原子 意味著臨界段被包含在 api 函數(shù)中。不需要額外的鎖定,因為 api 函數(shù)已經(jīng)包含了鎖定。由于 c 不能實

7、現(xiàn)原子操作,因此 linux 依靠底層架構(gòu)來提供這項功能。各種底層架構(gòu)存在很大差異,因此原子函數(shù)的實現(xiàn)方法也各不相同。一些方法完全通過匯編語言來實現(xiàn),而另一些方法依靠 c 語言并且使用 local_irq_save 和 local_irq_restore 禁用中斷。 當需要保護的數(shù)據(jù)非常簡單時,例如一個計數(shù)器,原子運算符是種理想的方法。盡管原理簡單,原子 api 提供了許多針對不同情形的運算符。下面是一個使用此 api 的示例。 要聲明一個原子變量(atomic variable),首先聲明一個 atomic_t 類型的變量。這個結(jié)構(gòu)包含了單個 int 元素。接下來,需確保您的原子變量使用 a

8、tomic_init 符號常量進行了初始化。 在清單 1 的情形中,原子計數(shù)器被設置為 0。也可以使用 atomic_set function 在運行時對原子變量進行初始化。 清單 1. 創(chuàng)建和初始化原子變量 atomic_t my_counter atomic_init(0); . or . atomic_set( my_counter, 0 ); 原子 api 支持一個涵蓋許多用例的富函數(shù)集??梢允褂?atomic_read 讀取原子變量中的內(nèi)容,也可以使用 atomic_add 為一個變量添加指定值。最常用的操作是使用 atomic_inc 使變量遞增。也可用減號運算符,它的作用與相加和

9、遞增操作相反。清單 2. 演示了這些函數(shù)。 清單 2. 簡單的算術原子函數(shù) val = atomic_read( my_counter ); atomic_add( 1, my_counter ); atomic_inc( my_counter ); atomic_sub( 1, my_counter ); atomic_dec( my_counter ); 該 api 也支持許多其他常用用例,包括 operate-and-test 例程。這些例程允許對原子變量進行操縱和測試(作為一個原子操作來執(zhí)行)。一個叫做 atomic_add_negative 的特殊函數(shù)被添加到原子變量中,然后當結(jié)果值

10、為負數(shù)時返回真(true)。這被內(nèi)核中一些依賴于架構(gòu)的信號量函數(shù)使用。 許多函數(shù)都不返回變量的值,但兩個函數(shù)除外。它們會返回結(jié)果值( atomic_add_return 和 atomic_sub_return ),如清單 3 所示。 清單 3. operate-and-test 原子函數(shù) if (atomic_sub_and_test( 1, my_counter ) / my_counter is zero if (atomic_dec_and_test( my_counter ) / my_counter is zero if (atomic_inc_and_test( my_counte

11、r ) / my_counter is zero if (atomic_add_negative( 1, my_counter ) / my_counter is less than zero val = atomic_add_return( 1, my_counter ); val = atomic_sub_return( 1, my_counter ); 如果您的架構(gòu)支持 64 位長類型( bits_per_long 是 64 的),那么 可以使用 long_t atomic 操作??梢栽?linux/include/asm-generic/atomic.h 中查看可用的長操作(long

12、operation)。 原子 api 還支持位掩碼(bitmask)操作。跟前面提到的算術操作不一樣,它只包含設置和清除操作。許多驅(qū)動程序使用這些原子操作,特別是 scsi。位掩碼原子操作的使用跟算術操作存在細微的差別,因為其中只有兩個可用的操作(設置掩碼和清除掩碼)。使用這些操作前,需要提供一個值和將要進行操作的位掩碼,如清單 4 所示。 清單 4. 位掩碼原子函數(shù) unsigned long my_bitmask; atomic_clear_mask( 0, my_bitmask ); atomic_set_mask( (124), my_bitmask ); 自旋鎖 自旋鎖是使用忙等待鎖

13、來確?;コ怄i的一種特殊方法。如果鎖可用,則獲取鎖,執(zhí)行互斥鎖動作,然后釋放鎖。如果鎖不可用,線程將忙等待該鎖,直到其可用為止。忙等待看起來效率低下,但它實際上比將線程休眠然后當鎖可用時將其喚醒要快得多。 自旋鎖只在 smp 系統(tǒng)中才有用,但是因為您的代碼最終將會在 smp 系統(tǒng)上運行,將它們添加到 up 系統(tǒng)是個明智的做法。 自旋鎖有兩種可用的形式:完全鎖(full lock)和讀寫鎖。 首先看一下完全鎖。 首先通過一個簡單的聲明創(chuàng)建一個新的自旋鎖。這可以通過調(diào)用 spin_lock_init 進行初始化。清單 5 中顯示的每個變量都會實現(xiàn)相同的結(jié)果。 清單 5. 創(chuàng)建和初始化自旋鎖 spin

14、lock_t my_spinlock = spin_lock_unlocked; . or . define_spinlock( my_spinlock ); . or . spin_lock_init( my_spinlock ); 定義了自旋鎖之后,就可以使用大量的鎖定變量了。每個變量用于不同的上下文。 清單 6 中顯示了 spin_lock 和 spin_unlock 變量。這是一個最簡單的變量,它不會執(zhí)行中斷禁用,但是包含全部的內(nèi)存壁壘(memory barrier)。這個變量假定中斷處理程序和該鎖之間沒有交互。 清單 6. 自旋鎖 lock 和 unlock 函數(shù) spin_lock

15、( my_spinlock ); / critical section spin_unlock( my_spinlock ); 接下來是 irqsave 和 irqrestore 對,如清單 7 所示。 spin_lock_irqsave 函數(shù)需要自旋鎖,并且在本地處理器(在 smp 情形中)上禁用中斷。spin_unlock_irqrestore 函數(shù)釋放自旋鎖,并且(通過 flags 參數(shù))恢復中斷。 清單 7. 自旋鎖變量,其中禁用了本地 cpu 中斷 spin_lock_irqsave( my_spinlock, flags ); / critical section spin_unl

16、ock_irqrestore( my_spinlock, flags ); spin_lock_irqsave / spin_unlock_irqrestore 的一個不太安全的變體是 spin_lock_irq / spin_unlock_irq 。 我建議不要使用此變體,因為它會假設中斷狀態(tài)。 最后,如果內(nèi)核線程通過 bottom half 方式共享數(shù)據(jù),那么可以使用自旋鎖的另一個變體。bottom half 方法可以將設備驅(qū)動程序中的工作延遲到中斷處理后執(zhí)行。這種自旋鎖禁用了本地 cpu 上的軟中斷。這可以阻止 softirq、tasklet 和 bottom half 在本地 cpu

17、上運行。這個變體如清單 8 所示。 清單 8. 自旋鎖函數(shù)實現(xiàn) bottom-half 交互 spin_lock_bh( my_spinlock ); / critical section spin_unlock_bh( my_spinlock ); 回頁首 讀/ 寫鎖 在許多情形下,對數(shù)據(jù)的訪問是由大量的讀和少量的寫操作來完成的(讀取數(shù)據(jù)比寫入數(shù)據(jù)更常見)。讀/寫鎖的創(chuàng)建就是為了支持這種模型。這個模型有趣的地方在于允許多個線程同時訪問相同數(shù)據(jù),但同一時刻只允許一個線程寫入數(shù)據(jù)。如果執(zhí)行寫操作的線程持有此鎖,則臨界段不能由其他線程讀取。如果一個執(zhí)行讀操作的線程持有此鎖,那么多個讀線程都可以進入

18、臨界段。清單 9 演示了這個模型。 清單 9. 讀 / 寫自旋鎖函數(shù) rwlock_t my_rwlock; rwlock_init( my_rwlock ); write_lock( my_rwlock ); / critical section - can read and write write_unlock( my_rwlock ); read_lock( my_rwlock ); / critical section - can read only read_unlock( my_rwlock ); 根據(jù)對鎖的需求,還針對 bottom half 和中斷請求(irq)對讀/寫自旋鎖進

19、行了修改。顯然,如果您使用的是原版的讀/寫鎖,那么按照標準自旋鎖的用法使用這個自旋鎖,而不區(qū)分讀線程和寫線程。 回頁首 內(nèi)核互斥鎖 在內(nèi)核中可以使用互斥鎖來實現(xiàn)信號量行為。內(nèi)核互斥鎖是在原子 api 之上實現(xiàn)的,但這對于內(nèi)核用戶是不可見的?;コ怄i很簡單,但是有一些規(guī)則必須.。同一時間只能有一個任務持有互斥鎖,而且只有這個任務可以對互斥鎖進行解鎖?;コ怄i 不能進行遞歸鎖定或解鎖,并且互斥鎖可能不能用于交互上下文。但是互斥鎖比當前的內(nèi)核信號量選項更快,并且更加緊湊,因此如果它們滿足您的需求,那么它們將是您明智的選擇。 可以通過 define_mutex 宏使用一個操作創(chuàng)建和初始化互斥鎖。這將創(chuàng)建一

20、個新的互斥鎖并初始化其結(jié)構(gòu)??梢栽?./linux/include/linux/mutex.h 中查看該實現(xiàn)。 define_mutex( my_mutex ); 互斥鎖 api 提供了 5 個函數(shù):其中 3 個用于鎖定,一個用于解鎖,另一個用于測試互斥鎖。首先看一下鎖定函數(shù)。在需要立即鎖定以及希望在互斥鎖不可用時掌握控制的情形下,可以使用第一個函數(shù) mutex_trylock 。該函數(shù)如清單 10 所示。 清單 10. 嘗試使用 mutex_trylock 獲得互斥鎖 ret = mutex_trylock( my_mutex ); if (ret != 0) / got the lock!

21、 else / did not get the lock 如果想等待這個鎖,可以調(diào)用 mutex_lock 。這個調(diào)用在互斥鎖可用時返回,否則,在互斥鎖鎖可用之前它將休眠。無論在哪種情形中,當控制被返回時,調(diào)用者將持有互斥鎖。最后,當調(diào)用者休眠時使用 mutex_lock_interruptible 。在這種情況下,該函數(shù)可能返回 -eintr 。清單 11 中顯示了這兩種調(diào)用。 清單 11. 鎖定一個可能處于休眠狀態(tài)的互斥鎖 mutex_lock( my_mutex ); / lock is now held by the caller. if (mutex_lock_interruptible( my_mutex ) != 0) / interrupted by a signal, no mutex held 當一個互斥鎖被鎖定后,

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論