spin_lock()--自旋鎖

#include  

#include

#include  

 自旋鎖線程不能睡眠

MODULE_LICENSE("GPL"); 

static int __init spin_lock_init_init(void); 

static void __exit spin_lock_init_exit(void); 

spinlock_t lock = SPIN_LOCK_UNLOCKED;       

int __init spin_lock_init_init(void) 

{         

       printk("<0>SPIN_LOCK_UNLOCKED: %d\n",SPIN_LOCK_UNLOCKED.raw_lock.slock);

       

       spin_lock_init( &lock );  //初始化自旋鎖

       printk("<0>after init, slock: %d\n",lock.raw_lock.slock);

       

       printk("<0>\n");

 

       spin_lock( &lock );      //第一次獲取自旋鎖

       printk("<0>first spin_lock, slock: %d\n",lock.raw_lock.slock);

       spin_unlock( &lock );    //第一次釋放自旋鎖

       printk("<0>first spin_unlock, slock: %d\n",lock.raw_lock.slock);

 

       printk("<0>\n");

 

       spin_lock( &lock );      //第二次獲取自旋鎖

       printk("<0>second spin_lock, slock: %d\n",lock.raw_lock.slock);

       spin_unlock( &lock );    //第二次釋放自旋鎖

       printk("<0>second spin_unlock, slock: %d\n",lock.raw_lock.slock);

 

       return 0;

}

void __exit spin_lock_init_exit(void) 

       printk("<0>exit!\n");

}

module_init(spin_lock_init_init); 

module_exit(spin_lock_init_exit);


 首先編譯模塊,執行命令insmod spin_lock_init.ko 插入模塊,然後執行命令dmesg –c,會出現如圖8.40所示的結果:宏:spin_lock_init <wbr>( <wbr>)

結果分析:

測試程序中調用了宏spin_lock( )和宏spin_unlock( ),分別用來獲取和釋放自旋鎖,關於其功能見本章中關於它們的分析。

測試程序中,首先輸出宏SPIN_LOCK_UNLOCKED的信息,它所表示的自旋鎖的狀態爲未使用。調用宏spin_lock_init( )初始化自旋鎖lock,結構體raw_spinlock_t中的slock字段被置爲0,即 Owner 和 Next 爲 0。然後第一次執行獲取和釋放鎖的操作,調用spin_lock( )後,slock字段爲256,16進製爲0x0100,即Next 域進行了加1操作;調用spin_unlock( ),slock字段爲0x0101,此時Owner域也進行了加1 操作,二者相等,鎖處於未使用狀態。第二次獲取和釋放鎖時,slock字段分別爲0x0201和0x0202,可以看到線程是按照申請順序依次獲取排隊自旋鎖的。

如果一個自旋鎖持有者在釋放鎖之前,另一個線程想要申請鎖,則它會檢測到鎖的狀態爲正在被使用(因爲鎖未釋放前Next域和Owner域不相等),從而進入忙等待。


本文不打算詳細探究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)

針對單處理器系統,對第一種情況,只要臨時關閉系統搶佔即可,我們可以使用以下方法:
  1. preempt_disable();  
  2. .....  
  3. // 訪問共享對象的代碼  
  4. ......  
  5. preempt_enable();  

同樣地,針對單處理器系統,第二種情況,只要臨時關閉中斷即可,我們可以使用以下方法:

  1. local_irq_disable();  
  2. ......  
  3. // 訪問共享對象的代碼  
  4. ......  
  5. local_irq_enable();  

那麼,針對多處理器的系統,以上的方法還成立麼?答案很顯然:不成立。

對於第一種情況,雖然搶佔被禁止了,可是另一個CPU上還有線程在運行,如果這個線程也正好要訪問該共享對象,上面的代碼段顯然是無能爲力了。

對於第二種情況,雖然本地CPU的中斷被禁止了,可是另一個CPU依然可以產生中斷,如果他的中斷服務程序也正好要訪問該共享對象,上面的代碼段也一樣無法對共享對象進行保護。

實際上,在linux中,上面所說的三種情況都可以用自旋鎖(spin_lock)解決。基本的自旋鎖函數對是:
  • spin_lock(spinlock_t *lock);
  • spin_unlock(spinlock_t *lock);
對於單處理器系統,在不打開調試選項時,spinlock_t實際上是一個空結構,把上面兩個函數展開後,實際上就只是調用preempt_disable()和preempt_enable(),對於單處理器系統,關掉搶佔後,其它線程不能被調度運行,所以並不需要做額外的工作,除非中斷的到來,不過內核提供了另外的變種函數來處理中斷的問題。

對於多處理器系統,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();
其實變種還不止這幾個,還有read_lock_xxx/write_lock_xxx、spin_lock_bh/spin_unlock_bh、spin_trylock_xxx等等。但常用的就上面幾種。

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;
寫到這裏不知大家明白了沒?對於2.6.33和之後的版本,我的理解是:
  • 儘可能使用spin_lock;
  • 絕對不允許被搶佔和休眠的地方,使用raw_spin_lock,否則使用spin_lock;
  • 如果你的臨界區足夠小,使用raw_spin_lock;
對於沒有打上Linux-RT(實時Linux)的patch的系統,spin_lock只是簡單地調用raw_spin_lock,實際上他們是完全一樣的,如果打上這個patch之後,spin_lock會使用信號量完成臨界區的保護工作,帶來的好處是同一個CPU可以有多個臨界區同時工作,而原有的體系因爲禁止搶佔的原因,一旦進入臨界區,其他臨界區就無法運行,新的體系在允許使用同一個臨界區的其他進程進行休眠等待,而不是強佔着CPU進行自旋操作。寫這篇文章的時候,內核的版本已經是3.3了,主線版本還沒有合併Linux-RT的內容,說不定哪天就會合並進來,也爲了你的代碼可以兼容Linux-RT,最好堅持上面三個原則。

總結:

1、自旋鎖不能休眠

2、自旋鎖互斥的過程:

spi_lock();  關閉調度器的課搶佔性, 讀自旋鎖,檢測,跟新自旋鎖 (定義的是一個全局變量鎖)

spi_lock_irq; 關閉中斷(local_irq_disable),關閉調度器的課搶佔性, 讀自旋鎖,檢測,跟新自旋鎖

3、併發的概念,主要是對共享資源的競爭狀態,不一定是時間上的。

發佈了27 篇原創文章 · 獲贊 0 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章