电竞比分网-中国电竞赛事及体育赛事平台

分享

內(nèi)核中的調(diào)度與同步

 昵稱3140331 2010-09-04
 

摘要

本章將為大家介紹內(nèi)核中存在的各種任務(wù)調(diào)度機(jī)理以及它們之間的邏輯關(guān)系(這里將覆蓋進(jìn)程調(diào)度、推后執(zhí)行、中斷等概念),在此基礎(chǔ)上向大家解釋內(nèi)核中需要同步保護(hù)的根本原因和保護(hù)方法。最后提供一個(gè)內(nèi)核共享鏈表同步訪問的例子,幫助大家理解內(nèi)核編程中的同步問題。

內(nèi)核任務(wù)調(diào)度與同步關(guān)系引言

    對(duì)于從事應(yīng)用程序開發(fā)的朋友來(lái)說(shuō),用戶空間的任務(wù)調(diào)度與同步之間的關(guān)系相對(duì)簡(jiǎn)單,無(wú)需過(guò)多考慮需要同步的原因。這一是因?yàn)樵谟脩艨臻g中各個(gè)進(jìn)程都擁有獨(dú)立的運(yùn)行空間,進(jìn)程內(nèi)部的數(shù)據(jù)對(duì)外不可見,所以各個(gè)進(jìn)程即使并發(fā)執(zhí)行也不會(huì)產(chǎn)生對(duì)數(shù)據(jù)訪問的競(jìng)爭(zhēng)。第二是因?yàn)橛脩艨臻g與內(nèi)核空間獨(dú)立,所以用戶進(jìn)程不會(huì)與內(nèi)核任務(wù)交錯(cuò)執(zhí)行,因此用戶進(jìn)程不存在與內(nèi)核任務(wù)并發(fā)的可能。以上兩個(gè)原因使得用戶同步僅僅需要在進(jìn)程間通訊和多線程編程時(shí)需要考慮。
   但是在內(nèi)核空間中情況要復(fù)雜得多,需要考慮同步的原因大大增加了。這是因?yàn)閮?nèi)核空間中的共享數(shù)據(jù)對(duì)內(nèi)核中的所有任務(wù)可見,所以當(dāng)在內(nèi)核中訪問數(shù)據(jù)時(shí),就必須考慮是否會(huì)有其他內(nèi)核任務(wù)并發(fā)訪問的可能、是否會(huì)產(chǎn)生競(jìng)爭(zhēng)條件、是否需要對(duì)數(shù)據(jù)同步。而內(nèi)核并發(fā)的“罪魁禍?zhǔn)?#8221;便是內(nèi)核中復(fù)雜多變的任務(wù)調(diào)度——這里的任務(wù)調(diào)度包含所有可能引起內(nèi)核任務(wù)更換的情況。
   并發(fā),競(jìng)爭(zhēng)和同步的概念,我們假定大家都有所了解,本文不再重申。下面一段描述了上述幾個(gè)概念之間的大致關(guān)系,這種關(guān)系在內(nèi)核中同樣適用。
對(duì)于多線程程序的開發(fā)者來(lái)說(shuō),往往會(huì)利用多線程訪問共享數(shù)據(jù),避免繁瑣的進(jìn)程間通訊。但是多線程對(duì)共享數(shù)據(jù)的并發(fā)訪問有可能產(chǎn)生競(jìng)爭(zhēng),使得數(shù)據(jù)處于不一致狀態(tài),所以需要一些同步方法來(lái)保護(hù)共享數(shù)據(jù)。多線程的并發(fā)執(zhí)行是由于線程被搶占式的調(diào)度——一個(gè)線程在對(duì)共享數(shù)據(jù)訪問期間(還未完成)被調(diào)度程序中斷,將另一個(gè)線程投入運(yùn)行——如果新被調(diào)度的線程也要對(duì)這個(gè)共享數(shù)據(jù)進(jìn)行訪問,就將產(chǎn)生競(jìng)爭(zhēng)。為了避免競(jìng)爭(zhēng)產(chǎn)生,需要使線程串行地訪問共享數(shù)據(jù) ,也就是說(shuō)訪問需要同步——在一方對(duì)數(shù)據(jù)訪問結(jié)束后,另一方才能對(duì)同一數(shù)據(jù)進(jìn)行訪問。

內(nèi)核任務(wù)

   這里所定義的內(nèi)核任務(wù)是指內(nèi)核中執(zhí)行的一切活動(dòng)對(duì)象,每個(gè)內(nèi)核任務(wù)都擁有一個(gè)獨(dú)立的程序計(jì)數(shù)器、棧和一組寄存器。更重要的是,它們都屬于內(nèi)核調(diào)度(這里的調(diào)度是廣義上的,不要與進(jìn)程調(diào)度混淆)對(duì)象,也就是說(shuō)它們是可以在內(nèi)核中交錯(cuò)執(zhí)行的。

內(nèi)核任務(wù)分類

   內(nèi)核任務(wù)包含“內(nèi)核線程”、“系統(tǒng)調(diào)用”、“硬件中斷”、“半底任務(wù)”等幾類。下來(lái)我們就簡(jiǎn)要地討論上述幾類內(nèi)核任務(wù)的特點(diǎn)。
 

系統(tǒng)調(diào)用

    系統(tǒng)調(diào)用是用戶程序通過(guò)門機(jī)制來(lái)進(jìn)入內(nèi)核執(zhí)行的內(nèi)核例程,它運(yùn)行在內(nèi)核態(tài),處于進(jìn)程上下文中(進(jìn)程上下文包括進(jìn)程的堆棧等等環(huán)境),可以認(rèn)為是代表用戶進(jìn)程的內(nèi)核任務(wù),因此具有用戶態(tài)任務(wù)的特性,比如可以執(zhí)行進(jìn)程調(diào)度程序(schedule())、可以睡眠、可以訪問當(dāng)前進(jìn)程數(shù)據(jù)(通過(guò)current)。但它屬于內(nèi)核任務(wù),所以在執(zhí)行過(guò)程中不能被搶占(2.6內(nèi)核前),只能自己放棄cpu(睡眠)時(shí),系統(tǒng)才能可能重新調(diào)度別的任務(wù)。(有關(guān)系統(tǒng)調(diào)用部分請(qǐng)看《系統(tǒng)調(diào)用》一章)

硬中斷任務(wù)

    硬中斷是指那些由處理器以外的外設(shè)產(chǎn)生的中斷,這些中斷被處理器接收后交給內(nèi)核中的中斷處理程序處理。要注意的是:第一, 硬中斷是異步產(chǎn)生的,中斷發(fā)生后立刻得到處理,也就是說(shuō)中斷操作可以搶占內(nèi)核中正在運(yùn)行的代碼。這點(diǎn)非常重要。第二,中斷操作是發(fā)生在中斷上下文中的(所謂中斷上下文指的是和任何進(jìn)程無(wú)關(guān)的上下文環(huán)境)。中斷上下文中不可以使用進(jìn)程相關(guān)的資源,也不能夠進(jìn)行調(diào)度或睡眠。因?yàn)檎{(diào)度會(huì)引起睡眠,但睡眠必須針對(duì)進(jìn)程而言(睡眠其實(shí)是標(biāo)記進(jìn)程狀態(tài),然后把當(dāng)前進(jìn)程推入睡眠列隊(duì)),而異步發(fā)生的中斷處理程序根本不知道當(dāng)前進(jìn)程的任何信息,也不關(guān)心當(dāng)前哪個(gè)進(jìn)程在運(yùn)行,它完全是個(gè)過(guò)客。(有關(guān)硬件中斷部分請(qǐng)看《硬件中斷》一章)

下半底任務(wù)

    半底的來(lái)歷完全出自上面提到的硬中斷的影響。硬件中斷任務(wù)(處理程序)是一個(gè)快速、異步、簡(jiǎn)單地對(duì)硬件做出迅速響應(yīng)并在最短時(shí)間內(nèi)完成必要操作的中斷處理程序。硬中斷處理程序可以搶占內(nèi)核任務(wù)并且執(zhí)行時(shí)還會(huì)屏蔽同級(jí)中斷或其它中斷,因此中斷處理必須要快、不能阻塞。這樣一來(lái)對(duì)于一些要求處理過(guò)程比較復(fù)雜的任務(wù)就不合適在中斷任務(wù)中一次處理。比如,網(wǎng)卡接收數(shù)據(jù)的過(guò)程中,首先網(wǎng)卡發(fā)送中斷信號(hào)告訴CPU來(lái)取數(shù)據(jù),然后系統(tǒng)從網(wǎng)卡中讀取數(shù)據(jù)存入系統(tǒng)緩沖區(qū)中,再下來(lái)解析數(shù)據(jù)然后送入應(yīng)用層。這些如果都讓中斷處理程序來(lái)處理顯然過(guò)程太長(zhǎng),造成新來(lái)的中斷丟失。因此Linux開發(fā)人員將這種任務(wù)分割為兩個(gè)部分,一個(gè)叫上底,即中斷處理程序,短平快地處理與硬件相關(guān)的操作(如從網(wǎng)卡讀數(shù)據(jù)到系統(tǒng)緩存);而把對(duì)時(shí)間要求相對(duì)寬松的任務(wù)(如解析數(shù)據(jù)的工作)放在另一個(gè)部分執(zhí)行,這個(gè)部分就是我們這里要講的下半底。
   下半底是一種推后執(zhí)行任務(wù),它將某些不那么緊迫的任務(wù)推遲到系統(tǒng)更方便的時(shí)刻運(yùn)行。內(nèi)核中實(shí)現(xiàn)下半底的手段經(jīng)過(guò)不斷演化,目前已經(jīng)從最原始的BH(bottom thalf)演生出BH、任務(wù)隊(duì)列(Task queues)、軟中斷(Softirq)、Tasklet、工作隊(duì)列(Work queues)(2.6內(nèi)核中新出現(xiàn)的)。下面我們就介紹一下他們各自的特點(diǎn)。
            
軟中斷操作
   軟中斷(softirq)不象硬中斷那樣是由硬件中斷信號(hào)觸發(fā)執(zhí)行的,所以也不同于硬件中斷那樣時(shí)隨時(shí)都能夠被執(zhí)行,籠統(tǒng)來(lái)講,軟中斷會(huì)在內(nèi)核處理任務(wù)完畢后返回用戶級(jí)程序前得到處理機(jī)會(huì)。具體的講,有三個(gè)時(shí)刻它將被執(zhí)行(do_softirq()):硬件中斷操作完成后;系統(tǒng)調(diào)用返回時(shí);內(nèi)核調(diào)度程序中;(另外,內(nèi)核線程ksoftirqd周期執(zhí)行軟中斷)從中可以看出軟中斷會(huì)緊隨硬中斷處理(好象狐假虎威),所以搶占內(nèi)核任務(wù)——至少在時(shí)鐘中斷后總有機(jī)會(huì)運(yùn)行一次。還要記得軟中斷可以在不同處器上并發(fā)執(zhí)行。
在有對(duì)稱多處理器的機(jī)器上,那么兩個(gè)任務(wù)就可以真正的在臨界區(qū)中同時(shí)執(zhí)行了,這種類型被稱為真并發(fā)。相對(duì)而言在,單處理器上并發(fā)其實(shí)并不是真的同時(shí)發(fā)生,而是相互交錯(cuò)執(zhí)行,是偽并發(fā)。但它們都同樣會(huì)造成競(jìng)爭(zhēng)條件,而且也需要同樣的保護(hù)。
   軟中斷是很底層的機(jī)制,一般除了在網(wǎng)絡(luò)子系統(tǒng)和SCSI子系統(tǒng)這樣對(duì)性能要求很高以及要求并發(fā)處理的時(shí)候,才會(huì)選擇使用軟中斷。軟中斷雖然靈活性高和效率高,但是你自己必須處理復(fù)雜的同步處理(因?yàn)樗稍诙嗵幚砥魃喜l(fā)),所以通常都不直接使用,而是作為支持Tasklet和BH的根本。
需要說(shuō)明的是,軟中斷的執(zhí)行也處于中斷上下文中,所以中斷上下文對(duì)它的限制是和硬中斷一樣的。
Tasklet
   Tasklet和bottom half都是建立在軟中斷之上的兩種延遲機(jī)制,其具體不同之處在于軟中斷是靜態(tài)分配的,而且同類軟中斷可以并發(fā)地在幾個(gè)CPU上運(yùn)行;Tasklet可以動(dòng)態(tài)分配,并且不同種類的Tasklets可以并發(fā)地在幾個(gè)CPU上運(yùn)行,但同類的tasklets 不可以;bottom half只能靜態(tài)分配,實(shí)質(zhì)上,下半部分是一個(gè)不能與其它下半部分并發(fā)執(zhí)行的高優(yōu)先級(jí)tasklet,即使它們類型不同,而且在不同CPU上運(yùn)行。Tasklet可以理解為軟中斷的派生,所以它的調(diào)度時(shí)機(jī)與軟中斷一致。
   對(duì)于內(nèi)核中需要延遲執(zhí)行的多數(shù)任務(wù)都可以利用tasklet來(lái)完成,由于同類tasklet本身已經(jīng)進(jìn)行了同步保護(hù),所以使用tasklet相比軟中斷要簡(jiǎn)單得多,而且效率也不錯(cuò)。
bottom half
   是 BH時(shí)最早的內(nèi)核延遲方法,它原始、簡(jiǎn)單且容易控制,因?yàn)樗械腂H處理程序都被嚴(yán)格地順序執(zhí)行——不允許任何兩個(gè)BH處理程序同時(shí)并發(fā)執(zhí)行,即使它們的類型不同也不可以,這樣一來(lái)BH執(zhí)行其間減少了許多同步保護(hù)。但是BH不得不被淘汰,因?yàn)樗?#8220;簡(jiǎn)便”犧牲了多處理器并發(fā)處理的高性能,等于一隊(duì)人過(guò)獨(dú)木橋那樣速度受到牽制。
任務(wù)隊(duì)列
   任務(wù)列隊(duì)是BH的替代品,來(lái)自BH,所以它的屬性也和BH相同。它的原意在于簡(jiǎn)化BH的操作接口,但它的隨意性(數(shù)量隨意、執(zhí)行時(shí)機(jī)隨意)卻給系統(tǒng)帶來(lái)了混亂,所以到今天已經(jīng)被工作隊(duì)列(在2.6內(nèi)核中)所取代。
不過(guò)在2.4內(nèi)核中任務(wù)隊(duì)列還是被大量應(yīng)用,尤其是調(diào)度隊(duì)列、定時(shí)器隊(duì)列和立即隊(duì)列等三種任務(wù)隊(duì)列(除了這三種系統(tǒng)已接管的特定任務(wù)隊(duì)列外,你自己也可隨心所欲的建立自己的任務(wù)隊(duì)列,當(dāng)然這時(shí)你要自己調(diào)度它)。調(diào)度隊(duì)列的任務(wù)會(huì)在每次進(jìn)程調(diào)度時(shí)得到處理,它是在進(jìn)程上下文中處理的;定時(shí)器隊(duì)列會(huì)在每次時(shí)鐘滴答時(shí)得到處理;立即隊(duì)列會(huì)在中斷返回或調(diào)度時(shí)獲得處理(所以處理最快),他們都是在中斷上下文中處理的。
   這些任務(wù)隊(duì)列在內(nèi)核內(nèi)由一個(gè)統(tǒng)一的內(nèi)核線程調(diào)度,該線程名為keventd,進(jìn)程號(hào)是2(2.4.18)。你可用ps命令查看到該進(jìn)程。

內(nèi)核線程

   內(nèi)核線程可以理解成在內(nèi)核中運(yùn)行的特殊進(jìn)程,它有自己的“進(jìn)程上下文”(借用調(diào)用它的用戶進(jìn)程的上下文),所以同樣被進(jìn)程調(diào)度程序調(diào)度,也可以睡眠——它和用戶進(jìn)程屬性何其相似,不同之處就在于內(nèi)核線程運(yùn)行于內(nèi)核空間,可訪問內(nèi)核數(shù)據(jù),運(yùn)行期間不能被搶占。
傳統(tǒng)的Unix系統(tǒng)把一些重要的任務(wù)委托給周期性執(zhí)行的進(jìn)程,這些任務(wù)包括刷新磁盤高速緩存,交換出不用的頁(yè)面,維護(hù)網(wǎng)絡(luò)鏈接等等。事實(shí)上,以嚴(yán)格線性的方式執(zhí)行這些任務(wù)的確效率不高,如果把他們放在后臺(tái)調(diào)度,不管是對(duì)它們的函數(shù)還是對(duì)終端用戶進(jìn)程都能得到較好地響應(yīng)。因?yàn)橐恍┫到y(tǒng)進(jìn)程只運(yùn)行在內(nèi)核態(tài),現(xiàn)代操作系統(tǒng)把它們的函數(shù)委托給內(nèi)核線程(Kernel Thread),內(nèi)核線程不受不必要的用戶態(tài)上下文的拖累。

內(nèi)核中的同步

   內(nèi)核只要存在任務(wù)交錯(cuò)執(zhí)行,就必然會(huì)存在對(duì)共享數(shù)據(jù)的并發(fā)問題,也就必然存在對(duì)數(shù)據(jù)的保護(hù)。而內(nèi)核中任務(wù)交錯(cuò)執(zhí)行的原因歸根結(jié)底還是由于內(nèi)核任務(wù)調(diào)度造成的。我們下面歸納一下內(nèi)核中同步的原因。

同步原因

  • 中斷——中斷幾乎可以在任何時(shí)刻異步發(fā)生,也就可能隨時(shí)打斷當(dāng)前正在執(zhí)行的代碼。
  • 睡眠及與用戶空間的同步——在內(nèi)核執(zhí)行的進(jìn)程可能會(huì)睡眠,這就會(huì)喚醒調(diào)度程序,從而導(dǎo)致調(diào)度一個(gè)新的用戶進(jìn)程執(zhí)行。
  • 對(duì)稱多處理——兩個(gè)或多個(gè)處理器可以同時(shí)執(zhí)行代碼。
  • 內(nèi)核搶占——因?yàn)閮?nèi)核具有搶占性,所以內(nèi)核中的任務(wù)可能會(huì)被另一任務(wù)搶占(在2.6內(nèi)核引進(jìn)的新能力)。
   后兩種情況大大增加了內(nèi)核任務(wù)并發(fā)執(zhí)行的可能性,使得并發(fā)隨時(shí)隨刻都有可能發(fā)生,而且不可清晰預(yù)見,規(guī)律難尋。

內(nèi)核任務(wù)之間的并發(fā)關(guān)系

   上述內(nèi)核任務(wù)很多情況是可以交錯(cuò)執(zhí)行的——記住,一個(gè)下半部實(shí)際上可能在任何時(shí)候執(zhí)行,所以很有可能產(chǎn)生競(jìng)爭(zhēng)(都要訪問同一個(gè)數(shù)據(jù)結(jié)構(gòu)時(shí),就產(chǎn)生了競(jìng)爭(zhēng))。下面分析這些內(nèi)核任務(wù)之間有哪些可能的并發(fā)行為。
可以抽象出,程序(用戶態(tài)和內(nèi)核態(tài)一樣)并發(fā)執(zhí)行的總原因無(wú)非是正在運(yùn)行中的程序被其它程序搶占,所以我們必須看看內(nèi)核任務(wù)之間的搶占關(guān)系:
 
  • 中斷處理程序可以搶占內(nèi)核中的所有程序(當(dāng)沒有鎖保護(hù)時(shí)),包括軟中斷,tasklet,bottom half和系統(tǒng)的調(diào)用、內(nèi)核線程,甚至也包括硬中斷處理程序。也就是說(shuō)中斷處理程序可以和這些所有的內(nèi)核任務(wù)并發(fā)執(zhí)行,如果被搶占的程序和中斷處理程序都要訪問同一個(gè)資源,就必然有可能產(chǎn)生競(jìng)爭(zhēng)。
  • 軟件中斷也可以搶占內(nèi)核中的所有任務(wù),所以內(nèi)核代碼(比如,系統(tǒng)調(diào)用、內(nèi)核線程等)中有數(shù)據(jù)和軟中斷共享,就會(huì)有競(jìng)爭(zhēng)——除此外硬件中斷處理程序也有可能被軟中斷打斷,條件是硬中斷被其它硬中斷打斷,軟中斷隨即便獲得了執(zhí)行機(jī)會(huì),因?yàn)檐浿袛嗍歉谟仓袛嗪髨?zhí)行的。此外要注意的是,軟中斷即使是同種類型的也可以并發(fā)地運(yùn)行在不同處理器上,所以它們之間共享數(shù)據(jù)都會(huì)產(chǎn)生競(jìng)爭(zhēng)。(如果在同一個(gè)處理器上,軟中斷之間是不能相互搶占的)。
  • 同類的tasklet不可能同時(shí)運(yùn)行,所以對(duì)于同類tasklet之間是串行運(yùn)行的,他們不會(huì)產(chǎn)生并發(fā);但兩個(gè)不同種類的tasklet有可能在不同處理器上并發(fā)運(yùn)行,如果之間有數(shù)據(jù)共享就會(huì)產(chǎn)生競(jìng)爭(zhēng)(在同一個(gè)處理器上運(yùn)行的tasklet不發(fā)生相互搶占的情況)。
  • Bottom half 無(wú)論是否是同類的,即使在不同處理器上也都不能并發(fā)執(zhí)行,它是絕對(duì)串行化的,所以它們之間永遠(yuǎn)不能產(chǎn)生競(jìng)爭(zhēng)。任務(wù)列隊(duì)屬性基本同BH。
  • 系統(tǒng)調(diào)用和內(nèi)核線程這種運(yùn)行在進(jìn)程上下文中的內(nèi)核任務(wù)可能和各種內(nèi)核任務(wù)并發(fā),除了上面提到的中斷(軟,硬)來(lái)?yè)屨妓a(chǎn)生并發(fā)外,它也有可能自發(fā)性地主動(dòng)睡眠(比如在一些阻塞性的操作中),放棄處理器,重新調(diào)度其它任務(wù),所以系統(tǒng)調(diào)用和內(nèi)核線程除會(huì)與軟硬中斷(半底等)發(fā)生競(jìng)爭(zhēng),也會(huì)與其他(包括自己)系統(tǒng)調(diào)用與內(nèi)核線程發(fā)生競(jìng)爭(zhēng)。我們尤其要注意這種情況。
   注意:tasklet和bottom half是建立在軟中斷之上的,所以它們也都遵從軟中斷的調(diào)度規(guī)則——都可以打斷進(jìn)程上下文中的內(nèi)核代碼(系統(tǒng)調(diào)用),都可被硬中斷打斷——這些情況下都可能產(chǎn)生并發(fā)。

內(nèi)核同步措施

   為了避免并發(fā),防止競(jìng)爭(zhēng)。內(nèi)核提供了一組同步方法來(lái)提供對(duì)共享數(shù)據(jù)的保護(hù)。 我們的重點(diǎn)不是介紹這些方法的詳細(xì)用法,而是強(qiáng)調(diào)為什么使用這些方法和它們之間的差別。
    Linux使用的同步機(jī)制可以說(shuō)從2.0到2.6以來(lái)不斷發(fā)展完善。從最初的原子操作,到后來(lái)的信號(hào)量,從大內(nèi)核鎖到今天的自旋鎖。這些同步機(jī)制的發(fā)展伴隨    Linux從單處理器到對(duì)稱多處理器的過(guò)度;伴隨著從非搶占內(nèi)核到搶占內(nèi)核的過(guò)度。鎖機(jī)制越來(lái)越有效,也越來(lái)越復(fù)雜。
   目前來(lái)說(shuō)內(nèi)核中原子操作多用來(lái)做計(jì)數(shù)使用,其它情況最常用的是兩重鎖以及它們的變種,一個(gè)是自旋鎖,另一個(gè)是信號(hào)量。我們下面就來(lái)著重介紹一下這兩種鎖機(jī)制。

自旋鎖

   自旋鎖是專為防止多處理器并發(fā)而引入的一種鎖,它在內(nèi)核中大量應(yīng)用于中斷處理等部分(對(duì)于單處理器來(lái)說(shuō),防止中斷處理中的并發(fā)可簡(jiǎn)單采用關(guān)閉中斷的方式,不需要自旋鎖)。
   自旋鎖最多只能被一個(gè)內(nèi)核任務(wù)持有,如果一個(gè)內(nèi)核任務(wù)試圖請(qǐng)求一個(gè)已被爭(zhēng)用(已經(jīng)被持有)的自旋鎖,那么這個(gè)任務(wù)就會(huì)一直進(jìn)行忙循環(huán)——旋轉(zhuǎn)——等待鎖重新可用。要是鎖未被爭(zhēng)用,請(qǐng)求它的內(nèi)核任務(wù)便能立刻得到它并且繼續(xù)進(jìn)行。自旋鎖可以在任何時(shí)刻防止多于一個(gè)的內(nèi)核任務(wù)同時(shí)進(jìn)入臨界區(qū),因此這種鎖可有效地避免多處理器上并發(fā)運(yùn)行的內(nèi)核任務(wù)競(jìng)爭(zhēng)共享資源。
事實(shí)上,自旋鎖的初衷就是:在短期間內(nèi)進(jìn)行輕量級(jí)的鎖定。一個(gè)被爭(zhēng)用的自旋鎖使得請(qǐng)求它的線程在等待鎖重新可用的期間進(jìn)行自旋(特別浪費(fèi)處理器時(shí)間),所以自旋鎖不應(yīng)該被持有時(shí)間過(guò)長(zhǎng)。如果需要長(zhǎng)時(shí)間鎖定的話, 最好使用信號(hào)量。
   自旋鎖的基本形式如下:
   spin_lock(&mr_lock);
   /*臨界區(qū)*/
   spin_unlock(&mr_lock);
   因?yàn)樽孕i在同一時(shí)刻只能被最多一個(gè)內(nèi)核任務(wù)持有,所以一個(gè)時(shí)刻只有一個(gè)線程允許存在于臨界區(qū)中。這點(diǎn)很好地滿足了對(duì)稱多處理機(jī)器需要的鎖定服務(wù)。在單處理器上,自旋鎖僅僅當(dāng)作一個(gè)設(shè)置內(nèi)核搶占的開關(guān)。如果內(nèi)核搶占也不存在,那么自旋鎖會(huì)在編譯時(shí)被完全剔除出內(nèi)核。
   自旋鎖在內(nèi)核中有許多變種,如對(duì)bottom half 而言,可以使用spin_lock_bh()用來(lái)獲得特定鎖并且關(guān)閉半底執(zhí)行。相反的操作由spin_unlock_bh()來(lái)執(zhí)行;如果臨界區(qū)的訪問邏輯可以被清晰的分為讀和寫這種模式,那么可以使用讀者/寫者自旋鎖,調(diào)用形式為:
   讀者的代碼路徑:
   read_lock(&mr_rwlock);
   /*只讀臨界區(qū)*/
   read_unlock(&mr_rwlock);
   寫者的代碼路徑:
   write_lock(&mr_rwlock);
   /*讀寫臨界區(qū)*/
   write_unlock(&mr_rwlock);
   簡(jiǎn)單的說(shuō),自旋鎖在內(nèi)核中主要用來(lái)防止多處理器中并發(fā)訪問臨界區(qū),防止內(nèi)核搶占造成的競(jìng)爭(zhēng)。另外自旋鎖不允許任務(wù)睡眠(持有自旋鎖的任務(wù)睡眠會(huì)造成自死鎖——因?yàn)樗哂锌赡茉斐沙钟墟i的內(nèi)核任務(wù)被重新調(diào)度,而再次申請(qǐng)自己已持有的鎖),它能夠在中斷上下文中使用。
   死鎖:假設(shè)有一個(gè)或多個(gè)內(nèi)核任務(wù)和一個(gè)或多個(gè)資源,每個(gè)內(nèi)核都在等待其中的一個(gè)資源,但所有的資源都已經(jīng)被占用了。這便會(huì)發(fā)生所有內(nèi)核任務(wù)都在相互等待,但它們永遠(yuǎn)不會(huì)釋放已經(jīng)占有的資源,于是任何內(nèi)核任務(wù)都無(wú)法獲得所需要的資源,無(wú)法繼續(xù)運(yùn)行,這便意味著死鎖發(fā)生了。自死瑣是說(shuō)自己占有了某個(gè)資源,然后自己又申請(qǐng)自己已占有的資源,顯然不可能再獲得該資源,因此就自縛手腳了。

信號(hào)量

   Linux中的信號(hào)量是一種睡眠鎖。如果有一個(gè)任務(wù)試圖獲得一個(gè)已被持有的信號(hào)量時(shí),信號(hào)量會(huì)將其推入等待隊(duì)列,然后讓其睡眠。這時(shí)處理器獲得自由去執(zhí)行其它代碼。當(dāng)持有信號(hào)量的進(jìn)程將信號(hào)量釋放后,在等待隊(duì)列中的一個(gè)任務(wù)將被喚醒,從而便可以獲得這個(gè)信號(hào)量。
信號(hào)量的睡眠特性,使得信號(hào)量適用于鎖會(huì)被長(zhǎng)時(shí)間持有的情況;只能在進(jìn)程上下文中使用,因?yàn)橹袛嗌舷挛闹惺遣荒鼙徽{(diào)度的;另外當(dāng)代碼持有信號(hào)量時(shí),不可以再持有自旋鎖。
信號(hào)量基本使用形式為:
static DECLARE_MUTEX(mr_sem);//聲明互斥信號(hào)量
if(down_interruptible(&mr_sem))
/*可被中斷的睡眠,當(dāng)信號(hào)來(lái)到,睡眠的任務(wù)被喚醒 */
/*臨界區(qū)…*/
up(&mr_sem);
同自旋鎖一樣,信號(hào)量在內(nèi)核中也有許多變種,比如讀者-寫者信號(hào)量等,這里不再做介紹了。
 信號(hào)量和自旋鎖區(qū)別
   雖然聽起來(lái)兩者之間的使用條件復(fù)雜,其實(shí)在實(shí)際使用中信號(hào)量和自旋鎖并不易混淆。注意以下原則。
   如果代碼需要睡眠——這往往是發(fā)生在和用戶空間同步時(shí)——使用信號(hào)量是唯一的選擇。由于不受睡眠的限制,使用信號(hào)量通常來(lái)說(shuō)更加簡(jiǎn)單一些。如果需要在自旋鎖和信號(hào)量中作選擇,應(yīng)該取決于鎖被持有的時(shí)間長(zhǎng)短。理想情況是所有的鎖都應(yīng)該盡可能短的被持有,但是如果鎖的持有時(shí)間較長(zhǎng)的話,使用信號(hào)量是更好的選擇。另外,信號(hào)量不同于自旋鎖,它不會(huì)關(guān)閉內(nèi)核搶占,所以持有信號(hào)量的代碼可以被搶占。這意味者信號(hào)量不會(huì)對(duì)影響調(diào)度反應(yīng)時(shí)間帶來(lái)負(fù)面影響。
自旋鎖對(duì)信號(hào)量
―――――――――――――――――――――――――――――――
    需求                                              建議的加鎖方法
低開銷加鎖                                   優(yōu)先使用自旋鎖
短期鎖定                                       優(yōu)先使用自旋鎖
長(zhǎng)期加鎖                                            優(yōu)先使用信號(hào)量
中斷上下文中加鎖                              使用自旋鎖
持有鎖是需要睡眠、調(diào)度                    使用信號(hào)量
―――――――――――――――――――――――――――――――
引自 《Linux內(nèi)核開發(fā)》
防止并發(fā)的方式除了上面提到的外還有很多,我們不詳細(xì)介紹了。說(shuō)了這么多,希望大家認(rèn)識(shí)到,并發(fā)控制在內(nèi)核編程中是個(gè)特別難纏的問題,要駕御它必須清楚地認(rèn)識(shí)到內(nèi)核中各種任務(wù)的調(diào)度時(shí)機(jī)與特點(diǎn),并且在開發(fā)初期就應(yīng)特別小心保護(hù)共享數(shù)據(jù)(一切共享數(shù)據(jù)、一切能被別人看到的數(shù)據(jù)都要注意保護(hù)),別等到開發(fā)完成才去亡羊補(bǔ)牢。

并發(fā)控制實(shí)例

   我們下面給出一個(gè)多內(nèi)核任務(wù)訪問共享資源的具體例子,其中會(huì)用到上面提到的各種同步方法,希望能給大家一個(gè)形象的記憶。
   該例子的具體場(chǎng)景描述如下。
   我們主要的共享資源是鏈表(mine),操作它的內(nèi)核任務(wù)有三種:一個(gè)是100個(gè)內(nèi)核線程(sharelist),它們負(fù)責(zé)從表頭將新節(jié)點(diǎn)(struct my_struct)插入鏈表。二是定時(shí)器任務(wù)(qt_task),它負(fù)責(zé)每個(gè)時(shí)鐘滴答時(shí)從鏈表頭刪除一個(gè)節(jié)點(diǎn)。三是系統(tǒng)調(diào)用(由rmmod命令調(diào)用的share_exit),它負(fù)責(zé)銷毀鏈表并卸載模塊。
   我們利用模塊(sharelist.o)實(shí)現(xiàn)上述場(chǎng)景。加載模塊時(shí)會(huì)建立定時(shí)器任務(wù)列隊(duì),并將要執(zhí)行的任務(wù)(task.rounting=qt_task)插入定時(shí)器隊(duì)列(tq_timer),然后反復(fù)調(diào)度執(zhí)行(但別不停地執(zhí)行)。與此同時(shí)利用系統(tǒng)中的keventd內(nèi)核線程(它的目的是執(zhí)行任務(wù)隊(duì)列,由schedule_task激活,PID=2),創(chuàng)建100個(gè)內(nèi)核線程(創(chuàng)建函數(shù)kernel_thread)執(zhí)行插入鏈表的工作(由sharelist完成)——但當(dāng)鏈表長(zhǎng)度超過(guò)100時(shí),則從鏈表尾刪除節(jié)點(diǎn)。最后當(dāng)你需要卸載模塊時(shí),調(diào)用share_exit函數(shù)銷毀整個(gè)鏈表,并做一些諸如銷毀我們建立的內(nèi)核進(jìn)程的收尾工作。
   下面我們具體看看在程序中該如何保護(hù)我們的鏈表。上述場(chǎng)景中存在的內(nèi)核并發(fā)包括——內(nèi)核線程之間的并發(fā)、內(nèi)核任務(wù)與定時(shí)器任務(wù)的并發(fā)。要知道內(nèi)核線程執(zhí)行在進(jìn)程上下文中,而定時(shí)器任務(wù)屬于下半部分,執(zhí)行在中斷上下文中。在這兩部分交錯(cuò)執(zhí)行中進(jìn)行保護(hù)則需要采用自旋鎖。我們例子中使用了spin_lock_bh()鎖在內(nèi)核線程的執(zhí)行路徑中對(duì)鏈表進(jìn)行保護(hù);在下半部分,由于任務(wù)隊(duì)列是串行執(zhí)行并且不能被內(nèi)核任務(wù)或系統(tǒng)調(diào)用打斷,所以不必加鎖。另外在卸載模塊時(shí),刪除鏈表中仍然存在系統(tǒng)調(diào)用與下半部分的并發(fā)可能,因此也需要按上述方式加鎖。
    除了對(duì)共享鏈表訪問使用自旋鎖以外,還有兩個(gè)需要同步的地方,一是計(jì)數(shù)(count),該變量屬于原子類型,用于記錄鏈表接點(diǎn)的id。另外一個(gè)是利用信號(hào)量同步內(nèi)核創(chuàng)建線程,調(diào)度keventd后執(zhí)行被堵塞住(down),等內(nèi)核線程實(shí)際啟動(dòng)后, 才可繼續(xù)執(zhí)行(up)。下面的圖給出了任務(wù)的基本關(guān)系和對(duì)鏈表的操作。
 

結(jié)束

    并發(fā)的發(fā)生隨處都有,但是由它引起的錯(cuò)誤可并非每次都有,因?yàn)椴l(fā)過(guò)程中引起錯(cuò)誤的地方往往就一兩步,因此交錯(cuò)執(zhí)行這一兩步要靠“運(yùn)氣”,出錯(cuò)的幾率有時(shí)很小。但是一旦發(fā)生后果都是災(zāi)難性的,比如宕機(jī),破壞數(shù)據(jù)完整性等。所以我們對(duì)并發(fā)絕不能掉以輕心,必須拿出“把紙老虎當(dāng)真老虎的”決心來(lái)對(duì)待一切內(nèi)核代碼中可能的并發(fā),即便在單處理器上編程也需要考慮到移植到多處理器的情況,總之一切都要謹(jǐn)慎小心。
 
實(shí)例代碼見原文

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多