當前位置:首頁 » 操作系統 » linux自旋鎖

linux自旋鎖

發布時間: 2022-08-07 10:19:18

A. linux自旋鎖原理

自旋鎖(Spin Lock)是一種典型的對臨界資源進行互斥訪問的手段,其名稱來源於它的工作方式。為了獲得一個自旋鎖,在某CPU上運行的代碼需先執行一個原子操作,該操作測試並設置(Test-AndSet)某個內存變數。由於它是原子操作,所以在該操作完成之前其他執行單元不可能訪問這個內存變數。如果測試結果表明鎖已經空閑,則程序獲得這個自旋鎖並繼續執行;如果測試結果表明鎖仍被佔用,程序將在一個小的循環內重復這個「測試並設置」操作,即進行所謂的「自旋」,通俗地說就是「在原地打轉」。當自旋鎖的持有者通過重置該變數釋放這個自旋鎖後,某個等待的「測試並設置」操作向其調用者報告鎖已釋放。理解自旋鎖最簡單的方法是把它作為一個變數看待,該變數把一個臨界區標記為「我當前在運行,請稍等一會」或者標記為「我當前不在運行,可以被使用。如果A執行單元首先進入常式,它將持有自旋鎖;當B執行單元試圖進入同一個常式時,將獲知自旋鎖已被持有,需等到A執行單元釋放後才能進入。在ARM體系結構下,自旋鎖的實現借用了ldrex指令、strex指令、ARM處理器內存屏障指令dmb和dsb、wfe指令和sev指令,這類似於代碼清單7.1的邏輯。可以說既要保證排他性,也要處理好內存屏障。自旋鎖主要針對SMP或單CPU但內核可搶占的情況,對於單CPU和內核不支持搶占的系統,自旋鎖退化為空操作。在單CPU和內核可搶占的系統中,自旋鎖持有期間中內核的搶占將被禁止。由於內核可搶占的單CPU系統的行為實際上很類似於SMP系統,因此,在這樣的單CPU系統中使用自旋鎖仍十分必要。另外,在多核SMP的情況下,任何一個核拿到了自旋鎖,該核上的搶占調度也暫時禁止了,但是沒有禁止另外一個核的搶占調度。盡管用了自旋鎖可以保證臨界區不受別的CPU和本CPU內的搶占進程打擾,但是得到鎖的代碼路徑在執行臨界區的時候,還可能受到中斷和底半部的影響。為了防止這種影響,就需要用到自旋鎖的衍生。

B. linux原子操作和自旋鎖的區別

現代操作系統支持多任務的並發,並發在提高計算資源利用率的同時也帶來了資源競爭的問題。例如C語言語句「count++;」在未經編譯器優化時生成的匯編代碼為。

當操作系統內存在多個進程同時執行這段代碼時,就可能帶來並發問題。

假設count變數初始值為0。進程1執行完「mov eax, [count]」後,寄存器eax內保存了count的值0。此時,進程2被調度執行,搶佔了進程1的CPU的控制權。進程2執行「count++;」的匯編代碼,將累加後的count值1寫回到內存。然後,進程1再次被調度執行,CPU控制權回到進程1。進程1接著執行,計算count的累加值仍為1,寫回到內存。雖然進程1和進程2執行了兩次「count++;」操作,但是count實際的內存值為1,而不是2!
單處理器原子操作
解決這個問題的方法是,將「count++;」語句翻譯為單指令操作。

Intel x86指令集支持內存操作數的inc操作,這樣「count++;」操作可以在一條指令內完成。因為進程的上下文切換是在總是在一條指令執行完成後,所以不會出現上述的並發問題。對於單處理器來說,一條處理器指令就是一個原子操作。
多處理器原子操作
但是在多處理器的環境下,例如SMP架構,這個結論不再成立。我們知道「inc [count]」指令的執行過程分為三步:
1)從內存將count的數據讀取到cpu。
2)累加讀取的值。
3)將修改的值寫回count內存。
這又回到前面並發問題類似的情況,只不過此時並發的主題不再是進程,而是處理器。
Intel x86指令集提供了指令前綴lock用於鎖定前端串列匯流排(FSB),保證了指令執行時不會受到其他處理器的干擾。

使用lock指令前綴後,處理器間對count內存的並發訪問(讀/寫)被禁止,從而保證了指令的原子性。

x86原子操作實現
Linux的源碼中x86體系結構原子操作的定義文件為。
linux2.6/include/asm-i386/atomic.h
文件內定義了原子類型atomic_t,其僅有一個欄位counter,用於保存32位的數據。
typedef struct { volatile int counter; } atomic_t;
其中原子操作函數atomic_inc完成自加原子操作。
/**
* atomic_inc - increment atomic variable
* @v: pointer of type atomic_t
*
* Atomically increments @v by 1.
*/
static __inline__ void atomic_inc(atomic_t *v)
{
__asm__ __volatile__(
LOCK "incl %0"
:"=m" (v->counter)
:"m" (v->counter));
}
其中LOCK宏的定義為。
#ifdef CONFIG_SMP
#define LOCK "lock ; "
#else
#define LOCK ""
#endif
可見,在對稱多處理器架構的情況下,LOCK被解釋為指令前綴lock。而對於單處理器架構,LOCK不包含任何內容。
arm原子操作實現
在arm的指令集中,不存在指令前綴lock,那如何完成原子操作呢?
Linux的源碼中arm體系結構原子操作的定義文件為。
linux2.6/include/asm-arm/atomic.h
其中自加原子操作由函數atomic_add_return實現。
static inline int atomic_add_return(int i, atomic_t *v)
{
unsigned long tmp;
int result;
__asm__ __volatile__("@ atomic_add_return\n"
"1: ldrex %0, [%2]\n"
" add %0, %0, %3\n"
" strex %1, %0, [%2]\n"
" teq %1, #0\n"
" bne 1b"
: "=&r" (result), "=&r" (tmp)
: "r" (&v->counter), "Ir" (i)
: "cc");
return result;
}
上述嵌入式匯編的實際形式為。
1:
ldrex [result], [v->counter]
add [result], [result], [i]
strex [temp], [result], [v->counter]
teq [temp], #0
bne 1b
ldrex指令將v->counter的值傳送到result,並設置全局標記「Exclusive」。
add指令完成「result+i」的操作,並將加法結果保存到result。
strex指令首先檢測全局標記「Exclusive」是否存在,如果存在,則將result的值寫回counter->v,並將temp置為0,清除「Exclusive」標記,否則直接將temp置為1結束。
teq指令測試temp值是否為0。
bne指令temp不等於0時跳轉到標號1,其中字元b表示向後跳轉。
整體看來,上述匯編代碼一直嘗試完成「v->counter+=i」的操作,直到temp為0時結束。
使用ldrex和strex指令對是否可以保證add指令的原子性呢?假設兩個進程並發執行「ldrex+add+strex」操作,當進程1執行ldrex後設定了全局標記「Exclusive」。此時切換到進程2,執行ldrex前全局標記「Exclusive」已經設定,ldrex執行後重復設定了該標記。然後執行add和strex指令,完成累加操作。再次切換回進程1,接著執行add指令,當執行strex指令時,由於「Exclusive」標記被進程2清除,因此不執行傳送操作,將temp設置為1。後繼teq指令測定temp不等於0,則跳轉到起始位置重新執行,最終完成累加操作!可見ldrex和strex指令對可以保證進程間的同步。多處理器的情況與此相同,因為arm的原子操作只關心「Exclusive」標記,而不在乎前端串列匯流排是否加鎖。
在ARMv6之前,swp指令就是通過鎖定匯流排的方式完成原子的數據交換,但是影響系統性能。ARMv6之後,一般使用ldrex和strex指令對代替swp指令的功能。
自旋鎖中的原子操作
Linux的源碼中x86體系結構自旋鎖的定義文件為。
linux2.6/include/asm-i386/spinlock.h
其中__raw_spin_lock完成自旋鎖的加鎖功能
#define __raw_spin_lock_string \
"\n1:\t" \
"lock ; decb %0\n\t" \
"jns 3f\n" \
"2:\t" \
"rep;nop\n\t" \
"cmpb $0,%0\n\t" \
"jle 2b\n\t" \
"jmp 1b\n" \
"3:\n\t"
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
__asm__ __volatile__(
__raw_spin_lock_string
:"=m" (lock->slock) : : "memory");
}
上述代碼的實際匯編形式為。
1:
lock decb [lock->slock]
jns 3
2:
rep nop
cmpb $0, [lock->slock]
jle 2
jmp 1
3:
其中lock->slock欄位初始值為1,執行原子操作decb後值為0。符號位為0,執行jns指令跳轉到3,完成自旋鎖的加鎖。
當再次申請自旋鎖時,執行原子操作decb後lock->slock值為-1。符號位為1,不執行jns指令。進入標簽2,執行一組nop指令後比較lock->slock是否小於等於0,如果小於等於0回到標簽2進行循環(自旋)。否則跳轉到標簽1重新申請自旋鎖,直到申請成功。
自旋鎖釋放時會將lock->slock設置為1,這樣保證了其他進程可以獲得自旋鎖。
信號量中的原子操作
Linux的源碼中x86體系結構自旋鎖的定義文件為。
linux2.6/include/asm-i386/semaphore.h
信號量的申請操作由函數down實現。
/*
* This is ugly, but we want the default case to fall through.
* "__down_failed" is a special asm handler that calls the C
* routine that actually waits. See arch/i386/kernel/semaphore.c
*/
static inline void down(struct semaphore * sem)
{
might_sleep();
__asm__ __volatile__(
"# atomic down operation\n\t"
LOCK "decl %0\n\t" /* --sem->count */
"js 2f\n"
"1:\n"
LOCK_SECTION_START("")
"2:\tlea %0,%%eax\n\t"
"call __down_failed\n\t"
"jmp 1b\n"
LOCK_SECTION_END
:"=m" (sem->count)
:
:"memory","ax");
}
實際的匯編代碼形式為。
lock decl [sem->count]
js 2
1:
<========== another section ==========>
2:
lea [sem->count], eax
call __down_failed
jmp 1
信號量的sem->count一般初始化為一個正整數,申請信號量時執行原子操作decl,將sem->count減1。如果該值減為負數(符號位為1)則跳轉到另一個段內的標簽2,否則申請信號量成功。
標簽2被編譯到另一個段內,進入標簽2後,執行lea指令取出sem->count的地址,放到eax寄存器作為參數,然後調用函數__down_failed表示信號量申請失敗,進程加入等待隊列。最後跳回標簽1結束信號量申請。
信號量的釋放操作由函數up實現。
/*
* Note! This is subtle. We jump to wake people up only if
* the semaphore was negative (== somebody was waiting on it).
* The default case (no contention) will result in NO
* jumps for both down() and up().
*/
static inline void up(struct semaphore * sem)
{
__asm__ __volatile__(
"# atomic up operation\n\t"
LOCK "incl %0\n\t" /* ++sem->count */
"jle 2f\n"
"1:\n"
LOCK_SECTION_START("")
"2:\tlea %0,%%eax\n\t"
"call __up_wakeup\n\t"
"jmp 1b\n"
LOCK_SECTION_END
".subsection 0\n"
:"=m" (sem->count)
:
:"memory","ax");
}
實際的匯編代碼形式為。
lock incl sem->count
jle 2
1:
<========== another section ==========>
2:
lea [sem->count], eax
call __up_wakeup
jmp 1
釋放信號量時執行原子操作incl將sem->count加1,如果該值小於等於0,則說明等待隊列有阻塞的進程需要喚醒,跳轉到標簽2,否則信號量釋放成功。
標簽2被編譯到另一個段內,進入標簽2後,執行lea指令取出sem->count的地址,放到eax寄存器作為參數,然後調用函數__up_wakeup喚醒等待隊列的進程。最後跳回標簽1結束信號量釋放。

C. Linux自旋鎖的使用

自旋鎖(Spin Lock)是一種典型的對臨界資源進行互斥訪問的手段,其名稱來源於它的工作方式。為了獲得一個自旋鎖,在某CPU上運行的代碼需先執行一個原子操作,該操作測試並設置(Test-AndSet)某個內存變數。由於它是原子操作,所以在該操作完成之前其他執行單元不可能訪問這個內存變數。如果測試結果表明鎖已經空閑,則程序獲得這個自旋鎖並繼續執行;如果測試結果表明鎖仍被佔用,程序將在一個小的循環內重復這個「測試並設置」操作,即進行所謂的「自旋」,通俗地說就是「在原地打轉」。當自旋鎖的持有者通過重置該變數釋放這個自旋鎖後,某個等待的「測試並設置」操作向其調用者報告鎖已釋放。理解自旋鎖最簡單的方法是把它作為一個變數看待,該變數把一個臨界區標記為「我當前在運行,請稍等一會」或者標記為「我當前不在運行,可以被使用。如果A執行單元首先進入常式,它將持有自旋鎖;當B執行單元試圖進入同一個常式時,將獲知自旋鎖已被持有,需等到A執行單元釋放後才能進入。在ARM體系結構下,自旋鎖的實現借用了ldrex指令、strex指令、ARM處理器內存屏障指令dmb和dsb、wfe指令和sev指令,這類似於代碼清單7.1的邏輯。可以說既要保證排他性,也要處理好內存屏障。
自旋鎖主要針對SMP或單CPU但內核可搶占的情況,對於單CPU和內核不支持搶占的系統,自旋鎖退化為空操作。在單CPU和內核可搶占的系統中,自旋鎖持有期間中內核的搶占將被禁止。由於內核可搶占的單CPU系統的行為實際上很類似於SMP系統,因此,在這樣的單CPU系統中使用自旋鎖仍十分必要。另外,在多核SMP的情況下,任何一個核拿到了自旋鎖,該核上的搶占調度也暫時禁止了,但是沒有禁止另外一個核的搶占調度。盡管用了自旋鎖可以保證臨界區不受別的CPU和本CPU內的搶占進程打擾,但是得到鎖的代碼路徑在執行臨界區的時候,還可能受到中斷和底半部的影響。為了防止這種影響,就需要用到自旋鎖的衍生。

D. linux自旋鎖使用時需要注意的幾個地方

1、在內核多線程編程時,為了保護共享資源通常需要使用鎖,而使用的比較多的就是spinlock,但需要注意的是:所有臨界區代碼都需要加鎖保護,否則就達不到保護效果。也就是,訪問共享資源的多個線程需要協同工作共同加鎖才能保證不出錯。在實際寫代碼時,有時候會網掉這一點,以致出現各種稀奇古怪的問題,而且很難找到原因。
2、在出現兩個和多個自旋鎖的嵌套使用時,務必注意加鎖和解鎖的順序。
比如:在線程1中,spinlock A -> spinlock B -> spin unlock B -> spin unlock A ;那麼,在需要同步的線程2中,若需要加相同的鎖,則順序也應該保持相同,spinlock A -> spinlock B -> spin unlock B -> spin unlock A ;否則,很有可能出現死鎖。
3、spinlock保護的代碼執行時間要盡量短,若有for循環之類的代碼,則一定要確保循環可以在短時間可以退出,從而使得spinlock可以釋放。
4、spinlock所保護的代碼在執行過程中不能睡眠。比如,在spinlock和spinunlock之間不能調用kmalloc, _from_user,kthread_stop等調用,因為這些函數調用均有可能導致線程睡眠。
5、spinlock在實際使用時有如下幾種類型,spin_lock,spin_lock_bh,spin_lock_irqsave。在具體使用時,需要根據被保護臨界區鎖處的上下文選擇合適的spinlock類型。
spin_lock用於不同cpu線程間同步,spin_lock_bh和spin_lock_irqsave主要用於本cpu線程間的同步,前者關軟中斷,後者關硬中斷。

E. 關於Linux自旋鎖

既然是對一個變數進行保護,當然是一個自旋鎖了,還沒見過一個變數能當兩個用的。
我覺得你對這段代碼的理解有問題,用 spin_lock 和 spin_unlock 的目的是保證程序在對 xxx_lock 進行操作的時候,不會有其它進程改變這個值,是為了保證數據的准確性。
你可以設想一下,如果沒有自旋鎖,代碼運行起來會有什麼問題。假設 A,B兩個進程同時訪問 open , 沒有使用自旋鎖,此時 xxx_lock=0, A 進程在判斷 if (xxx_count) 時,會認為設備沒有被使用,那麼它會繼續後面的 xxx_count++ 操作,但假如這時 CPU 切換進程, A 進程還沒有來得及把 xxx_count 變成 1 的時候, B 進程開始運行,那麼 B 進程此時也會認為設備沒有被使用,它也會進行後繼操作,這樣就會出現兩個進程同時訪問設備的錯誤。

open 和 release 當然可以同時訪問,只不過在運行 spin_lock 的時候,後訪問的進程會被阻塞而已。假設有 A 進程訪問 open ,B 進程訪問 release ,你可以把這種情況理解為 A , B 進程同時訪問 open 函數,這樣或許能更好的理解這段代碼。因為 open 和 release 在使用自旋鎖的時候,方法是一樣的。

spin_lock 和 CPU 系統無關,不管是單 CPU 還是多 CPU ,運行結果都是一樣的。

這個邏輯關系比較難解釋,不知道你看懂我的意思沒。

F. 自旋鎖 是用於線程同步還是linux內核同步

自旋鎖在線程同步和linux內核同步中都有,但在用戶態使用的比較少,在內核使用的比較多

熱點內容
資料庫10061 發布:2025-01-16 16:11:47 瀏覽:700
電腦網路ip地址怎麼配置 發布:2025-01-16 16:03:48 瀏覽:329
我的世界安卓網易版怎麼裝材質包 發布:2025-01-16 16:00:55 瀏覽:254
404頁面源碼 發布:2025-01-16 15:58:48 瀏覽:887
手機建行密碼忘記了怎麼辦 發布:2025-01-16 15:45:38 瀏覽:224
易語言視頻播放源碼 發布:2025-01-16 15:39:35 瀏覽:343
肇觀演算法 發布:2025-01-16 15:38:39 瀏覽:610
管家婆找不到加密狗 發布:2025-01-16 15:10:28 瀏覽:308
linux的etcfstab 發布:2025-01-16 15:00:43 瀏覽:364
電腦無法登錄內網伺服器 發布:2025-01-16 15:00:28 瀏覽:575