2012-08-10 235 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