本文轉自http://blog.csdn.net/droidphone/article/details/7395983
本文不打算詳細探究spin_lock的詳細實現機制,只是最近對raw_spin_lock的出現比較困擾,搞不清楚什么時候用spin_lock,什么時候用raw_spin_lock,因此有了這篇文章。
/*****************************************************************************************************/
聲明:本博內容均由http://blog.csdn.net/droidphone原創,轉載請注明出處,謝謝!
/*****************************************************************************************************/
1. 臨界區(Critical Section)
我們知道,臨界區是指某個代碼區間,在該區間中需要訪問某些共享的數據對象,又或者是總線,硬件寄存器等,通常這段代碼區間的范圍要控制在盡可能小的范圍內。臨界區內需要對這些數據對象和硬件對象的訪問進行保護,保證在退出臨界區前不會被臨界區外的代碼對這些對象進行修改。出現以下幾種情形時,我們需要使用臨界區進行保護:
- (1) 在可以搶占(preemption)的系統中,兩個線程同時訪問同一個對象;
- (2) 線程和中斷同時訪問同一個對象;
- (3) 在多核系統中(SMP),可能兩個CPU可能同時訪問同一個對象;
2. 自旋鎖(spin_lock)
針對單處理器系統,對第一種情況,只要臨時關閉系統搶占即可,我們可以使用以下方法:
- preempt_disable();
- .....
- // 訪問共享對象的代碼
- ......
- preempt_enable();
同樣地,針對單處理器系統,第二種情況,只要臨時關閉中斷即可,我們可以使用以下方法:
- local_irq_disable();
- ......
- // 訪問共享對象的代碼
- ......
- local_irq_enable();
那么,針對多處理器的系統,以上的方法還成立么?答案很顯然:不成立。
對於第一種情況,雖然搶占被禁止了,可是另一個CPU上還有線程在運行,如果這個線程也正好要訪問該共享對象,上面的代碼段顯然是無能為力了。
對於第二種情況,雖然本地CPU的中斷被禁止了,可是另一個CPU依然可以產生中斷,如果他的中斷服務程序也正好要訪問該共享對象,上面的代碼段也一樣無法對共享對象進行保護。
實際上,在linux中,上面所說的三種情況都可以用自旋鎖(spin_lock)解決。基本的自旋鎖函數對是:
- spin_lock(spinlock_t *lock);
- spin_unlock(spinlock_t *lock);
對於多處理器系統,spinlock_t實際上等效於內存單元中的一個整數,內核保證spin_lock系列函數對該整數進行原子操作,除了調用preempt_disable()和preempt_enable()防止線程被搶占外,還必須對spinlock_t上鎖,這樣,如果另一個CPU的代碼要使用該臨界區對象,就必須進行自旋等待。
對於中斷和普通線程都要訪問的對象,內核提供了另外兩套變種函數:
- spin_lock_irq(spinlock_t *lock);
- spin_unlock_irq(spinlock_t *lock);
- spin_lock_irqsave(lock, flags);
- spin_lock_irqrestore(lock, flags);
- 如果只是在普通線程之間同時訪問共享對象,使用spin_lock()/spin_unlock();
- 如果是在中斷和普通線程之間同時訪問共享對象,並且確信退出臨界區后要打開中斷,使用spin_lock_irq()/spin_unlock_irq();
- 如果是在中斷和普通線程之間同時訪問共享對象,並且退出臨界區后要保持中斷的狀態,使用spin_lock_irqsave()/spin_unlock_irqrestore();
3. raw_spin_lock
在2.6.33之后的版本,內核加入了raw_spin_lock系列,使用方法和spin_lock系列一模一樣,只是參數有spinlock_t變為了raw_spinlock_t。而且在內核的主線版本中,spin_lock系列只是簡單地調用了raw_spin_lock系列的函數,但內核的代碼卻是有的地方使用spin_lock,有的地方使用raw_spin_lock。是不是很奇怪?要解答這個問題,我們要回到2004年,MontaVista Software, Inc的開發人員在郵件列表中提出來一個Real-Time Linux Kernel的模型,旨在提升Linux的實時性,之后Ingo Molnar很快在他的一個項目中實現了這個模型,並最終產生了一個Real-Time preemption的patch。
該模型允許在臨界區中被搶占,而且申請臨界區的操作可以導致進程休眠等待,這將導致自旋鎖的機制被修改,由原來的整數原子操作變更為信號量操作。當時內核中已經有大約10000處使用了自旋鎖的代碼,直接修改spin_lock將會導致這個patch過於龐大,於是,他們決定只修改哪些真正不允許搶占和休眠的地方,而這些地方只有100多處,這些地方改為使用raw_spin_lock,但是,因為原來的內核中已經有raw_spin_lock這一名字空間,用於代表體系相關的原子操作的實現,於是linus本人建議:
- 把原來的raw_spin_lock改為arch_spin_lock;
- 把原來的spin_lock改為raw_spin_lock;
- 實現一個新的spin_lock;
- 盡可能使用spin_lock;
- 絕對不允許被搶占和休眠的地方,使用raw_spin_lock,否則使用spin_lock;
- 如果你的臨界區足夠小,使用raw_spin_lock;