大話Linux內核中鎖機制之原子操作、自旋鎖【轉】

轉自:http://blog.sina.com.cn/s/blog_6d7fa49b01014q7p.html

多人會問這樣的問題,Linux內核中提供了各式各樣的同步鎖機制到底有何作用?追根到底其實是由于操作系統中存在多進程對共享資源的并發訪問,從而引起了進程間的競態。這其中包括了我們所熟知的SMP系統,多核間的相互競爭資源,單CPU之間的相互競爭,中斷和進程間的相互搶占等諸多問題。

通常情況下,如圖1所示,對于一段程序,我們的理想是總是美好的,希望它能夠這樣執行:進程1先對臨界區完成操作,然后進程2再去操作臨界區。但是往往現實總是殘酷的,進程1在執行過程中,進程2很可能在此插入一腳,導致兩個進程同時對臨界區進行讀寫訪問,讀是沒有問題,但寫的話問題就大了。這樣的話,得到的結果往往不是我們想要的。

?

圖1? 一個簡單的例子

因此,我們需要一些解決方法,在Linux內核中它提供了如下幾種鎖機制,供用戶在針對不同情況分別或配合使用,包括:原子操作、自旋鎖、內存屏障、讀寫自旋鎖、順序鎖、信號量、讀寫信號量、完成量、RCU機制、BKL(大內核鎖)等等,下面筆者將分五篇博文一一討論這些鎖機制。另外,本文所涉及的關于Linux內核源碼采用版本為:Linux 3.3.1。OK,讓我們首先討論有關原子操作和自旋鎖的相關內容吧。

一、原子操作

所謂的原子操作即是保證指令以原子的方式執行,它在執行過程中不被打斷。它包括了原子整數操作和原子位操作,在內核中分別定義于include\linux\types.h和arch\x86\include\asm\bitops.h。通常了解一個東西,我們是先了解它怎么用的,因此,我們先來看看內核提供給用戶的一些接口函數。對于整數原子操作函數,如下圖1.1所示,下述有關加法的操作在內核中均有相應的減法操作。

?

圖1.1 ???? 內核中的整數原子操作函數

如圖1.2展示的是內核中提供的一些主要位原子操作函數。同時內核還提供了一組與上述操作對應的非原子位操作函數,名字前多兩下劃線。由于不保證原子性,因此速度可能執行更快。

?

圖1.2 ???? 內核中的位原子操作函數

下面筆者展示一個關于原子操作的具體例子,請注意加粗部分的內容,它的作用是實現了設備只能被一個進程打開。配合注釋的內容,應該不難理解,如圖1.3所示。

?

圖1.3 ???? 原子操作示例程序

下面給出筆者在討論關于原子操作的認為的一些比較重要的內容:1、原子操作在不同體系架構實現的方法不同,基本采用匯編實現;2、上述的整數原子函數集僅針對32位,內核中關于64位有另一套函數。3、對于SMP系統,內核還提供了local_t數據類型,實現對單個CPU的整數原子操作,接口函數僅將atomic_替換成local_即可,具體的定義可參見arch/x86/include/asm/local.h中定義。

接下來看它的實現核心,如圖1.4所示。鑒于篇幅的限制,中間關于SMP的匯編操作在這里省去,感興趣的讀者可參見具體的內核源碼。

?

圖1.4 ???? 原子操作的實現核心

可以看到對于SMP系統,它的實現核心是lock指令,而對于單CPU系統來說,則退化為空操作,因為對于單CPU來說,在某程序執行期間,不可能有其它CPU來中斷它的執行,因此,實際上,非SMP系統中的原子操作是沒有必要存在的。下面討論SMP系統。討論前,先了解x86中的lock指令。lock指令是一種前綴,它可與其他指令聯合,用來維持總線的鎖存信號直到與其聯合的指令執行完為止。當CPU與其他處理機協同工作時,該指令可避免破壞有用信息。它對中斷沒有任何影響,因為中斷只能在指令之間產生。lock前綴的真正作用是保持對系統總線的控制,直到整條指令執行完畢。

了解完lock指令的作用后,對于原子操作采用lock指令的原因就已經很明顯。但需注意:lock指令只是針對自身CPU進行處理。lock指令在執行中占用CPU資源,從硬件上考慮,多核之間要負責相互通信,要讓某個核的修改被其他核發現,因此lock指令的過多使用必然降低系統的性能。

至此,關于原子操作的內容基本上討論到這里。總結一下,對于原子操作,它的優點就是簡單,但它的缺點也很明了,即是只能作計數操作,保護的東西太少,從它所提供的接口函數即可看出。

二、自旋鎖

接下來筆者將討論關于自旋鎖的內容。它的定義可表述如下:某個進程在試圖加鎖的時候,若當前鎖已經處于“鎖定”狀態,試圖加鎖進程就進行不斷的“旋轉”,用一個死循環測試鎖的狀態,直到成功的獲得鎖。它在內核include\linux\spinlock_types.h中定義,核心的結構體及成員如圖2.1所示。

圖2.1 ???? 自旋鎖核心結構體及成員

下面首先看下自旋鎖提供了哪些函數,依次定義include\linux\spinlock.h文件中,部分函數如圖2.2所示。

?

圖2.2 ???? 自旋鎖提供的部分接口函數

同樣配合的看個例子,這個例子其實就是對device_count變量的保護,例子如圖2.3所示,同樣需注意加粗部分。仔細研究這個例子對于后續了解順序鎖有很大幫助,到時讀者便會發現它其實是順序鎖的核心實現理念。

?

圖2.3 ???? 自旋鎖示例程序

上面關于自旋鎖的例子應該不難理解,下面讓我們深入自旋鎖加解鎖的核心源碼,進一步來看下它到底是怎么實現的。首先,對于單CPU來說,它的機制實際上就是禁止和使能搶占,下圖展示的是自旋鎖加鎖和解鎖在內核中層層迭代的源碼,特別注意加粗部分內容。深入琢磨下去,實際上這里牽扯到一個“引用計數器”的概念。它是內存管理的一個技巧,可以看做C/C++中的一種垃圾回收機制,具體的內容讀者可以去了解,這里不再細說。

?

圖2.4 ???? 自旋鎖單CPU的加鎖函數實現核心

以上展示的是一個內核加鎖函數的源碼實現的過程,實際上對于解鎖也是這么一個過程。如圖2.5所示。

?

圖2.5 ???? 自旋鎖針對單CPU的實現源碼

總結來說,其實對于單CPU來說,其實就是很簡單的內容,對于CPU存在內核搶占機制的,將禁止內核搶占,否則,退化為空操作。

對于SMP系統來說,它除了簡單的禁止或使能本CPU的搶占機制外,還做了一些另外的操作。通過源碼的搜索,我們可以發現它的實現核心其實是圖2.6中所展示的兩個函數,采用AT&T匯編實現。看的挺復雜,但實際上分析起來還是很簡單的。

?

圖2.6 ???? 自旋鎖針對SMP系統的實現源碼

在這里,我們可以看到它真正實現了對于多進程的之間某個進程“自旋的情況”,看源碼前先記住幾條指令:”xaddw”, ”cmpb”, “movb”, “incb”,其中xaddw表示先交換源操作數和目的操作數的數值,然后兩個操作數再按字求和,最終結果保存在目的寄存器中;”cmpb”, “movb”, “incb”較為簡單,后續的b(byte)后綴表示按字節執行這條指令。同時注意在Linux內核中,采用的AT&T匯編格式,指令操作數的順序是先源后目的,而不是x86匯編中的先目的后源,如圖2.6中的“xaddw”匯編指令則是%1所代表的寄存器為目的寄存器,即lock->slock變量。下面我們看下具體的執行過程,其中P1,P2表示系統中的兩個不同的進程,如圖2.7所描述。

?

圖2.7 ???? 自旋鎖內核的執行過程

到這里,讀者應該明白了到底自旋鎖是怎么實現自旋的。注意:對于“xaddw”它實際上完成了三條指令的事,為了防止被這個過程被打斷,所以加了LOCK_PREFIX宏,在前面的原子操作我們也看到了LOCK_PREFIX宏實際上是針對lock指令的包裝,當然是針對SMP系統。

當然,上述給出的源碼是最大只支持256個處理器的情況,對于操作256個處理器的時候,內核中還有一套函數去處理,感興趣的可以去研究下。可能分析完源碼后有人會提出這個的疑問:如果P2和P3都在等待自旋鎖,Linux系統如何保證能夠正確的順序執行呢?其實,這個在源碼中已經體現出來了,實際上,考慮slcok的值,我們可以觀察到它實際上已經保證了后續在等待自旋鎖的進程的順序執行性,比如上述分析過程中我們得到P2的slcok=0x0201,假如P1還未釋放,P3又來申請自旋鎖,這時候,內核經過計算得到P3的slcok=0x0301。進而繼續分析源碼,我們可知P3要想執行,必須得到P2執行完畢后(此時slcok=0x0302),方可有條件(slcok經過低8位加1后等于0x0303)申請到自旋鎖,從而無形中保證了申請自旋鎖進程的順序執行性。由于slock只有高8位用于保證順序性,所以這段源碼最大只支持256個處理器同時申請自旋鎖。

另外說到自旋鎖,不得不提自旋鎖和中斷之間的關系,首先看一個雙重請求的例子,假如某一進程在臨界區正在執行,然而這時候,突然有一個中斷來打斷了它,于是,在臨界區觸發了中斷處理程序,若中斷處理程序里面也有包含申請自旋鎖的操作,這將造成一個大問題,即所謂的雙重請求的例子。如下圖2.8所示。

?

圖2.8 ???? 雙重請求圖例

當然內核當然考慮了此種情況,于是在自旋鎖中就有了關于關閉中斷的函數:spin_lock_irq()和spin_unlock_irq()。如圖2.9代碼所示的函數,但正如圖中所提的那樣,這兩個函數的使用是有條件的,它要求中斷在加鎖前必須是激活的。假如現在有一個進程,它的中斷本來就是關閉的,但是你通過這個鎖的過程后中斷變成開了,這不就又造成問題了嗎。考慮此種情況,內核中提出了如圖2.10所展示的自旋鎖函數:spin_lock_irqsave()和spin_unlock_irqrestore()。可能讀者在此會有些許疑惑,既然flags是作為spin_lock_irqsave()的輸出參數,理論上應當要有”&”符號才對,這里卻沒有。事實上,spin_lock_irqsave()中flags不用“&”是因為這個函數是以宏形式定義的,一直嵌套,最終到arch\x86\include\asm\irqflags.h下,感興趣的搜索到本源。一般來說,若是輸出參數不帶”&”符號的函數,幾乎都是采用宏形式的定義的,這點需注意。

? ? ? ?圖2.9? spin_lock_irq()的使用實例? ? ? ? ? ? ? ? ? ? ? ? ? ??圖2.10? spin_lock_irqsave()的使用實例

關于中斷下半部的問題,還有幾點需要說明。首先如果存在中斷下半部和某個進程之間存在數據共享的問題,那就需要注意一下,因為中斷下半部可搶占進程上下文,因此下半部和進程之間存在臨界區時除了加鎖,還需要禁止下半部執行。內核中提供的包括函數spin_lock_bh() 和spin_unlock_bh()即是實現禁止或使能下半部。同時關于自旋鎖中的中斷還有兩點要說明:① 若中斷處理程序與中斷下半部共享數據,則對數據區加鎖的同時也要禁止中斷,因為中斷也是可搶占中斷下半部。② 若數據被軟中斷共享,也需要加鎖,因為在不同處理器上存在軟中斷同時執行問題。

OK!上述討論了自旋鎖多方面的內容,下面是對自旋鎖一番總結。首先從前面的實現機制上,讀者可以看到自旋鎖主要針對SMP系統,而且它們存在搶占情況。對于單CPU系統,自旋鎖的實現則退化為空操作。其次自旋鎖是忙等待,要求臨界區執行時間短。再次自旋鎖可能引發死鎖,引發的情況有:自旋鎖的遞歸調用(雙重請求)或獲得自旋鎖后不釋放,最終將導致系統不可用。最后一點則是若自旋鎖在鎖定期間調用可能引發睡眠的函數,如kmalloc()等,從此“一睡不醒”(因為這時候“無人”負責喚醒,主要原因是連中斷都被關閉了,從此在無人喚醒,除非重啟系統)。這一點需特別注意。

至此,關于自旋鎖部分的內容到此討論結束,讓我們跳出來觀全局,如圖2.11所示。其實抽象的看自旋鎖的實現機制還是挺簡單的,而它提供的關于中斷的一系列函數則為自旋鎖提供了“安全帶”的作用。

?

圖2.11 ?? 跳出來觀全局—自旋鎖實現機制

出于文章篇幅的限制,本篇博文到此結束,后續將會給出《大話Linux內核中鎖機制之內存屏障、讀寫自旋鎖及順序鎖》,感興趣的讀者可繼續閱讀后一篇博文。由于筆者水平所限,博文中難免有出錯之處,歡迎讀者指出,大家相互討論,共同進步。

轉載請注明出處: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
【我的作品---旋轉倒立擺】 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
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利.

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/540978.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/540978.shtml
英文地址,請注明出處:http://en.pswp.cn/news/540978.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

hashmap put方法_Java HashMap put()方法與示例

hashmap put方法HashMap類的put()方法 (HashMap Class put() method) put() method is available in java.util package. put()方法在java.util包中可用。 put() method is used to link the given value element with the given key element in this HashMap. put()方法用于在…

java中jdom,java – JDOM中的命名空間(默認)

我正在嘗試使用最新的JDOM包生成XML文檔.我遇到了根元素和命名空間的問題.我需要生成這個根元素:xmlns"http://www.energystar.gov/manageBldgs/req"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://www.…

java enummap_Java EnumMap get()方法與示例

java enummapEnumMap類的get()方法 (EnumMap Class get() method) get() method is available in java.util package. get()方法在java.util包中可用。 get() method is used to get the value mapped with the given key element (key_ele) otherwise it returns null when no…

java后臺json傳遞,后臺json傳遞

json除了可以用于前臺傳遞,還可用于后臺之間傳遞。它可以傳遞List,Map,Bean等類型的數據。例如:User u1new User();u1.setUsername("zy");u1.setPassword("123");User u2new User();u2.setUsername("msl");u2.setPassword…

專家呼吁建安全漏洞信息共享機制并強化管控

近日,由中國網絡空間安全協會主辦,中國網絡空間安全協會網絡空間安全法律與公共政策專業委員會、北京郵電大學互聯網治理與法律研究中心、公安部第三研究所網絡安全法律研究中心、西安交通大學信息安全法律研究中心承辦的《網絡安全法(草案二…

java的equals方法_Java Date equals()方法與示例

java的equals方法日期類equals()方法 (Date Class equals() method) equals() method is available in java.util package. equals()方法在java.util包中可用。 equals() method is used to check whether this date and the given object (o) are equals or not. equals()方法…

lnmp解析php,LNMP之 php解析

[rootLNMP ~]# vim /usr/local/nginx/conf/nginx.conf打開以下PHP 相關項且更改 scripts$fastcgi_script_name;> /usrlocal/nginx/html$fastcgi_script_name;location ~ \.php$ {root html;fastcgi_pass 127.0.0.1:9000;fastcgi_index index.php;fastcgi_param…

spring歷史背景

1.2004年spring出現第一版本spring frameworl1.0 2.寫代碼永遠是最簡單的,后續的運維工作才是讓人感到無助的 3.spring boot在運維方面做了很多工作,部署,監控,度量。結合spring cloud還可以實現服務發現,服務降級等功…

計算機網絡中的傳輸協議是_計算機網絡中的傳輸方式

計算機網絡中的傳輸協議是傳輸方式 (Transmission Modes) The mechanism of transferring data or information between two linked devices connected over a network is referred to as Transmission Modes. 在通過網絡連接的兩個鏈接的設備之間傳輸數據或信息的機制稱為傳輸…

https 密鑰 php,https加密方式是什么

Https加密介紹Http直接通過明文在瀏覽器和服務器之間傳遞消息,容易被監聽抓取到通信內容。Https采用對稱加密和非對稱加密結合的方式來進行通信。Https不是應用層的新協議,而是Http通信接口用SSL和TLS來加強加密和認證機制。加密方式對稱加密&#xff1a…

一個前端框架應該有的一些公共函數

一、防止ie瀏覽器按backspace回退頁面 //防止后退返回頁面,如果非文本框、密碼框、文本域控件,或控件非可用裝填,則禁用后退按鍵 var uanavigator.userAgent.toLowerCase(); var isIEua.indexOf("msie")>-1; window.document.onkeydown fu…

Kruskal(P)和Prim(K)算法

最小生成樹 (Minimum Spanning Tree) An MST is a subset of the edges of the connected, undirected graph that connect all the vertices together, in which there is no forming of a cycle and there should be minimum possible total edge weight. MST是已連接的無向圖…

java get post 注解,GET/POST接收或發送數據的問題

在文章開始,先來回憶一下GET、POST這兩種請求方式的區別。?Http定義了與服務器交互的不同方法,最基本的方法有4種,分別是GET,POST,PUT,DELETE。URL全稱是資源描述符,我們可以這樣認為&#xff…

mybatis中sql語句傳入多個參數方法

1 使用map <select id"selectRole" parameterType"map" resultType"RoleMap">SELECT id, roleName, noteFROM roleWHERE roleName LIKE Concat(%,#{roleName},%)and note like Concat(%,#{note},%)</select> 在接口中如下定義 List&…

kotlin半生對象_Kotlin程序| 隨播對象特征

kotlin半生對象伴侶對象 (Companion object) If you need a function or a property to be tied to a class rather than to instances of it (similar to static in java), you can declare it inside a companion object: 如果需要將函數或屬性綁定到類而不是實例(類似于java…

mysql安裝注意步驟,mysql安裝步驟

mysql安裝步驟1、在官網下載對應的壓縮文件&#xff0c;放到本地文件夾下&#xff0c;解壓縮。2、配置Path環境變量&#xff1a;新增mysql的bin文件夾路徑&#xff0c;C:\software\mysql-8.0.23-winx64\bin。3、在mysql根目錄下新增my.ini配置文件。內容如下&#xff0c;basedi…

maven插件介紹之tomcat7-maven-plugin

tomcat7-maven-plugin插件的pom.xml依賴為&#xff1a; <dependency><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.2</version> </dependency>一&#xff1a;直接執行…

在Python中模擬do-while循環

Python as a language doesnt support the do-while loop. However, we can have a workaround to emulate the do-while loop. Python作為一種語言不支持do-while循環。 但是&#xff0c;我們可以采用一種變通方法來模擬do-while循環 。 The syntax for do-while is as follo…

織夢cms生成首頁html的php文件,織夢DedeCMS定時自動生成首頁HTML的實現方法

只需要制作一個文件然后在首頁模板添加一句代碼就可以實現讓織夢DedeCMS自動生成首頁html&#xff0c;具體方法如下&#xff1a;第一步、需要在首頁調用隨機文章&#xff0c;這樣每次自動更新才會有更新的效果&#xff0c;隨機文章調用標簽如下&#xff1a;{dede:arclist sortr…

Linux下安裝Flume

1 下載Flume Welcome to Apache Flume — Apache Flume 下載1.9.0版本 2 上傳服務器并解壓安裝 3 刪除lib目錄下的guava-11.0.2.jar &#xff08;如同服務器安裝了hadoop&#xff0c;則刪除&#xff0c;如沒有安裝hadoop則保留這個文件&#xff0c;否則無法啟動flume&#…