[轉]ARM平臺下獨占訪問指令LDREX和STREX
參考:ARM平臺下獨占訪問指令LDREX和STREX的原理與使用詳解
全文轉載如下:
為了實現線程間同步,一般都要在執行關鍵代碼段之前加互斥(Mutex)鎖,且在執行完關鍵代碼段之後解鎖。為了實現所謂的互斥鎖的概念,一般都需要所在平臺提供支持。
本文主要用來說明ARM平臺上特有的獨占訪問指令LDREX和STREX的工作原理,以及如何使用。而它們也是ARM平臺上,實現互斥鎖等線程同步工具的基礎。
我們先來看看LDREX和STREX兩條指令的語義。其實LDREX和STREX指令,是將單純的更新內存的原子操作分成了兩個獨立的步驟。
1)LDREX用來讀取內存中的值,並標記對該段內存的獨占訪問:
LDREX Rx, [Ry]
上面的指令意味著,讀取寄存器Ry指向的4字節內存值,將其保存到Rx寄存器中,同時標記對Ry指向內存區域的獨占訪問。
如果執行LDREX指令的時候發現已經被標記為獨占訪問了,並不會對指令的執行產生影響。
2)而STREX在更新內存數值時,會檢查該段內存是否已經被標記為獨占訪問,並以此來決定是否更新內存中的值:
STREX Rx, Ry, [Rz]
如果執行這條指令的時候發現已經被標記為獨占訪問了,則將寄存器Ry中的值更新到寄存器Rz指向的內存,並將寄存器Rx設置成0。指令執行成功後,會將獨占訪問標記位清除。
而如果執行這條指令的時候發現沒有設置獨占標記,則不會更新內存,且將寄存器Rx的值設置成1。
一旦某條STREX指令執行成功後,以後再對同一段內存嘗試使用STREX指令更新的時候,會發現獨占標記已經被清空了,就不能再更新了,從而實現獨占訪問的機制。
大致的流程就是這樣,但是ARM內部為了實現這個功能,還有不少復雜的情況要處理。
在ARM系統中,內存有兩種不同且對立的屬性,即共享(Shareable)和非共享(Non-shareable)。共享意味著該段內存可以被系統中不同處理器訪問到,這些處理器可以是同構的也可以是異構的。而非共享,則相反,意味著該段內存只能被系統中的一個處理器所訪問到,對別的處理器來說不可見。
為了實現獨占訪問,ARM系統中還特別提供了所謂獨占監視器(Exclusive Monitor)的東西,其結構大致如下:
可以看出來,一共有兩種類型的獨占監視器。每一個處理器內部都有一個本地監視器(Local Monitor),且在整個系統範圍內還有一個全局監視器(Global Monitor)。
如果要對非共享內存區中的值進行獨占訪問,只需要涉及本處理器內部的本地監視器就可以了;而如果要對共享內存區中的內存進行獨占訪問,除了要涉及到本處理器內部的本地監視器外,由於該內存區域可以被系統中所有處理器訪問到,因此還必須要由全局監視器來協調。
對於本地監視器來說,它只標記了本處理器對某段內存的獨占訪問,在調用LDREX指令時設置獨占訪問標誌,在調用STREX指令時清除獨占訪問標誌。
而對於全局監視器來說,它可以標記每個處理器對某段內存的獨占訪問。也就是說,當一個處理器調用LDREX訪問某段共享內存時,全局監視器只會設置針對該處理器的獨占訪問標記,不會影響到其它的處理器。當在以下兩種情況下,會清除某個處理器的獨占訪問標記:
1)當該處理器調用LDREX指令,申請獨占訪問另一段內存時;
2)當別的處理器成功更新了該段獨占訪問內存值時。
對於第二種情況,也就是說,當獨占內存訪問內存的值在任何情況下,被任何一個處理器更改過之後,所有申請獨占該段內存的處理器的獨占標記都會被清空。
另外,更新內存的操作不一定非要是STREX指令,任何其它存儲指令都可以。但如果不是STREX的話,則沒法保證獨占訪問性。
現在的處理器基本上都是多核的,一個芯片上集成了多個處理器。而且對於一般的操作系統,系統內存基本上都被設置上了共享屬性,也就是說對系統中所有處理器可見。因此,我們這裏主要分析多核系統中對共享內存的獨占訪問的情況。
為了更加清楚的說明,我們可以舉一個例子。假設系統中有兩個處理器內核,而一個程序由三個線程組成,其中兩個線程被分配到了第一個處理器上,另外一個線程被分配到了第二個處理器上。且他們的執行序列如下:
大致經歷的步驟如下:
1)CPU2上的線程3最早執行LDREX,鎖定某段共享內存區域。它會相應更新本地監視器和全局監視器。
2)然後,CPU1上的線程1執行LDREX,它也會更新本地監視器和全局監視器。這時在全局監視器上,CPU1和CPU2都對該段內存做了獨占標記。
3)接著,CPU1上的線程2執行LDREX指令,它會發現本處理器的本地監視器對該段內存有了獨占標記,同時全局監視器上CPU1也對該段內存做了獨占標記,但這並不會影響這條指令的操作。
4)再下來,CPU1上的線程1最先執行了STREX指令,嘗試更新該段內存的值。它會發現本地監視器對該段內存是有獨占標記的,而全局監視器上CPU1也有該段內存的獨占標記,則更新內存值成功。同時,清除本地監視器對該段內存的獨占標記,還有全局監視器所有處理器對該段內存的獨占標記。
5)下面,CPU2上的線程3執行STREX指令,也想更新該段內存值。它會發現本地監視器擁有對該段內存的獨占標記,但是在全局監視器上CPU1沒有了該段內存的獨占標記(前面一步清空了),則更新不成功。
6)最後,CPU1上的線程2執行STREX指令,試著更新該段內存值。它會發現本地監視器已經沒有了對該段內存的獨占標記(第4步清除了),則直接更新失敗,不需要再查全局監視器了。
所以,可以看出來,這套機制的精髓就是,無論有多少個處理器,有多少個地方會申請對同一個內存段進行操作,保證只有最早的更新可以成功,這之後的更新都會失敗。失敗了就證明對該段內存有訪問沖突了。實際的使用中,可以重新用LDREX讀取該段內存中保存的最新值,再處理一次,再嘗試保存,直到成功為止。
還有一點需要說明,LDREX和STREX是對內存中的一個字(Word,32 bit)進行獨占訪問的指令。如果想獨占訪問的內存區域不是一個字,還有其它的指令:
1)LDREXB和STREXB:對內存中的一個字節(Byte,8 bit)進行獨占訪問;
2)LDREXH和STREXH:中的一個半字(Half Word,16 bit)進行獨占訪問;
3)LDREXD和STREXD:中的一個雙字(Double Word,64 bit)進行獨占訪問。
它們必須配對使用,不能混用。
[轉]ARM平臺下獨占訪問指令LDREX和STREX