关于使用 ARM 7 CCL Lisp 编译器、树莓派解决锁定问题的建议?

J K*_*ein 14 lisp mutex arm common-lisp raspberry-pi

背景:CCL又名 OpenMCL 是一个非常好的、值得尊敬的、轻量级但相当快的 Common Lisp 编译器。它非常适合 RPi,因为它在 32 位模型上运行,并且不太占用内存。理论上,与较重的 SBCL lisp 编译器不同,它支持 32 位 RPi 上的线程。但它有一个长期存在的互斥错误。

然而,这是一个ARM机器语言问题,而不是一个Lisp问题。我希望 ARM 专家能读到这篇文章并大吃一惊来自不同背景的时刻。

问题在于,CCL 在 Raspberry Pi 2 和 3(可能还有 4)以及其他 ARM 板上存在致命缺陷:当使用线程和锁定时,它会因内存损坏而失败。螺纹故障多年来一直为人所知

我相信我进一步将问题隔离为锁定失败:当 CCL 线程获取锁(互斥体)并检查它是否拥有该锁时,有时会发现另一个线程拥有该锁。线程似乎会窃取彼此的锁,这对于垃圾收集等来说是致命的。在我看来,一个核心已控制锁的信息在其他核心自己获取锁之前不会渗透到其他核心(竞争条件)。此错误不会发生在单核 RP 上,例如 Pi Zero。

我已经在这个 GitHub 存储库中探索了这个错误。相关函数是(threadtest2)生成线程、执行锁并检查锁所有权。我最初认为锁定代码可能是缺少DMB指令;DMB“确保独占写入同步到所有处理器”。因此我把DMB指令放在了所有的锁定代码中(但仔细一看,DMB已经在一些地方出现了,所以最初的编译器作者已经想到了这一点)。

具体来说,我将 DMB 放入了从ARM/l0-misc.lisp中调用的无 futex 版本调用的arm-misc.lisp的几乎每个锁定例程中,但没有成功。ARM/l0-misc.lisp 中的低级函数是“%ptr-store-fixnum-conditional”。这不使用 DMB,而是使用 LDREX/STREX 原子更新函数。%get-spin-lock%lock-recursive-lock-ptr

[编辑] 正如用户coredump在下面指出的那样,根据博客和 ARM 文档,DMB 在多核上确实是必要的,尽管对于它应该出现在 STREX 之后还是 LDREX 之前的多少位置存在一些分歧。

显然,我并不是要求任何人诊断这个编译器。我的问题是

这种盗锁行为是否引起了人们的注意?有没有其他人在其他上下文中看到过 ARM 上的锁窃取或竞争条件问题,并且他们找到了解决方案吗?关于 DMB,我是否遗漏了什么,或者是否需要其他说明?


作为附录,这里是我对 ARM/lo-misc.lisp 中可能失败的部分的注释,函数%ptr-store-fixnum-conditional - 这是 Lisp 格式的机器代码。我按照下面的评论插入了一些 DMB,但没有帮助。

    ;; this is the function used to grab the mutex, using ldrex/strex
    ;; to set a memory location atomically
    (defarmlapfunction %ptr-store-fixnum-conditional 
       ((ptr arg_x) (expected-oldval arg_y) (newval arg_z))
     (let ((address imm2)  ;; define some variables
           (actual-oldval imm1))
           (macptr-ptr address ptr)
           @again
      (DMB) ;; my new DMB, according to Chen's blog (not ARM manual)
          ;; first, load word from memory with ldrex, 
          ;;    initializing atomic memory operation
          ;;    and claiming control of this memory for this core
          (ldrex actual-oldval (:@ address))
          ;; if the actual-oldval is wrong, then give up on 
          ;;  this pointer store because the lock is taken,
          ;;    (looping higher up in code until free)
          (cmp actual-oldval expected-oldval)
          (bne @done)
          ;; 
          ;; 2nd part of exclusive memory access:
          ;;  store newval into memory and put a flag into imm0
          (strex imm0 newval (:@ address))
          ;; if the exclusive store failed, another core messed 
          ;;    with memory, so loop for another  ldrex/strex cycle 
          (cmp imm0 (:$ 0))
          (bne @again)
     (DMB) ;; my new DMB after conditional jump
          ;; success: the lock was obtained (and exclusive access
          ;;    was cleared by strex)
          (mov arg_z actual-oldval)
          (bx lr)  ;; return to caller in case of good mutex grab
          @done
          ;; clear exclusive access if lock grab failed
          (clrex)
          (mov arg_z actual-oldval)
      (DMB) ;; my new DMB.  Maybe not needed?
          (bx lr)))  ;; return to caller in case of failed mutex grab
Run Code Online (Sandbox Code Playgroud)

附录 - 我再次尝试在每个 LDREX/STREX 周围放置 DMB,但没有帮助。我还尝试将 DMB 放入每个 %SET-xxx 函数中,遵循有关释放互斥体的 ARM 文档,但这更难追踪 - 在 grep 整个内容后,我无法找到 %%set-unsigned-long 的定义位置源树,所以我盲目地在 %SET-xxx 函数内的每个 STR 指令之前填充 DMB。

我相信 CCL​​ 在其他平台上使用系统级 futex,并且仅在 ARM 上执行自己的自定义锁定(?),如果这是另一个线索的话。也许可以使用操作系统提供的 futex 来修复整个问题?也许没有其他系统使用自定义锁,所以 ARM 只是第一个显示损坏的(多核)系统?


Tib*_*ic4 1

您可以尝试在 LDREX 指令之前和 STREX 指令之后添加 DMB 指令,看看是否有帮助。DMB指令是内存屏障指令,它确保独占写入同步到所有处理器。DMB 指令在 ARM 架构参考手册中进行了描述。