我一直在为ARM的嵌入式操作系统工作,但是即使在引用ARMARM和linux源代码之后,我仍然对架构有一些了解。
原子操作。
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系统有帮助,因为MP系统可能有更多的CPU试图同时访问同一个位置。但是对于UP(可能还有MP),如果定时器中断(或SMP的IPI)在LDREX和STREX的这个小窗口中触发,异常处理程序执行可能会改变cpu上下文并返回到新任务,但令人震惊的部分现在进入,它执行'CLREX',因此删除前一个线程持有的任何独占锁。那么在UP系统上使用LDREX和STREX比使用LDR和STR在原子性方面有多好?
我确实读过一些关于独占锁定监视器的内容,所以我有一个可能的理论,当线程恢复并执行STREX时,os监视器会导致此调用失败,可以检测到并且循环可以重新执行使用过程中的新值(分支回LDREX),我在这里吗?
答案 0 :(得分:11)
负载链接/存储专用范例背后的想法是,如果存储在加载后很快跟随 ,没有中间存储操作,并且如果没有其他任何东西触及该位置,商店可能成功,但如果其他地方触及了该商店某些失败的位置。无法保证商店有时不会因为没有明显原因而失败;但是,如果加载和存储之间的时间保持最小,并且它们之间没有内存访问,那么循环如下:
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;
}
如果在计算新值时某些内容发生了变化,则此代码必须重新运行其主循环,但如果__STREXW因某些其他原因而失败,则只需要重新运行小循环[希望不太可能鉴于__LDREXW和__STREXW之间只有两条指令
附录的 一个例子,根据旧的"计算新值。可能是复杂的将是"值"实际上是对复杂数据结构的引用。代码可以获取旧引用,从旧引用新的数据结构,然后更新引用。这种模式在垃圾收集框架中比在"裸金属中更常出现"编程,但即使在编写裸机时也有各种各样的方法。正常的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范例,优先级最高的任务不会受到较低优先级任务的影响(但会导致较低优先级的任务不得不浪费掉工作)。
答案 1 :(得分:10)
好的,从website获得答案。
如果上下文切换在进程执行了Load-Exclusive之后但在执行Store-Exclusive之前调度了进程,则Store-Exclusive会在进程恢复时返回false否定结果,并且内存不会更新。这不会影响程序功能,因为该进程可以立即重试该操作。