2012-08-10 199 views
12

我一直在研究ARM的嵌入式操作系統,但是有一些事情我甚至在提及ARMARM和linux源代碼後對架構並不瞭解。ARM中的原子操作

原子操作。

ARM ARM表示加載和存儲指令是原子的,它的執行保證在中斷處理程序執行之前完成。通過查看

arch/arm/include/asm/atomic.h : 
    #define atomic_read(v) (*(volatile int *)&(v)->counter) 
    #define atomic_set(v,i) (((v)->counter) = (i)) 

然而經查實,問題就來了時,我想操縱原子這個值利用CPU指令(atomic_inc,atomic_dec,atomic_cmpxchg等),它使用LDREX和STREX對ARMv7(我的目標) 。

ARMARM對本節中的中斷沒有任何說明,所以我假設在LDREX和STREX之間可能會發生中斷。它提到的事情是關於鎖定內存總線,我猜測它只對MP系統有幫助,在這種系統中可能有更多的CPU試圖在同一時間訪問相同的位置。但是對於UP(也可能是MP),如果在LDREX和STREX的這個小窗口中觸發定時器中斷(或IPI for SMP),則異常處理程序可能會更改cpu上下文並返回到新任務,但是令人震驚的部分現在進入,它執行'CLREX',從而刪除前一個線程所擁有的排他鎖。那麼在UP系統上使用LDREX和STREX比LDR和STR在原子性上更好?

我的確讀過一些關於獨佔鎖定監視器的內容,所以我有一種可能的理論認爲,當線程恢復並執行STREX時,os監視器會導致此調用失敗,從而可以檢測到該環路,使用過程中的新值執行(分支返回LDREX),我在這裏嗎?

回答

9

好的,從他們的website得到了答案。

如果上下文切換在進程執行了Load-Exclusive之後但在執行Store-Exclusive之前計劃了一個進程,則Store-Exclusive會在進程恢復時返回錯誤的否定結果,並且內存不會更新。這不會影響程序功能,因爲進程可以立即重試操作。

8

的加載鏈接/存儲排他性模式背後的想法是,如果存儲將按照負荷後很快,沒有中間的存儲操作,如果如果沒有其他已觸及的位置,這家店是可能成功,但如果其他東西已觸及位置,商店是某些失敗。無法保證商店不會因無明顯原因而失敗;如果加載和存儲之間的時間保持爲最小,但是,它們之間有沒有存儲器訪問,環路等:

do 
{ 
    new_value = __LDREXW(dest) + 1; 
} while (__STREXW(new_value, dest)); 

一般可依靠幾個嘗試內成功。如果計算基於舊值新值所需的一些顯著的計算,應該重寫環路:

do 
{ 
    old_value = *dest; 

    new_value = complicated_function(old_value); 
} while (CompareAndStore(dest, new_value, old_value) != 0); 

... Assuming CompareAndStore is something like: 

uint32_t CompareAndStore(uint32_t *dest, uint32_t new_value, uint_32 old_value) 
{ 
    do 
    { 
    if (__LDREXW(dest) != old_value) return 1; // Failure 
    } while(__STREXW(new_value, dest); 
    return 0; 
} 

此代碼將有如果有新的變化,以重新運行它的主循環* DEST當正在計算新的價值,但只有小環將需要重新運行,如果__STREXW失敗某些其他原因[這是希望不太可能,因爲只會有在__LDREXW和__STREXW約兩個指令]

附錄 「計算基於舊的新價值」可能會變得複雜的情況的一個例子是「價值」所在的情況e有效地提及複雜的數據結構。代碼可能會獲取舊引用,從舊引用新的數據結構,然後更新引用。這種模式在垃圾回收框架中比在「裸機」編程中出現得更頻繁,但即使在編程裸機時也會出現多種方式。正常的malloc/calloc分配器通常不是線程安全的/中斷安全的,但是固定大小的結構的分配器通常是。如果一個人擁有的功率的二一定數量的數據結構的「池」(說255),可以使用類似:

#define FOO_POOL_SIZE_SHIFT 8 
#define FOO_POOL_SIZE (1 << FOO_POOL_SIZE_SHIFT) 
#define FOO_POOL_SIZE_MASK (FOO_POOL_SIZE-1) 

void do_update(void) 
{ 
    // The foo_pool_alloc() method should return a slot number in the lower bits and 
    // some sort of counter value in the upper bits so that once some particular 
    // uint32_t value is returned, that same value will not be returned again unless 
    // there are at least (UINT_MAX)/(FOO_POOL_SIZE) intervening allocations (to avoid 
    // the possibility that while one task is performing its update, a second task 
    // changes the thing to a new one and releases the old one, and a third task gets 
    // given the newly-freed item and changes the thing to that, such that from the 
    // point of view of the first task, the thing never changed.) 

    uint32_t new_thing = foo_pool_alloc(); 
    uint32_t old_thing; 
    do 
    { 
    // Capture old reference 
    old_thing = foo_current_thing; 

    // Compute new thing based on old one 
    update_thing(&foo_pool[new_thing & FOO_POOL_SIZE_MASK], 
     &foo_pool[old_thing & FOO_POOL_SIZE_MASK); 
    } while(CompareAndSwap(&foo_current_thing, new_thing, old_thing) != 0); 
    foo_pool_free(old_thing); 
} 

如果不經常是多線程/中斷/任何試圖同時更新相同的東西,這種方法應該允許安全地執行更新。如果在可能嘗試更新同一項目的事物之間存在優先關係,那麼優先級最高的一個在第一次嘗試時保證成功,而次最高優先級的一個將在任何未被搶佔的嘗試中成功最高優先級的更新等等。如果有人使用鎖定,那麼想要執行更新的最高優先級任務將不得不等待較低優先級的更新完成;使用CompareAndSwap範例,優先級最高的任務將不會受到較低優先級任務的影響(但會導致較低的任務不得不浪費工作)。

+0

我一直在做同樣的事情,但新值需要重要計算的部分,我仍然困惑。使用cmxchg循環是有意義的,因爲那麼專有監視器不會被上下文切換清除,但重新執行重要計算需要很多開銷,因爲我觀察到街道失敗並沒有明顯的原因(UP在PSR中掩蓋了IRQ )在你的文章中提到。 – sgupta 2013-03-13 01:59:19

+0

@ user1075375:查看附錄 – supercat 2013-03-13 15:48:26

+0

這些(__LDREXW&__STREXW)是Cortex-M系列微控制器級處理器的Keil編譯器支持的內在函數,通常不可用於主流ARM目標(例如AArch64)和編譯器(例如gcc,llvm ) 對? http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/BABDEEJC.html – ahcox 2015-02-05 19:20:29