arm架構下spinlock原理 (程式碼解讀)
http://blog.csdn.net/longwang155069/article/details/52055876
自旋鎖的引入
原子變數適用在多核之間多單一共享變數進行互斥訪問,如果要保護多個變數,並且這些變數之間有邏輯關係時,原子變數就不適用了。例如:常見的雙向連結串列。假設有三個連結串列節點A、B、C。需要將節點B插入節點A、C之間。如果CPU A剛好將A節點的後向指標指向B,但是還沒有將B的後向指標指向C。此時CPU B要遍歷連結串列,這將會一個災難性的後果。
如果共享資料段在中斷上下文或者程序上下文被訪問呢? 如果在程序上下文被訪問,完全可以使用訊號量semaphore機制來實現互斥。如果在中斷上下文被訪問呢? 就不能使用semaphore來實現互斥,因為semaphore會引起睡眠的。這時候就引入了spin_lock
spin_lock的實現思想
先說生活中一個示例,如果機智的你乘坐過火車的話,就一定知道早上6點-7點在火車上廁所的感受了。如果機智你的起來上廁所,發現一大堆人都等著上廁所,男女老少。接設你前面排了三個人,分別為A, B, C。
當A進入廁所之後,關閉了廁所的門,然後就會看見一個紅燈亮著“有人“,這時候B,C和機智的你都在等待。當A出來後,B進去不到20s就出來了。然後進去了C,然後你就苦苦的在等待,一直在觀察這什麼時候紅燈熄滅,這讓機智的你等待了10min, 然後機智的你進去就10s搞定。好了關於生活的例子說完了,再回到spin_lock中。
可以將廁所當作臨界區。A, B, C, 機智的你是四個cpu, 紅燈是臨界區時候有cpu進入狀態。
當A進入臨界區(廁所),然後就會將進入狀態修改為忙(紅燈亮),然後B,C以及機智的你都會判斷當前狀態,如果是忙,就等待,不忙就讓B先進去,B進入之後同樣的操作。
spin_lock早期程式碼分析
因為spin_lock在ARM平臺上的實現策略發生過變化,所以先分析以前版本2.6.18的spin_lock。
主要是以SMP系統分析,後面會稍帶分析UP系統。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
其中preempt_disable()是用來關閉掉搶佔的。如果系統中打開了CONFIG_PREEMPT該選項的話,就是用來關閉系統的搶佔,如果沒有開啟相當於什麼都沒幹,只是為了統一程式碼。至於這裡為什麼需要關閉搶佔,在後面會說。
- 1
- 1
這段程式碼使用來除錯使用的,沒有系統沒有開啟CONFIG_DEBUG_LOCK_ALLOC配置的話,這樣程式碼也相當於什麼都沒幹。繼續往下。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
回頭看看spinlock_t變數的定義:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
通過層層的呼叫,最後spinlock_t就是一個volatile unsigned int型變數。
彙編程式碼 | C語言 | 解釋 |
---|---|---|
1: ldrex %0, [%1] | tmp=lock->lock | 讀取lock的狀態賦值給tmp |
teq %0, #0 | if(tmp == 0) | 判斷lock的狀態是否為0。如果是0說明可以獲得鎖;如果不為0,說明自旋鎖處於上鎖狀態,不能訪問,執行bne 1b指令,跳到標號1處不停執行。 |
strexeq %0, %2, [%1] | lock->lock=1 | 使用常量1來更新鎖的狀態,並將執行結果放入到tmp中 |
teqeq %0, #0 | if(tmp == 0) | 用來判斷tmp是否為0,如果為0,表明更新鎖的狀態成功;如果不為0表明鎖的狀態沒喲更新成功,執行”bne 1b”,跳轉到標號1繼續執行。 |
早期spin_lock存在的不公平性
還是回到火車上上廁所的故事中,某天早上去上廁所,發現有一大堆的人都在排隊。但是進去廁所的人已經進去了半個小時,後面的人已經開始等待不急了,有的謾罵起來,有人大喊憋不住了,機智你的剛好肚子疼,快憋不住了。剛好排在第一位是你的媳婦,然後你就插隊立馬上了廁所。你出來後,接著是你兒子,然後你全家。後面的人就一直等待了1個小時終於進入了廁所。
將這個現象轉移到程式中就是,在現代多核的cpu中,因為每個cpu都有chach的存在,導致不需要去訪問主存獲取lock,所以噹噹前獲取lock的cpu,釋放鎖後,使其他cpu的cache都失效,然後釋放的鎖在下一次就比較容易進入臨界去,導致出現了不公平。
ticket機制原理
先看最新的spin_lock的結構體定義:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
在分析程式碼之前,還需要解釋一下tickets中的owner和next的含義。詳細可見提交:
546c2896a42202dbc7d02f7c6ec9948ac1bf511b
因為有cache的作用,導致本次釋放lock的cpu在下一次就可以更快的獲取鎖。所以在ARMv6上引入了”票”演算法來保證每個cpu都是像“FIFO“訪問臨界區。
還是說回到火車上廁所的事件,還是早上排隊上廁所。這時候好多人都插隊,導致沒有熟人的人一直上不了廁所,於是火車管理員(虛擬的,只是為了講解原理而已)出現了。火車管理員說“從現在開始不準插隊,我來監督,所有人排位一隊“。管理員站在廁所門口,讓大家都按次序排隊上廁所,這時候就沒有人插隊了。
將這個事件轉移到程式中的ticket中。剛開始的時候臨界區沒有cpu進入,狀態是空閒的。next和owner的值都是0,當cpu1進入臨界區後。將next++, 當cpu1從臨界區域執行完後,將owner++。這時候next和owner都為1,說明臨界區沒有cpu進入。這時候cpu2進入臨界區,將next++, 然後cpu2好像乾的活比較多,當cpu3進來後,next++,這時候next已經是3了,當cpu2執行完畢後,owner++,owner的值變為2, 表示讓cpu2進入臨界區,這就保障了各個cpu之間都是先來後到的執行。
ARM32 上spin_lock程式碼實現
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
彙編 | C語言 | 解釋 |
---|---|---|
1: ldrex %0, [%3] | lockval = lock | 讀取鎖的值賦值給lockval |
add %1, %0, %4 | newval = lockval + (1 << 16) | 將next++之後的值存在newval中 |
strex %2, %1, [%3] | lock = newval | 將新的值存在lock中,將是否成功結果存入在tmp中 |
teq %2, #0 | if(tmp == 0) | 判斷上條指令是否成功,如果不成功執行”bne 1b”跳到標號1執行 |
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
當tickets中的next和owner不相等的時候,說明臨界區在忙, 需要等待。然後cpu會執行wfe指令。當其他cpu忙完之後,會更新owner的值,如果owner的值如果與next值相同,那到next號的cpu執行。
ARM64 上spin_lock程式碼實現
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
彙編 | C語言 | 解釋 |
---|---|---|
prfm pstl1strm, %3 | 將lock變數讀到cache,增加訪問速度 | |
1: ldaxr %w0, %3 | lockval = lock | 將lock的值賦值給lockval |
add %w1, %w0, %w5 | newval=lockval + (1 << 16) | 將lock中的next++, 然後將結果賦值給newval |
stxr %w2, %w1, %3 | lock = newval | 將newval賦值給lock,同時將是否設定成功結果存放到tmp |
cbnz %w2, 1b | if(tmp != 0)goto 1 | 如果tmp不為0,跳到標號1執行 |
eor %w1, %w0, %w0, ror #16 | if(next == owner) | 判斷next是否等於owner |
cbz %w1, 3f | if(newval == 0) | 進入臨界區 |
2: wfe | 自旋等待 | |
ldaxrh %w2, %4 | tmp = lock->owner | 獲取當前的Owner值存放在tmp中 |
eor %w1, %w2, %w0, lsr #16 | if(next == owner) | 判斷next是否等於owner |
cbnz %w1, 2b | 如果不等跳到標號2自旋,負責進入臨界區域 |
ARM64 上spin_unlock程式碼實現
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
解鎖的操作相對簡單,就是給owner執行加1的操作。
http://blog.csdn.net/longwang155069/article/details/52211024