ARM中的原子操作

sgu*_*pta 16 kernel arm atomic thread-safety

我一直在为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))
Run Code Online (Sandbox Code Playgroud)

但是,当我想使用对于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时,os监视器会导致此调用失败,可以检测到并且可以使用new重新执行循环过程中的价值(分支回LDREX),我在这里吗?

sup*_*cat 13

负载链接/存储专用范例背后的想法是,如果存储在加载后很快跟进,没有中间存储操作,并且如果没有其他任何东西触及该位置,则商店可能会成功,但是如果有的话否则触及了商店肯定会失败的位置.无法保证商店有时不会因为没有明显原因而失败; 但是,如果加载和存储之间的时间保持最小,并且它们之间没有内存访问,则循环如下:

do
{
  new_value = __LDREXW(dest) + 1;
} while (__STREXW(new_value, dest));
Run Code Online (Sandbox Code Playgroud)

通常可以依靠在几次尝试中成功.如果基于旧值计算新值需要一些重要的计算,则应该将循环重写为:

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;
}
Run Code Online (Sandbox Code Playgroud)

如果在计算新值时某些内容发生变化,则此代码必须重新运行其主循环,但如果__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);
}
Run Code Online (Sandbox Code Playgroud)

如果不经常有多个线程/中断/同时尝试更新同一个东西,这种方法应该允许安全地执行更新.如果可能尝试更新同一项目的事物之间存在优先级关系,则优先级最高的一方将保证在第一次尝试时成功,下一个最高优先级的一方将成功执行任何未被抢占的尝试.如果一个人正在使用锁定,那么想要执行更新的最高优先级任务必须等待优先级较低的更新完成; 使用CompareAndSwap范例,最高优先级的任务将不受较低优先级的任务的影响(但会导致较低的任务不得不浪费掉工作).


sgu*_*pta 10

好的,从他们的网站得到答案.

如果上下文切换在进程执行了Load-Exclusive之后但在执行Store-Exclusive之前调度了进程,则Store-Exclusive会在进程恢复时返回false否定结果,并且内存不会更新.这不会影响程序功能,因为该进程可以立即重试该操作.