系統(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í)例代碼見原文