一文解決信號量保護(hù)之位帶操作
CM3位帶操作
如果存儲器系統(tǒng)支持“鎖定傳送”( lockedtransfers),或者總線上只有一個主機(jī),還可以使用CM3的位帶功能來實現(xiàn)互斥鎖的操作。通過使用位帶,則可以在C程序中實現(xiàn)互斥鎖,但是操作過程與互斥訪問是不同的。在使用位帶來做資源分配的控制機(jī)制時,需要使用位帶存儲區(qū)的內(nèi)存單元(比如,一個字),該內(nèi)存單元的每個位表示資源正被特定的任務(wù)使用。在位帶別名區(qū)的讀寫實質(zhì)上是鎖定的“讀‐改‐寫”(在傳送期間總線不能被其它主機(jī)占有)。因此,只要每個任務(wù)都僅修改分配給它們自己的鎖定位,其它任務(wù)鎖定位的值就不會丟失,即使是兩個任務(wù)同時寫自己的鎖定位也不怕,如圖:

以兩個任務(wù)為例,看如下情況:(紅色箭頭代表跳轉(zhuǎn)執(zhí)行)

任務(wù)2首先讀取并判斷,之后因某種原因跳轉(zhuǎn)到任務(wù)1執(zhí)行讀取、判斷、設(shè)置操作,又因為某種原因在設(shè)置完之后又跳轉(zhuǎn)到任務(wù)2去進(jìn)行設(shè)置、讀取操作,后來又到任務(wù)1去執(zhí)行讀取并判斷操作,發(fā)現(xiàn)有其它位置1,則放棄該資源,然后當(dāng)運行任務(wù)2時,因為還是之前的數(shù)據(jù),所以認(rèn)為最終的判斷認(rèn)為有其它任務(wù)占用,也放棄該資源。最終的結(jié)果就是兩個任務(wù)都放棄了該資源。但這種情況很少見,因為這需要在幾條指令中來來回回跳轉(zhuǎn)才會發(fā)生這種情況,一般情況是整個操作流程只會被其它任務(wù)中斷一次。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ??


再來分析以下執(zhí)行流程,任務(wù)1在讀取完之后任務(wù)2設(shè)置了占用標(biāo)志,然后回到任務(wù)1判斷,結(jié)果就是任務(wù)1占用資源,即使后續(xù)又被任務(wù)2中斷了,任務(wù)2也是會讀取到任務(wù)1的占用標(biāo)志,從而放棄資源。

以下是中斷判斷操作(判斷未執(zhí)行)之后可能跳轉(zhuǎn)的位置,自行分析(關(guān)鍵在于設(shè)置)。

那么位帶別名操作避免的是什么呢?其實就是用于避免讀-改-寫的操作中因為中斷,導(dǎo)致其它任務(wù)設(shè)置的數(shù)據(jù)因為最后寫的步驟而丟失了。如下:

因為在設(shè)置的時候發(fā)生中斷,導(dǎo)致任務(wù)2設(shè)置,但是因為中斷的是設(shè)置操作,所以當(dāng)回到設(shè)置的時候,讀取的數(shù)據(jù)還是之前的數(shù)據(jù),此時再進(jìn)行設(shè)置操作必然導(dǎo)致任務(wù)2的設(shè)置操作丟失,并最終導(dǎo)致嚴(yán)重后果。
再深入思考后發(fā)現(xiàn)這些操作的關(guān)鍵在于你設(shè)置標(biāo)志后判斷你是否真的擁有該資源。那是否可以將前面的讀取判斷操作省去而直接去設(shè)置該標(biāo)志呢?我認(rèn)為是可以的,但是這里有一個缺點就是因為你不管有沒有其它任務(wù)設(shè)置標(biāo)志都自己去設(shè)置,一旦在有其他任務(wù)設(shè)置的情況下設(shè)置標(biāo)志,你就必須進(jìn)行清除自己標(biāo)志的操作,加上你之前設(shè)置的操作,明顯比先判斷之后再進(jìn)行設(shè)置操作效率更低。
那么是不是說如果沒有位帶操作就沒辦法進(jìn)行互斥訪問呢?當(dāng)然不是,簡單的方法是關(guān)中斷的方式,但通過先前的分析發(fā)現(xiàn),其實關(guān)鍵點在于別破壞其他設(shè)置的標(biāo)志即可,這樣我們可以把字節(jié)當(dāng)位來看也是可以的,你設(shè)置你的字節(jié)標(biāo)志,我設(shè)置我的字節(jié)標(biāo)志,互不干擾,一樣可以達(dá)到位帶操作的效果,只是空間占用更大一些罷了。但是單片機(jī)最大的讀取類型是double型數(shù)據(jù),在stm32中即為8字節(jié),也就是說可設(shè)置的最大任務(wù)數(shù)就是8個任務(wù),而位帶操作為8*8=64個任務(wù),但是是否可以采用多次判斷的方式增加任務(wù)數(shù)呢?比如兩個double組合使用時利用兩次判斷兩個變量的方法確定是否占用資源。這是一個解決方法,但是這個方法是否存在風(fēng)險呢?通過后面的分析其實可以得到答案。
再進(jìn)行深入思考之后,你就會發(fā)現(xiàn),最為關(guān)鍵的就是設(shè)置之后的“讀取”這個確認(rèn)操作,這個操作是整個資源鎖定操作的分水嶺。在整個操作流程中,不管哪個操作被中斷,然后被其它任務(wù)設(shè)置標(biāo)志位,關(guān)鍵都在讀取這一步,誰將變量讀取到寄存器時沒有其他任務(wù)設(shè)置標(biāo)志位中,誰就占用了資源。
兩個任務(wù)在宏觀上可以認(rèn)為在同時執(zhí)行,但是在執(zhí)行讀取操作的時候,如果兩個任務(wù)同時進(jìn)行到了這一步,必然有一個先后(不管誰先誰后,,只要對方?jīng)]有在你讀取前放棄占用,都會放棄占用,而如果說真存在同時讀取(兩個cpu)的情況,那么必然是同時放棄資源的結(jié)果)。既然已經(jīng)執(zhí)行到了讀取操作,那么就必然進(jìn)行了設(shè)置操作,也就是說兩個人同時設(shè)置了標(biāo)志位,都準(zhǔn)備占用資源,不管誰進(jìn)行讀取,最終的結(jié)果就是資源已經(jīng)被占用,之后就是放棄了,即使任務(wù)在在讀取后馬上被中斷判斷的操作,但卻不會影響該任務(wù)后續(xù)的判斷、放棄操作了,因為你讀取的數(shù)據(jù)已經(jīng)決定了該任務(wù)的所有后續(xù)操作,所以關(guān)鍵操作就是讀取操作,你讀取的到底是什么數(shù)據(jù)。
那么最糟糕的情況就如前面所說,兩個任務(wù)的在讀取之后都發(fā)現(xiàn)被其他任務(wù)占用,然后都放棄資源,然后我們再分析其他可能性,兩個任務(wù)的讀取的數(shù)據(jù)理論上有四種可能,11,10,01,00。但是實際上00是可以排除的,因為讀取之前必然已經(jīng)進(jìn)行了設(shè)置,那么除了
該任本身的標(biāo)志位外只有其他任務(wù)可能設(shè)置的標(biāo)志位了,即要么設(shè)置,要么沒設(shè)置。如果設(shè)置了,又分為兩種情況,如果另一個任務(wù)已經(jīng)放棄了占用,那么它就可以占用資源,如果該任務(wù)在讀取之前另一任務(wù)沒有放棄資源,那么就放棄資源。
用圖表示可能更清晰一些:

其實進(jìn)行深入分析之后可以發(fā)現(xiàn),就是之前所說的兩個關(guān)鍵操作它們的先后順序,而決定是否占用資源的關(guān)鍵操作就是讀取操作。誰先讀取到?jīng)]有其他任務(wù)占用的情況誰就占用了該資源。
現(xiàn)在再來考慮有沒有可能出現(xiàn)兩個任務(wù)讀取操作之后發(fā)現(xiàn)對方都沒請求占用資源而同時占用資源的情況?我們知道互斥操作為的就是避免這種情況而特意設(shè)定的操作流程,如果這種情況不能避免分析再多都沒用。事實上這種情況是不可能出現(xiàn)的,因為一個任務(wù)在讀取的時候這個任務(wù)已經(jīng)設(shè)置完本任務(wù)的標(biāo)志位了,一旦讀取之前另一個任務(wù)沒有設(shè)置標(biāo)志位,就算是占用了資源,然后另一任務(wù)在讀取的時候必然是有任務(wù)占用的結(jié)果,就會放棄占用,就不會存在這個任務(wù)讀取時沒任務(wù)占用,另一個任務(wù)讀取的時候也沒占用,因為同一時間只能有一個任務(wù)先進(jìn)行讀取操作,不可能同時,即使是同時進(jìn)行讀取操作,最終的結(jié)果也只是同時放棄資源罷了。也就是說在在設(shè)置操作后給了任務(wù)兩種可能性,占用或不占用,同時也避免了兩個任務(wù)同時占用的情況。
所以之前的遺留的問題答案是不會有風(fēng)險,因為最糟糕的情況也只是兩個任務(wù)同時放棄資源。
總之一句話,誰讀取時沒其他任務(wù)占用,不管后面發(fā)生什么情況,這個資源我占定了。
在上面的互斥量問題的思考中,我們可以得出一個結(jié)論,讀取的數(shù)據(jù)是什么決定了你接下來的動作是什么,并且按照先前的探討可以發(fā)現(xiàn)即使不關(guān)閉總中斷也不會導(dǎo)致資源使用的混亂問題,正是基于此考慮,認(rèn)為很多情況下是可以不關(guān)閉中斷的,但是當(dāng)我看到如下uCOS II源碼的時候,認(rèn)為不關(guān)閉中斷也是可以的,但是進(jìn)行深入思考發(fā)現(xiàn)必須關(guān)閉中斷才行。

看如上消息郵箱OSMboxPend的源碼,可以發(fā)現(xiàn)在讀取消息指針的時候就關(guān)閉了中斷,但是按照先前的思考,即使沒有關(guān)閉中斷,大不了在讀取過程中被中斷,然后將OSEventPtr設(shè)置為非零狀態(tài)。但是不會影響后面的判斷操作,但是真的如此嗎?

從上面兩種情況分析可以發(fā)現(xiàn),在不關(guān)閉中斷的情況下確實可以保證判斷語句正確執(zhí)行(這里假設(shè)pmsg = pevent->OSEventPtr;這條C語言語句需要3條匯編語句操作執(zhí)行),但是最終根據(jù)讀取的pmsg值進(jìn)行判讀后的結(jié)果卻會導(dǎo)致兩種截然不同的效果。一個是直接返回,當(dāng)前任務(wù)正常執(zhí)行,另一種情況是將當(dāng)前任務(wù)設(shè)置為等待狀態(tài),除此之外可能還會有影響整個系統(tǒng)混亂的操作,這是絕對不允許的情況。
在之前分析共享資源的互斥量時發(fā)現(xiàn)也會出現(xiàn)這種情況,但是為什么卻不會沒事呢?這是因為最糟糕的情況就是都獲取不到共享資源,而一般來說都獲取不到資源的雖然少見,但在之前那種不管中斷操作的情況下確實會出現(xiàn),但是即使都獲取不到資源也不會導(dǎo)致系統(tǒng)嚴(yán)重后果,因為一般來說,當(dāng)自己沒有獲取到資源的時候,下次還會繼續(xù)嘗試獲取,另一個任務(wù)同樣如此,在不管中斷的情況下,只是導(dǎo)致重新獲取資源的話,這是可以接受的一種情況。但是現(xiàn)在分析的這種情況卻不允許,因為本來能正常運行的,你卻讓它進(jìn)入等待狀態(tài),很可能導(dǎo)致嚴(yán)重的后果。所以在判斷問題上,如果打斷判斷的不同結(jié)果會導(dǎo)致非常嚴(yán)重的后果,那么最好將讀取、判斷、分類動作設(shè)置成原子操作(不可打斷的操作),而將整個流程設(shè)置成原子操作的一般方法就是關(guān)中斷。
原文作者:嵌入式Linux
