我一直在为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))

但是,当我想使用对ARMv7使用LDREX和STREX的cpu指令(atomic_inc,atomic_dec,atomic_cmpxchg等)以原子方式操纵此值时,就会出现问题。

ARMARM在这一节中没有提及有关中断被阻止的任何信息,因此我假设在LDREX和STREX之间可能会发生中断。它确实提到的是锁定内存总线,我认为这仅对MP系统有用,因为MP系统中可能有更多的CPU试图同时访问同一位置。但是对于UP(可能是MP),如果在LDREX和STREX的这个小窗口中触发了计时器中断(或SMP的IPI),则异常处理程序可能会更改cpu上下文并返回到新任务,但是令人震惊的部分现在出现了,它将执行“CLREX”,因此删除了先前线程持有的所有排他锁。那么在UP系统上使用LDREX和STREX的原子性要比LDR和STR好吗?

我确实读过一些有关互斥锁监视器的内容,所以我有一个可能的理论,当线程继续执行STREX并执行STREX时,os监视器会导致此调用失败,可以检测到该调用,并可以使用new重新执行循环过程中的值(value)(回到LDREX),我在这里吗?

最佳答案

链接加载/存储专有的范式的思想是,如果存储在加载后不久就跟随,没有介入的内存操作,并且如果没有其他事情触及该位置,则该存储很可能会成功,但是如果有的话否则就碰到商店肯定会失败的位置。不能保证商店有时不会因没有明显的原因而失败;但是,如果将加载和存储之间的时间保持最短,并且在它们之间没有内存访问,则会发生如下循环:

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范例,优先级最高的任务将不受较低任务的影响(但将导致较低任务必须浪费工作)。

关于thread-safety - ARM中的原子操作,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/11894059/

10-12 22:25