大话Linux内核中锁机制之原子操作、自旋锁【转】
轉(zhuǎn)自:http://blog.sina.com.cn/s/blog_6d7fa49b01014q7p.html
多人會問這樣的問題,Linux內(nèi)核中提供了各式各樣的同步鎖機制到底有何作用?追根到底其實是由于操作系統(tǒng)中存在多進程對共享資源的并發(fā)訪問,從而引起了進程間的競態(tài)。這其中包括了我們所熟知的SMP系統(tǒng),多核間的相互競爭資源,單CPU之間的相互競爭,中斷和進程間的相互搶占等諸多問題。
通常情況下,如圖1所示,對于一段程序,我們的理想是總是美好的,希望它能夠這樣執(zhí)行:進程1先對臨界區(qū)完成操作,然后進程2再去操作臨界區(qū)。但是往往現(xiàn)實總是殘酷的,進程1在執(zhí)行過程中,進程2很可能在此插入一腳,導致兩個進程同時對臨界區(qū)進行讀寫訪問,讀是沒有問題,但寫的話問題就大了。這樣的話,得到的結(jié)果往往不是我們想要的。
?
圖1? 一個簡單的例子
因此,我們需要一些解決方法,在Linux內(nèi)核中它提供了如下幾種鎖機制,供用戶在針對不同情況分別或配合使用,包括:原子操作、自旋鎖、內(nèi)存屏障、讀寫自旋鎖、順序鎖、信號量、讀寫信號量、完成量、RCU機制、BKL(大內(nèi)核鎖)等等,下面筆者將分五篇博文一一討論這些鎖機制。另外,本文所涉及的關(guān)于Linux內(nèi)核源碼采用版本為:Linux 3.3.1。OK,讓我們首先討論有關(guān)原子操作和自旋鎖的相關(guān)內(nèi)容吧。
一、原子操作
所謂的原子操作即是保證指令以原子的方式執(zhí)行,它在執(zhí)行過程中不被打斷。它包括了原子整數(shù)操作和原子位操作,在內(nèi)核中分別定義于include\linux\types.h和arch\x86\include\asm\bitops.h。通常了解一個東西,我們是先了解它怎么用的,因此,我們先來看看內(nèi)核提供給用戶的一些接口函數(shù)。對于整數(shù)原子操作函數(shù),如下圖1.1所示,下述有關(guān)加法的操作在內(nèi)核中均有相應的減法操作。
?
圖1.1 ???? 內(nèi)核中的整數(shù)原子操作函數(shù)
如圖1.2展示的是內(nèi)核中提供的一些主要位原子操作函數(shù)。同時內(nèi)核還提供了一組與上述操作對應的非原子位操作函數(shù),名字前多兩下劃線。由于不保證原子性,因此速度可能執(zhí)行更快。
?
圖1.2 ???? 內(nèi)核中的位原子操作函數(shù)
下面筆者展示一個關(guān)于原子操作的具體例子,請注意加粗部分的內(nèi)容,它的作用是實現(xiàn)了設(shè)備只能被一個進程打開。配合注釋的內(nèi)容,應該不難理解,如圖1.3所示。
?
圖1.3 ???? 原子操作示例程序
下面給出筆者在討論關(guān)于原子操作的認為的一些比較重要的內(nèi)容:1、原子操作在不同體系架構(gòu)實現(xiàn)的方法不同,基本采用匯編實現(xiàn);2、上述的整數(shù)原子函數(shù)集僅針對32位,內(nèi)核中關(guān)于64位有另一套函數(shù)。3、對于SMP系統(tǒng),內(nèi)核還提供了local_t數(shù)據(jù)類型,實現(xiàn)對單個CPU的整數(shù)原子操作,接口函數(shù)僅將atomic_替換成local_即可,具體的定義可參見arch/x86/include/asm/local.h中定義。
接下來看它的實現(xiàn)核心,如圖1.4所示。鑒于篇幅的限制,中間關(guān)于SMP的匯編操作在這里省去,感興趣的讀者可參見具體的內(nèi)核源碼。
?
圖1.4 ???? 原子操作的實現(xiàn)核心
可以看到對于SMP系統(tǒng),它的實現(xiàn)核心是lock指令,而對于單CPU系統(tǒng)來說,則退化為空操作,因為對于單CPU來說,在某程序執(zhí)行期間,不可能有其它CPU來中斷它的執(zhí)行,因此,實際上,非SMP系統(tǒng)中的原子操作是沒有必要存在的。下面討論SMP系統(tǒng)。討論前,先了解x86中的lock指令。lock指令是一種前綴,它可與其他指令聯(lián)合,用來維持總線的鎖存信號直到與其聯(lián)合的指令執(zhí)行完為止。當CPU與其他處理機協(xié)同工作時,該指令可避免破壞有用信息。它對中斷沒有任何影響,因為中斷只能在指令之間產(chǎn)生。lock前綴的真正作用是保持對系統(tǒng)總線的控制,直到整條指令執(zhí)行完畢。
了解完lock指令的作用后,對于原子操作采用lock指令的原因就已經(jīng)很明顯。但需注意:lock指令只是針對自身CPU進行處理。lock指令在執(zhí)行中占用CPU資源,從硬件上考慮,多核之間要負責相互通信,要讓某個核的修改被其他核發(fā)現(xiàn),因此lock指令的過多使用必然降低系統(tǒng)的性能。
至此,關(guān)于原子操作的內(nèi)容基本上討論到這里。總結(jié)一下,對于原子操作,它的優(yōu)點就是簡單,但它的缺點也很明了,即是只能作計數(shù)操作,保護的東西太少,從它所提供的接口函數(shù)即可看出。
二、自旋鎖
接下來筆者將討論關(guān)于自旋鎖的內(nèi)容。它的定義可表述如下:某個進程在試圖加鎖的時候,若當前鎖已經(jīng)處于“鎖定”狀態(tài),試圖加鎖進程就進行不斷的“旋轉(zhuǎn)”,用一個死循環(huán)測試鎖的狀態(tài),直到成功的獲得鎖。它在內(nèi)核include\linux\spinlock_types.h中定義,核心的結(jié)構(gòu)體及成員如圖2.1所示。
圖2.1 ???? 自旋鎖核心結(jié)構(gòu)體及成員
下面首先看下自旋鎖提供了哪些函數(shù),依次定義include\linux\spinlock.h文件中,部分函數(shù)如圖2.2所示。
?
圖2.2 ???? 自旋鎖提供的部分接口函數(shù)
同樣配合的看個例子,這個例子其實就是對device_count變量的保護,例子如圖2.3所示,同樣需注意加粗部分。仔細研究這個例子對于后續(xù)了解順序鎖有很大幫助,到時讀者便會發(fā)現(xiàn)它其實是順序鎖的核心實現(xiàn)理念。
?
圖2.3 ???? 自旋鎖示例程序
上面關(guān)于自旋鎖的例子應該不難理解,下面讓我們深入自旋鎖加解鎖的核心源碼,進一步來看下它到底是怎么實現(xiàn)的。首先,對于單CPU來說,它的機制實際上就是禁止和使能搶占,下圖展示的是自旋鎖加鎖和解鎖在內(nèi)核中層層迭代的源碼,特別注意加粗部分內(nèi)容。深入琢磨下去,實際上這里牽扯到一個“引用計數(shù)器”的概念。它是內(nèi)存管理的一個技巧,可以看做C/C++中的一種垃圾回收機制,具體的內(nèi)容讀者可以去了解,這里不再細說。
?
圖2.4 ???? 自旋鎖單CPU的加鎖函數(shù)實現(xiàn)核心
以上展示的是一個內(nèi)核加鎖函數(shù)的源碼實現(xiàn)的過程,實際上對于解鎖也是這么一個過程。如圖2.5所示。
?
圖2.5 ???? 自旋鎖針對單CPU的實現(xiàn)源碼
總結(jié)來說,其實對于單CPU來說,其實就是很簡單的內(nèi)容,對于CPU存在內(nèi)核搶占機制的,將禁止內(nèi)核搶占,否則,退化為空操作。
對于SMP系統(tǒng)來說,它除了簡單的禁止或使能本CPU的搶占機制外,還做了一些另外的操作。通過源碼的搜索,我們可以發(fā)現(xiàn)它的實現(xiàn)核心其實是圖2.6中所展示的兩個函數(shù),采用AT&T匯編實現(xiàn)。看的挺復雜,但實際上分析起來還是很簡單的。
?
圖2.6 ???? 自旋鎖針對SMP系統(tǒng)的實現(xiàn)源碼
在這里,我們可以看到它真正實現(xiàn)了對于多進程的之間某個進程“自旋的情況”,看源碼前先記住幾條指令:”xaddw”, ”cmpb”, “movb”, “incb”,其中xaddw表示先交換源操作數(shù)和目的操作數(shù)的數(shù)值,然后兩個操作數(shù)再按字求和,最終結(jié)果保存在目的寄存器中;”cmpb”, “movb”, “incb”較為簡單,后續(xù)的b(byte)后綴表示按字節(jié)執(zhí)行這條指令。同時注意在Linux內(nèi)核中,采用的AT&T匯編格式,指令操作數(shù)的順序是先源后目的,而不是x86匯編中的先目的后源,如圖2.6中的“xaddw”匯編指令則是%1所代表的寄存器為目的寄存器,即lock->slock變量。下面我們看下具體的執(zhí)行過程,其中P1,P2表示系統(tǒng)中的兩個不同的進程,如圖2.7所描述。
?
圖2.7 ???? 自旋鎖內(nèi)核的執(zhí)行過程
到這里,讀者應該明白了到底自旋鎖是怎么實現(xiàn)自旋的。注意:對于“xaddw”它實際上完成了三條指令的事,為了防止被這個過程被打斷,所以加了LOCK_PREFIX宏,在前面的原子操作我們也看到了LOCK_PREFIX宏實際上是針對lock指令的包裝,當然是針對SMP系統(tǒng)。
當然,上述給出的源碼是最大只支持256個處理器的情況,對于操作256個處理器的時候,內(nèi)核中還有一套函數(shù)去處理,感興趣的可以去研究下。可能分析完源碼后有人會提出這個的疑問:如果P2和P3都在等待自旋鎖,Linux系統(tǒng)如何保證能夠正確的順序執(zhí)行呢?其實,這個在源碼中已經(jīng)體現(xiàn)出來了,實際上,考慮slcok的值,我們可以觀察到它實際上已經(jīng)保證了后續(xù)在等待自旋鎖的進程的順序執(zhí)行性,比如上述分析過程中我們得到P2的slcok=0x0201,假如P1還未釋放,P3又來申請自旋鎖,這時候,內(nèi)核經(jīng)過計算得到P3的slcok=0x0301。進而繼續(xù)分析源碼,我們可知P3要想執(zhí)行,必須得到P2執(zhí)行完畢后(此時slcok=0x0302),方可有條件(slcok經(jīng)過低8位加1后等于0x0303)申請到自旋鎖,從而無形中保證了申請自旋鎖進程的順序執(zhí)行性。由于slock只有高8位用于保證順序性,所以這段源碼最大只支持256個處理器同時申請自旋鎖。
另外說到自旋鎖,不得不提自旋鎖和中斷之間的關(guān)系,首先看一個雙重請求的例子,假如某一進程在臨界區(qū)正在執(zhí)行,然而這時候,突然有一個中斷來打斷了它,于是,在臨界區(qū)觸發(fā)了中斷處理程序,若中斷處理程序里面也有包含申請自旋鎖的操作,這將造成一個大問題,即所謂的雙重請求的例子。如下圖2.8所示。
?
圖2.8 ???? 雙重請求圖例
當然內(nèi)核當然考慮了此種情況,于是在自旋鎖中就有了關(guān)于關(guān)閉中斷的函數(shù):spin_lock_irq()和spin_unlock_irq()。如圖2.9代碼所示的函數(shù),但正如圖中所提的那樣,這兩個函數(shù)的使用是有條件的,它要求中斷在加鎖前必須是激活的。假如現(xiàn)在有一個進程,它的中斷本來就是關(guān)閉的,但是你通過這個鎖的過程后中斷變成開了,這不就又造成問題了嗎。考慮此種情況,內(nèi)核中提出了如圖2.10所展示的自旋鎖函數(shù):spin_lock_irqsave()和spin_unlock_irqrestore()。可能讀者在此會有些許疑惑,既然flags是作為spin_lock_irqsave()的輸出參數(shù),理論上應當要有”&”符號才對,這里卻沒有。事實上,spin_lock_irqsave()中flags不用“&”是因為這個函數(shù)是以宏形式定義的,一直嵌套,最終到arch\x86\include\asm\irqflags.h下,感興趣的搜索到本源。一般來說,若是輸出參數(shù)不帶”&”符號的函數(shù),幾乎都是采用宏形式的定義的,這點需注意。
? ? ? ?圖2.9? spin_lock_irq()的使用實例? ? ? ? ? ? ? ? ? ? ? ? ? ??圖2.10? spin_lock_irqsave()的使用實例
關(guān)于中斷下半部的問題,還有幾點需要說明。首先如果存在中斷下半部和某個進程之間存在數(shù)據(jù)共享的問題,那就需要注意一下,因為中斷下半部可搶占進程上下文,因此下半部和進程之間存在臨界區(qū)時除了加鎖,還需要禁止下半部執(zhí)行。內(nèi)核中提供的包括函數(shù)spin_lock_bh() 和spin_unlock_bh()即是實現(xiàn)禁止或使能下半部。同時關(guān)于自旋鎖中的中斷還有兩點要說明:① 若中斷處理程序與中斷下半部共享數(shù)據(jù),則對數(shù)據(jù)區(qū)加鎖的同時也要禁止中斷,因為中斷也是可搶占中斷下半部。② 若數(shù)據(jù)被軟中斷共享,也需要加鎖,因為在不同處理器上存在軟中斷同時執(zhí)行問題。
OK!上述討論了自旋鎖多方面的內(nèi)容,下面是對自旋鎖一番總結(jié)。首先從前面的實現(xiàn)機制上,讀者可以看到自旋鎖主要針對SMP系統(tǒng),而且它們存在搶占情況。對于單CPU系統(tǒng),自旋鎖的實現(xiàn)則退化為空操作。其次自旋鎖是忙等待,要求臨界區(qū)執(zhí)行時間短。再次自旋鎖可能引發(fā)死鎖,引發(fā)的情況有:自旋鎖的遞歸調(diào)用(雙重請求)或獲得自旋鎖后不釋放,最終將導致系統(tǒng)不可用。最后一點則是若自旋鎖在鎖定期間調(diào)用可能引發(fā)睡眠的函數(shù),如kmalloc()等,從此“一睡不醒”(因為這時候“無人”負責喚醒,主要原因是連中斷都被關(guān)閉了,從此在無人喚醒,除非重啟系統(tǒng))。這一點需特別注意。
至此,關(guān)于自旋鎖部分的內(nèi)容到此討論結(jié)束,讓我們跳出來觀全局,如圖2.11所示。其實抽象的看自旋鎖的實現(xiàn)機制還是挺簡單的,而它提供的關(guān)于中斷的一系列函數(shù)則為自旋鎖提供了“安全帶”的作用。
?
圖2.11 ?? 跳出來觀全局—自旋鎖實現(xiàn)機制
出于文章篇幅的限制,本篇博文到此結(jié)束,后續(xù)將會給出《大話Linux內(nèi)核中鎖機制之內(nèi)存屏障、讀寫自旋鎖及順序鎖》,感興趣的讀者可繼續(xù)閱讀后一篇博文。由于筆者水平所限,博文中難免有出錯之處,歡迎讀者指出,大家相互討論,共同進步。
轉(zhuǎn)載請注明出處:http://blog.sina.com.cn/huangjiadong19880706
【作者】張昺華 【出處】http://www.cnblogs.com/sky-heaven/ 【博客園】 http://www.cnblogs.com/sky-heaven/ 【新浪博客】 http://blog.sina.com.cn/u/2049150530 【知乎】 http://www.zhihu.com/people/zhang-bing-hua 【我的作品---旋轉(zhuǎn)倒立擺】 http://v.youku.com/v_show/id_XODM5NDAzNjQw.html?spm=a2hzp.8253869.0.0&from=y1.7-2 【我的作品---自平衡自動循跡車】 http://v.youku.com/v_show/id_XODM5MzYyNTIw.html?spm=a2hzp.8253869.0.0&from=y1.7-2 【新浪微博】 張昺華--sky 【twitter】 @sky2030_ 【facebook】 張昺華 zhangbinghua 本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權(quán)利.總結(jié)
以上是生活随笔為你收集整理的大话Linux内核中锁机制之原子操作、自旋锁【转】的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 给孩子一束安全的光 明基WiT Mind
- 下一篇: linux 其他常用命令