spinlock的設計和實現

2021-04-09 00:59:43 字數 3250 閱讀 4332

在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/a**-i386/spinlock.h */

typedef struct 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)

static inline void spin_unlock(spinlock_t *lock)

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

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 source的好處吧。

spin lock的實現看起來簡單,但是細微之處卻很複雜,如果大家需要進一步理解,請細細讀一下

kernel的mail list和intel關於pentium的文件。

ARM Linux下spinlock 的實現

1 spin lock 結構體 typedef struct spinlock endif spinlock t typedef struct raw spinlock raw spinlock t typedef struct tickets arch spinlock t 暫且拋開那些confi...

mutex和spin lock的區別

mutex和spin lock的區別和應用 sleep waiting和busy waiting的區別 2011 10 19 11 43 訊號量mutex是sleep waiting。就是說當沒有獲得mutex時,會有上下文切換,將自己 加到忙等待佇列中,直到另外乙個執行緒釋放mutex並喚醒它,而...

mutex 和spin lock的區別

在這裡推薦一本書 深入linux核心機制分析 這本書對這些講得非常好,對有基礎的朋友幫助會很大,新手建議不好要使用,因為有一種雲裡霧裡的感覺,非常的枯燥。有哪些核心鎖機制?1 原子操作 atomic t資料型別,atomic inc atomic t v 將v加1 原子操作比普通操作效率要低,因此必...