介紹spin-lock的一篇雜文

來源:互聯網
上載者:User

[引] http://apps.hi.baidu.com/share/detail/39279882

在Linux的核心中,spin lock用在多處理器環境中。當一個CPU訪問一個臨界資源 
(critical section)的時候,需要預先取得spin lock,如果取不到的話,它就在空迴圈 
等待,直到另外的CPU釋放spin lock。由於涉及到多個處理器,spin lock的效率非常重要。
 
因為在等待spin lock的過程,處理器只是不停的迴圈檢查,並不執行其他指令。但即使這樣
, 
一般來說,spn lock的開銷還是比進程調度(context switch)少得多。這就是spin lock 
被廣泛應用在多處理器環境的原因。 

1. spin lock的資料結構 

/* include/asm-i386/spinlock.h */ 

typedef struct { 
volatile unsigned int lock; 
} spinlock_t; 

spin lock的資料結構很簡單,只是一個整數變數lock, 如果lock等於1的話,表示 
這個spin lock是自由的;如果lock小於等於0的話,則表示spin lock已經被其他CPU所 
擷取。 

2. spin lock的實現 

#define spin_lock_string 
"n1:t" 
"lock ; decb %0nt" 
"js 2fn" 
".section .text.lock,"ax"n" 
"2:t" 
"cmpb $0,%0nt" 
"rep;nopnt" 
"jle 2bnt" 
"jmp 1bn" 
".previous" 

#define spin_unlock_string 
"movb $1,%0" 
:"=m" (lock->lock) : : "memory" 

static inline void spin_lock(spinlock_t *lock) 

__asm__ __volatile__( 
spin_lock_string 
:"=m" (lock->lock) : : "memory"); 

static inline void spin_unlock(spinlock_t *lock) 

char oldval = 1; 

__asm__ __volatile__( 
spin_unlock_string 
); 

如果將上面的語句轉化成純彙編的話,則是這樣: 

spin_lock(lock) 

1: 
lock ; decb %0 
js 2f 

.section .text.lock, "ax" 
2: cmpb $0,%0 
rep;nop 
jle 2b 
jmp 1b 
.previous 

其中%0就是函數參數傳進來的lock->lock,下面詳細地解釋一下每一條 
彙編指令: 
* lock ; decb %0 
decb將lock->lock減1,它前邊的lock指令表示在執行decb的時候,要鎖住 
記憶體匯流排(memory bus),另外的CPU不能訪問記憶體,以保證decb指令的原子性。 
注意,decb並不是原子操作(atomic operation),它需要將變數從記憶體讀出來, 
放入寄存器(register),減1,再寫入記憶體。如果在這時候另外的CPU也進行同樣的操作的
 
時候,那麼decb的執行結果就會不確定,也就是說,操作的原子性遭到了破壞。 

* js 2f 
如果decb的結果小於0,表示無法取得spin lock,則跳到標籤為2的指令(f表示向前跳)。
 
如果decb的結果等於0,表示已經獲得spin lock,執行下一條指令,則跳出整段代碼,函數
返回。 
注意, "j2 2f"的下一條指令並不是"cmpb $0,%0"。 

* .section .text.lock, "ax" 
.previous 
從.section到.previous的這一段代碼被用來檢測spin lock何時被釋放。linux定義了一個 
專門的區(.text.lock)來存放這段代碼。它們和前邊的"js 2f"並不在一個區(section)裡
, 
    所以說"js 2f"的下一條指令並不是"cmpb $0,%0"。 
    之所以定義成一個單獨的區,原因是在大多數情況下,spin lock是能擷取成功的,
從.section 
    到.previous的這一段代碼並不經常被調用,如果把它跟別的常用指令混在一起,會
浪費指令 
    緩衝的空間。從這裡也可以看出,linux核心的實現,要時時注意效率。 

* 2: cmpb $0,%0 
  rep;nop 
jle 2b 
jmp 1b 
檢查lock->lock,和0比較,如果小於等於0(jle 2b),則跳回到標籤2的指令,重新比較 
(b表示往回跳)。如果大於0,表示spin lock已經被釋放,則往回跳回到標籤1,重新試圖 
     取得spin lock。 

  * rep;nop 
這是一條很有趣的指令:),咋一看,這隻是一條空指令,但實際上這條指令可以降低CPU的運
行頻率,減低電的消耗量,但最重要的是,提高了整體的效率。因為這段指令執行太
快的話,會產生很多讀取記憶體變數的指令,另外的一個CPU可能也要寫這個記憶體變數,現在的CPU經
常需要重新排序指令來提高效率,如果讀指令太多的話,為了保證指令之間的依賴性,CPU會以
犧牲流水線執行(pipeline)所帶來的好處。從pentium 4以後,intel引進了一條pause指令,
專門用於spin lock這種情況,據intel的文檔說,加上pause可以提高25倍的效率! 

spin_unlock(lock) 
  * movb $1,%0 
spin_unlock的實現很簡單,只是重新將lock->lock置1就行了。 

  還有一個問題我想談的是,在linux 2.3以前,spin lock是用"lock; btrl $0,%0"來實
現解鎖的,但是後來的版本只使用了簡單的mov指令,執行時間從22個刻度降低到1個時鐘
周期。 
但是最開始linus本人不同意這種做法,以為他以為由於intel晶片的指令重排序,會使spin lock 
的實現不穩定,但後來intel裡的一個工程師出來澄清了linus的錯誤。這也許是open sourc
e的好處吧。 

  spin lock的實現看起來簡單,但是細微之處卻很複雜,如果大家需要進一步理解,請細
細讀一下 kernel的mail list和intel關於pentium的文檔。 

 

 

=====================

Memory Ordering (http://www.cnblogs.com/codingmylife/archive/2010/04/28/1722573.html)

Background
很久很久很久以前,CPU忠厚老實,一條一條指令的執行我們給它的程式,規規矩矩的進行計算和記憶體的存取。
很久很久以前, CPU學會了Out-Of-Order,CPU有了Cache,但一切都工作的很好,就像很久很久很久以前一樣,而且工作效率得到了很大的提高。
很久以前,我們需要多個CPU一起工作,於是出現了傳說中的SMP系統,每個CPU都有獨立的Cache,都會亂序執行,會打亂記憶體存取順序,於是事情變得複雜了…… Problem
由於每個CPU都有自己的Cache,記憶體讀寫不再一定需要真的作記憶體訪問,而是直接從Cache裡面操作,同時CPU可能會在合適的時候對於記憶體訪問進行重新排序以提高效率,在只有一個CPU的時候,這很完美。
而當有多個CPU的時候—— 從Cache到記憶體的flush操作通常是被延遲的,所以就需要某種方法保證CPU A進行的記憶體寫操作真的可以被CPU B讀取到。 CPU可能會因為某些原因(比如某兩個變數同在一個Cacheline中)而打亂 實際記憶體寫入順序 實際記憶體讀取順序 所以就需要某種方法保證在需要的時候 之前的讀寫操作已經完成 未來的讀寫操作還沒開始 考慮一個例子:
Thread A:
while (flag == 0)
        ; // do nothing
printf("%d\n", data);
Thread B:
data = 523;
flag = 1;
這裡data代表了某種資料,它可以像這裡一樣是一個簡單的整數,也可能是某種複雜的資料結構,總之,我們在Thread B中對data進行了寫入,並利用flag變數表示data已經準備好了。
在Thread A中,一個忙等待直到發現data已經準備好了,然後開始使用data,這裡是簡單的把data列印出來。
現在考慮如果CPU發現對於data和flag的寫入,如果按照先寫入flag後寫入data的方式進行,或者考慮由於Cache的flush操作的延遲,使得記憶體中變數的實際修改順序是先flag後data,那麼都將導致Thread A的結果不正確。事實上,由於記憶體讀入操作同樣是可能亂序進行的,Thread A甚至可能在讀入flag進行判斷之前就已經完成了對data的讀入操作,這同樣導致錯誤的結果。 Solution
在這個例子中,我們的需求是,Thread A中對於flag判斷時,後面的任何讀入操作都沒有開始,Thread B中對於flag寫入時,任何之前的寫入操作都已經完成。
在Linux核心中,smp_rmb()、smp_wmb()、smp_mb()就是用來解決這類問題的,mb表示memory barrier。rmb表示讀操作不可跨越(注意,不是人民幣的意思:-P),也就是我們這個例子中的Thread A所需要的。wmb表示寫操作不可跨越,也就是這裡Thread B所需要的。mb集合了rmb和wmb的能力,讀寫操作都不可跨越。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.