一文看懂ARM平臺下獨占訪問指令LDREX和STREX的原理
為了實現(xiàn)線程間同步,一般都要在執(zhí)行關(guān)鍵代碼段之前加互斥(Mutex)鎖,且在執(zhí)行完關(guān)鍵代碼段之后解鎖。為了實現(xiàn)所謂的互斥鎖的概念,一般都需要所在平臺提供支持。
在計算機(jī)領(lǐng)域里,如果要在多線程的情況下要保持?jǐn)?shù)據(jù)的同步,需要引入稱作Load-Link(LL)和Store-Conditional(SC)的操作,通常簡稱為LL/SC。LL操作返回一個內(nèi)存地址上當(dāng)前存儲的值,后面的SC操作,會向這個內(nèi)存地址寫入一個新值,但是只有在這個內(nèi)存地址上存儲的值,從上個LL操作開始直到現(xiàn)在都沒有發(fā)生改變的情況下,寫入操作才能成功,否則都會失敗。這個操作非常重要,是很多平臺實現(xiàn)基本原子操作的基礎(chǔ)。 對于ARM平臺來說,也在硬件層面上提供了對LL/SC的支持,LL操作用的是LDREX指令,SC操作用的是STREX指令。
本文主要用來說明ARM平臺上特有的獨占訪問指令LDREX和STREX的工作原理,以及如何使用。而它們也是ARM平臺上,實現(xiàn)互斥鎖等線程同步工具的基礎(chǔ)。
我們先來看看LDREX和STREX兩條指令的語義。其實LDREX和STREX指令,是將單純的更新內(nèi)存的原子操作分成了兩個獨立的步驟。
LDREX用來讀取內(nèi)存中的值,并標(biāo)記對該段內(nèi)存的獨占訪問:
LDREX Rx, [Ry]
上面的指令意味著,讀取寄存器Ry指向的4字節(jié)內(nèi)存值,將其保存到Rx寄存器中,同時標(biāo)記對Ry指向內(nèi)存區(qū)域的獨占訪問。如果執(zhí)行LDREX指令的時候發(fā)現(xiàn)已經(jīng)被標(biāo)記為獨占訪問了,并不會對指令的執(zhí)行產(chǎn)生影響。
STREX在更新內(nèi)存數(shù)值時,會檢查該段內(nèi)存是否已經(jīng)被標(biāo)記為獨占訪問,并以此來決定是否更新內(nèi)存中的值:
STREX Rx, Ry, [Rz]
如果執(zhí)行這條指令的時候發(fā)現(xiàn)已經(jīng)被標(biāo)記為獨占訪問了,則將寄存器Ry中的值更新到寄存器Rz指向的內(nèi)存,并將寄存器Rx設(shè)置成0。指令執(zhí)行成功后,會將獨占訪問標(biāo)記位清除。而如果執(zhí)行這條指令的時候發(fā)現(xiàn)沒有設(shè)置獨占標(biāo)記,則不會更新內(nèi)存,且將寄存器Rx的值設(shè)置成1。一旦某條STREX指令執(zhí)行成功后,以后再對同一段內(nèi)存嘗試使用STREX指令更新的時候,會發(fā)現(xiàn)獨占標(biāo)記已經(jīng)被清空了,就不能再更新了,從而實現(xiàn)獨占訪問的機(jī)制。
大致的流程就是這樣,但是ARM內(nèi)部為了實現(xiàn)這個功能,還有不少復(fù)雜的情況要處理。
在ARM系統(tǒng)中,內(nèi)存有兩種不同且對立的屬性,即共享(Shareable)和非共享(Non-shareable)。共享意味著該段內(nèi)存可以被系統(tǒng)中不同處理器訪問到,這些處理器可以是同構(gòu)的也可以是異構(gòu)的。而非共享,則相反,意味著該段內(nèi)存只能被系統(tǒng)中的一個處理器所訪問到,對別的處理器來說不可見。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ??


為了實現(xiàn)獨占訪問,ARM系統(tǒng)中還特別提供了所謂獨占監(jiān)視器(Exclusive Monitor)的東西,其結(jié)構(gòu)大致如下:

可以看出來,一共有兩種類型的獨占監(jiān)視器。每一個處理器內(nèi)部都有一個本地監(jiān)視器(Local Monitor),且在整個系統(tǒng)范圍內(nèi)還有一個全局監(jiān)視器(Global Monitor)。
如果要對非共享內(nèi)存區(qū)中的值進(jìn)行獨占訪問,只需要涉及本處理器內(nèi)部的本地監(jiān)視器就可以了;而如果要對共享內(nèi)存區(qū)中的內(nèi)存進(jìn)行獨占訪問,除了要涉及到本處理器內(nèi)部的本地監(jiān)視器外,由于該內(nèi)存區(qū)域可以被系統(tǒng)中所有處理器訪問到,因此還必須要由全局監(jiān)視器來協(xié)調(diào)。
對于本地監(jiān)視器來說,它只標(biāo)記了本處理器對某段內(nèi)存的獨占訪問,在調(diào)用LDREX指令時設(shè)置獨占訪問標(biāo)志,在調(diào)用STREX指令時清除獨占訪問標(biāo)志。而對于全局監(jiān)視器來說,它可以標(biāo)記每個處理器對某段內(nèi)存的獨占訪問。也就是說,當(dāng)一個處理器調(diào)用LDREX訪問某段共享內(nèi)存時,全局監(jiān)視器只會設(shè)置針對該處理器的獨占訪問標(biāo)記,不會影響到其它的處理器。
當(dāng)在以下兩種情況下,會清除某個處理器的獨占訪問標(biāo)記:
當(dāng)該處理器調(diào)用LDREX指令,申請獨占訪問另一段內(nèi)存時;
當(dāng)別的處理器成功更新了該段獨占訪問內(nèi)存值時。
對于第二種情況,也就是說,當(dāng)獨占內(nèi)存訪問內(nèi)存的值在任何情況下,被任何一個處理器更改過之后,所有申請獨占該段內(nèi)存的處理器的獨占標(biāo)記都會被清空。
現(xiàn)在的處理器基本上都是多核的,一個芯片上集成了多個處理器。而且對于一般的操作系統(tǒng),系統(tǒng)內(nèi)存基本上都被設(shè)置上了共享屬性,也就是說對系統(tǒng)中所有處理器可見。因此,我們這里主要分析多核系統(tǒng)中對共享內(nèi)存的獨占訪問的情況。
為了更加清楚的說明,我們可以舉一個例子。假設(shè)系統(tǒng)中有兩個處理器內(nèi)核,而一個程序由三個線程組成,其中兩個線程被分配到了第一個處理器上,另外一個線程被分配到了第二個處理器上。且他們的執(zhí)行序列如下:

大致經(jīng)歷的步驟如下:
CPU2上的線程3最早執(zhí)行LDREX,鎖定某段共享內(nèi)存區(qū)域。它會相應(yīng)更新本地監(jiān)視器和全局監(jiān)視器。
然后,CPU1上的線程1執(zhí)行LDREX,它也會更新本地監(jiān)視器和全局監(jiān)視器。這時在全局監(jiān)視器上,CPU1和CPU2都對該段內(nèi)存做了獨占標(biāo)記。
接著,CPU1上的線程2執(zhí)行LDREX指令,它會發(fā)現(xiàn)本處理器的本地監(jiān)視器對該段內(nèi)存有了獨占標(biāo)記,同時全局監(jiān)視器上CPU1也對該段內(nèi)存做了獨占標(biāo)記,但這并不會影響這條指令的操作。
再下來,CPU1上的線程1最先執(zhí)行了STREX指令,嘗試更新該段內(nèi)存的值。它會發(fā)現(xiàn)本地監(jiān)視器對該段內(nèi)存是有獨占標(biāo)記的,而全局監(jiān)視器上CPU1也有該段內(nèi)存的獨占標(biāo)記,則更新內(nèi)存值成功。同時,清除本地監(jiān)視器對該段內(nèi)存的獨占標(biāo)記,還有全局監(jiān)視器所有處理器對該段內(nèi)存的獨占標(biāo)記。
下面,CPU2上的線程3執(zhí)行STREX指令,也想更新該段內(nèi)存值。它會發(fā)現(xiàn)本地監(jiān)視器擁有對該段內(nèi)存的獨占標(biāo)記,但是在全局監(jiān)視器上CPU1沒有了該段內(nèi)存的獨占標(biāo)記(前面一步清空了),則更新不成功。
最后,CPU1上的線程2執(zhí)行STREX指令,試著更新該段內(nèi)存值。它會發(fā)現(xiàn)本地監(jiān)視器已經(jīng)沒有了對該段內(nèi)存的獨占標(biāo)記(第4步清除了),則直接更新失敗,不需要再查全局監(jiān)視器了。
所以,可以看出來,這套機(jī)制的精髓就是,無論有多少個處理器,有多少個地方會申請對同一個內(nèi)存段進(jìn)行操作,保證只有最早的更新可以成功,這之后的更新都會失敗。失敗了就證明對該段內(nèi)存有訪問沖突了。實際的使用中,可以重新用LDREX讀取該段內(nèi)存中保存的最新值,再處理一次,再嘗試保存,直到成功為止。
還有一點需要說明,LDREX和STREX是對內(nèi)存中的一個字(Word,32 bit)進(jìn)行獨占訪問的指令。如果想獨占訪問的內(nèi)存區(qū)域不是一個字,還有其它的指令: 1)LDREXB和STREXB:對內(nèi)存中的一個字節(jié)(Byte,8 bit)進(jìn)行獨占訪問; 2)LDREXH和STREXH:中的一個半字(Half Word,16 bit)進(jìn)行獨占訪問; 3)LDREXD和STREXD:中的一個雙字(Double Word,64 bit)進(jìn)行獨占訪問。
在ARMv8指令集下,LDREX指令被改名成了LDXR指令,而STREX指令被改名成了STXR指令,功能基本上是一樣的,除了添加了一個新的特性。當(dāng)全局監(jiān)視器標(biāo)記的對某段內(nèi)存的獨占訪問被清空后,將向所有標(biāo)記了對該段內(nèi)存獨占訪問的CPU核都發(fā)送事件,將它們從WFE指令中喚醒,繼續(xù)執(zhí)行。
