目次: Zephyr
SMP対応のうち、ビルドエラーの対処が終わったので、実行時エラーに対処します。
CONFIG_SMPを有効にすると、k_spin_lock() 内でatomic_cas() を呼ぶようになります。するとk_spin_lock() -> atomic_cas() -> z_impl_atomic_cas() -> k_spin_lock() という循環呼び出しが発生し、スタックオーバーフローを起こしてクラッシュします。これはZephyrのバグではなくコンフィグの設定間違いが原因です。
// zephyr/include/spinlock.h
static ALWAYS_INLINE k_spinlock_key_t k_spin_lock(struct k_spinlock *l)
{
ARG_UNUSED(l);
k_spinlock_key_t k;
/* Note that we need to use the underlying arch-specific lock
* implementation. The "irq_lock()" API in SMP context is
* actually a wrapper for a global spinlock!
*/
k.key = arch_irq_lock();
#ifdef CONFIG_SPIN_VALIDATE
__ASSERT(z_spin_lock_valid(l), "Recursive spinlock %p", l);
#endif
#ifdef CONFIG_SMP
while (!atomic_cas(&l->locked, 0, 1)) { //★CONFIG_SMPが有効だとatomic_cas() を呼ぶ★
}
#endif
...
// zephyr/include/sys/atomic.h
#ifdef CONFIG_ATOMIC_OPERATIONS_BUILTIN
static inline bool atomic_cas(atomic_t *target, atomic_val_t old_value,
atomic_val_t new_value)
{
return __atomic_compare_exchange_n(target, &old_value, new_value,
0, __ATOMIC_SEQ_CST,
__ATOMIC_SEQ_CST);
}
#elif defined(CONFIG_ATOMIC_OPERATIONS_C) //★既存のRISC-Vボードはこちらが有効になっている★
__syscall bool atomic_cas(atomic_t *target, atomic_val_t old_value,
atomic_val_t new_value);
#else
extern bool atomic_cas(atomic_t *target, atomic_val_t old_value,
atomic_val_t new_value);
#endif
...
// build/zephyr/include/generated/syscalls/atomic.h
static inline bool atomic_cas(atomic_t * target, atomic_val_t old_value, atomic_val_t new_value)
{
#ifdef CONFIG_USERSPACE
if (z_syscall_trap()) {
return (bool) arch_syscall_invoke3(*(uintptr_t *)&target, *(uintptr_t *)&old_value, *(uintptr_t *)&new_value, K_SYSCALL_ATOMIC_CAS);
}
#endif
compiler_barrier();
return z_impl_atomic_cas(target, old_value, new_value); //★ここにくる★
}
...
// zephyr/kernel/CMakeLists.txt
target_sources_ifdef(CONFIG_ATOMIC_OPERATIONS_C kernel PRIVATE atomic_c.c) //★CONFIG_ATOMIC_OPERATIONS_C有効のとき実装はatomic_c.c★
...
// zephyr/kernel/atomic_c.c
bool z_impl_atomic_cas(atomic_t *target, atomic_val_t old_value,
atomic_val_t new_value)
{
k_spinlock_key_t key;
int ret = false;
key = k_spin_lock(&lock); //★循環呼び出し★
if (*target == old_value) {
*target = new_value;
ret = true;
}
k_spin_unlock(&lock, key);
return ret;
}
...
RISC-VのSoCのコンフィグでは大抵CONFIG_ATOMIC_OPERATIONS_Cが有効になっていて、atomic_cas() の実装としてスピンロックを使います。これはSMPと相性が悪く、CONFIG_ATOMIC_OPERATIONS_CとCONFIG_SMPを同時に有効にすると先ほど説明した循環呼び出しが発生してしまいます。
循環呼び出しを防ぐには独自にatomic_cas() を実装する必要がありますが、アトミック操作を自分で実装&検証するのは大変ですから、RISC-Vのアトミック命令(Atomic Extension)とコンパイラの機能を頼ります。
以前追加したQEMU RISC-V 32bit virtpc用のコンフィグをSMPのテスト用に改造します。
# zephyr/soc/riscv/riscv-privilege/rv32-virt/Kconfig.soc
config SOC_QEMU_RV32_VIRT
bool "QEMU RV32 virt SOC implementation"
select ATOMIC_OPERATIONS_C if !SMP # 非SMPのときは従来通り
select ATOMIC_OPERATIONS_BUILTIN if SMP # SMPのときはAtomic Extensionに頼る
コンフィグCONFIG_ATOMIC_OPERATIONS_BUILTINを有効にすると、Zephyrはatomic_cas() の実装として __atomic_compare_exchange_n() ビルトイン関数を使います。ビルトイン関数を使うにはコンパイラのサポートが必要で、今のところ、サポートしているのはGCCのみだと思います。LLVMでも使えるかもしれませんが、未調査です。
これでCONFIG_SMPを有効にしても、エラーやハングアップすることなく、今までどおりに動作するようになったはずです。
< | 2020 | > | ||||
<< | < | 10 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | - | 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
合計:
本日: