原文地址: http://hi.baidu.com/_kouu/blog/item/c7f1bcd864bb76f939012f9f.html
Big Kernel Lock(
BKL
)(大內核鎖),是linux內核中使用到的一種鎖,它跟普通的鎖原理上的一樣的:
lock_kernel();
/* 臨界區(qū) */
unlock_kernel();
但是它又有一些非常詭異的地方。從表面上看:
1、BKL是一個全局的鎖
(注意,是“一個”而不是“一種”),它保護所有使用它來同步的臨界區(qū)。一旦一個進程獲得BKL,進入被它保護的臨界區(qū),不但該臨界區(qū)被上鎖,所有被它保護的臨界區(qū)都一起被鎖住。
這看起來非常之武斷:進程A在CPU_1上操作鏈表list_a,而進程B在CPU_2上操作全局變量var_b,這兩者本身毫無瓜葛。但如果你使用了BKL,它們就要“被同步”。
2、BKL是遞歸鎖。
同一進程中可以對BKL嵌套的上鎖和解鎖,當解鎖次數(shù)等于上鎖次數(shù)時,鎖才真正被釋放。
這一點雖然跟內核中的其他鎖不大一樣,但倒也不算神奇,用戶態(tài)的pthread_mutex也支持這樣的遞歸鎖。
3
、BKL有自動釋放的特性。
在CPU_N上,如果當前進程A持有BKL,則當CPU_N上面發(fā)生調度時,進程A持有的BKL將被自動釋放。而當進程A再次被調度執(zhí)行時,它又自動獲得BKL(如果BKL正在被其他進程持有,則進程A不會被調度執(zhí)行)。
這個特性對于普通的用戶態(tài)程序來說實在是不可思議:進程A進入了臨界區(qū),要準備修改某全局鏈表list_a,但是由于某種原因而進入睡眠(比如系統(tǒng)內存緊缺時,等待分配內存),結果鎖就被自動釋放了。而另一個進程B就可以堂而皇之的獲得鎖而進入臨界區(qū),并且把全局鏈表list_a給改了。等進程A從睡眠中被喚醒的時候,就發(fā)現(xiàn)這個世界全變了……而鎖呢?竟然完全不起作用。
BKL的前兩個特性還好理解,第三個特性實在是匪夷所思。這么詭異的鎖是怎么來的呢?
百度一下“大內核鎖”可以了解到:據(jù)說在linux 2.0時代,內核是不支持SMP(對稱多處理器)的。在邁入linux 2.2時代的時候,SMP逐漸流行起來,內核需要對其進行支持了。但是發(fā)現(xiàn),內核中的很多代碼在多個CPU上同時運行的時候會存在問題。怎么辦呢?最完美的解決辦法當然是把所有存在問題的地方都找出來,然后給它們分別安排一個鎖。但是這樣做的話工作量會很大,為了快速支持SMP,linux內核出了狠招,這就是BKL:CPU進入內核態(tài)時就上BKL、退出內核態(tài)時釋放。于是,系統(tǒng)中同一時刻就只有一個CPU會處于內核態(tài),內核代碼就沒有了“在多個CPU上同時運行”的問題。
這一招很有效,但是顯然很拙劣,內核代碼不能在多個CPU上同時運行,SMP的優(yōu)勢大打折扣。于是后來的內核版本又逐步逐步的在削減被BKL所保護的臨界區(qū),以期把它們都消滅干凈。
類似上面的描述在網(wǎng)絡上比比皆是,但是似乎網(wǎng)絡上對于BKL的描述也僅限于此。看完這些,你對BKL是否感覺云里霧里的呢?反正我之前看到BKL的時候實在是想不通,感覺這些描述沒講到點子上。還有人說,BKL保護的是代碼,而不是數(shù)據(jù)(資源)。這個說法實在太抽象,抽象到說了等于沒說。(有什么代碼是需要保護的?如果不是因為代碼跟資源打交道,它還有被保護的必要?)
為什么要設計這樣一種鎖呢?對于一種特殊的、非傳統(tǒng)的機制,要理解為什么,要找到它存在的意義,最好的辦法莫過于:找出一個場景,在這個場景下使用這種特殊的機制比使用其他傳統(tǒng)的機制更有優(yōu)勢。比如,為什么要設計讀寫鎖?因為在讀寫分明的場景下,使用讀寫鎖可以避免讀讀沖突,減少程序的阻塞。再比如,為什么要設計seqlock(見《linux seqlock & rcu 淺析》)?因為在讀寫分明、且讀多寫少寫優(yōu)先的場景下,使用seqlock可以避免寫操作被阻塞。
那么BKL呢?它在什么場景下能帶來什么樣的優(yōu)勢呢?網(wǎng)絡上找不到這樣的描述。我試圖在內核代碼中尋找這樣的場景,但是完全找不到。似乎BKL的存在毫無道理,怎么看,把現(xiàn)有的BKL換成spinlock或者信號量,都是更好的選擇(這些鎖至少不是全局的)。
要想解釋BKL為什么會設計成這樣子,或許只能從它的誕生時需要解決的問題入手……
想一想,一個進程上了一個鎖,保護了一個臨界區(qū)。那么在鎖被釋放之前,其他進程都應該遵守規(guī)則,而不進入這個臨界區(qū)。這里的“其他進程”有兩層含義:
1、由于調度,
在同一CPU上交錯運行的其他進程
;
2、由于SMP,
在不同CPU上同時運行的其他進程
;
把“其他進程”劃分成這兩類,似乎有點畸形,但這也正是BKL畸形之所在。因為我們一般所說的同步、所用到的鎖都是針對所有“其他進程”的,不管是“在同一CPU上交錯運行的其他進程”,還是“在不同CPU上同時運行的其他進程”,都應該在被同步的范圍之內。
在SMP和BKL被引入內核之前,內核代碼是能夠正常運行的,這時候系統(tǒng)中只有一個CPU。內核中也有一些同步措施,它們的作用是同步“在同一CPU上交叉運行的其他進程”。
隨著SMP的引入,“在不同CPU上同時運行的其他進程”出現(xiàn)了,而BKL的作用就是(且僅僅是)解決它們的同步問題。于是,當發(fā)生進程調度的時候,內核自動把上一個進程持有的BKL釋放。因為BKL不關心“在同一CPU上交叉運行的其他進程”的同步問題,這些問題是由原有的那些機制去保證的。
前面說過,我們一般看到的鎖都是同時支持上面兩類“其他進程”的。而BKL只支持其中之一,這就是它不健全的地方。正是因為這種不健全,所以很難想象有什么應用場景是我們應該去使用BKL的。它并不是一個可以獨立使用的東西,它只是一個補丁,在它的背后必須有另一種同步機制的支持,這種機制必須足以應付“在同一CPU上交叉運行的其他進程”帶來的同步問題。
那么BKL為什么不能設計成跟普通的鎖一樣健全呢?因為,正是由于BKL的不健全,一個持有BKL的進程在進入睡眠之后,其它進程還可以持有BKL,還可以進入臨界區(qū)。如果它健全的話,其他進程都只有傻傻等待的份,SMP的優(yōu)勢將再打折扣。
再具體一點看看,當SMP引入內核之后,產(chǎn)生了兩個問題:
1、原先那些針對“在同一CPU上交叉運行的其他進程”的鎖需要升級,要支持“在不同CPU上同時運行的其他進程”。
這一點是顯而易見的,上面已經(jīng)討論過了,不能同時支持兩類“其他進程”的鎖是不健全的鎖。并且這個問題也很好解決,把鎖的實現(xiàn)改一改就好了,CPU必定會提供SMP環(huán)境下的相應的指令支持。
2、解決了第一個問題還不夠。有這樣一些地方,原先并不存在“在同一CPU上交叉運行的其他進程”的可能性,因此被認為不需要同步,或者說已經(jīng)隱含了這樣的同步關系。
在內核中,沒有內核搶占(暫時禁用或不支持)且與中斷處理程序沒有關系的代碼,在單CPU情況下,如果它不主動讓出CPU,那么就可以認為不會有其他進程與它交叉運行。
有了這樣的隱含的同步關系,也就不需要進行顯式的同步。但是SMP的出現(xiàn)打破了這一論斷,因為出現(xiàn)了“在不同CPU上同時運行的其他進程”,原本隱含的同步關系已經(jīng)不能代表同步關系的全部,所以,其中的一些地方可能還是需要顯式的進行同步。BKL就是針對這些地方而產(chǎn)生的。
總的說來,BKL就是為了在那些原本已經(jīng)隱含了單CPU下的同步關系的地方打一個補丁,以確保這些地方在SMP環(huán)境下也不會出現(xiàn)問題。反過來,不健全的BKL之所以能夠工作,是因為背后隱含了單CPU下的同步關系。
然而正如文章開頭說到的,這個問題是有完美解決方案的,把這些因為SMP而需要同步的地方都找出來,然后一一安排合適的同步機制(這些同步機制能同時解決單CPU和SMP下的同步問題)。BKL只不過是一種臨時方案。
由于BKL的不健全,邏輯上有很詭異的地方,內核代碼越來越復雜,牽涉到BKL的地方更是越來越難以維護。再加上BKL對SMP的限制較大。所以一直以來內核開發(fā)者們對BKL深惡痛絕,并且一再努力削減。(當然,請神容易送神難。大內核鎖的全局性和遞歸性使得調用它的代碼很難被理清。)
終于,BKL據(jù)說要在2.6.37版本的內核代碼中被完全消滅了。
更多文章、技術交流、商務合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
