ARM Cortex A9にクリティカルセクションを実装する方法


15

ARM926コアからCortexA9にいくつかのレガシーコードを移植しています。このコードはベアメタルであり、OSまたは標準ライブラリは含まれていません。すべてカスタムです。コードのクリティカルセクショニングによって防止されるべき競合状態に関連すると思われる障害が発生しています。

このCPUにクリティカルセクションが正しく実装されていない可能性があるかどうかを確認するには、アプローチに関するフィードバックが必要です。GCCを使用しています。微妙なエラーがあると思います。

また、ARM用のこれらのタイプのプリミティブを備えたオープンソースライブラリ(または優れた軽量スピンロック/セメフォライブラリ)もありますか?

#define ARM_INT_KEY_TYPE            unsigned int
#define ARM_INT_LOCK(key_)   \
asm volatile(\
    "mrs %[key], cpsr\n\t"\
    "orr r1, %[key], #0xC0\n\t"\
    "msr cpsr_c, r1\n\t" : [key]"=r"(key_) :: "r1", "cc" );

#define ARM_INT_UNLOCK(key_) asm volatile ("MSR cpsr_c,%0" : : "r" (key_))

コードは次のように使用されます。

/* lock interrupts */
ARM_INT_KEY_TYPE key;
ARM_INT_LOCK(key);

<access registers, shared globals, etc...>

ARM_INT_UNLOCK(key);

「キー」の概念は、ネストされたクリティカルセクションを許可することであり、これらは関数の最初と最後でリエントラント関数を作成するために使用されます。

ありがとう!


1
infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dht0008a/を参照してください...埋め込みasm btwでは実行しないでください。記事のように機能させてください。
ジェイソン胡14

ARMについては何も知りませんが、mutex(または任意のクロススレッドまたはクロスプロセス同期関数)については、「メモリ」クローバーを使用して、a)レジスタに現在キャッシュされているすべてのメモリ値がフラッシュされることを確認する必要がありますasmを実行するにメモリ戻り、b)asmが再ロードされた後にアクセスされるメモリ内の値。(HuStmpHrrrが推奨するように)呼び出しを実行すると、このclobberが暗黙的に実行されることに注意してください。

また、私はまだARMを話せませんが、「key_」の制約は正しく見えません。これは再入力に使用されることを意図していると言うので、ロックで「= r」と宣言することは疑わしいようです。「=」は、上書きすることを意味し、既存の値は重要ではありません。既存の値を更新する意図を示すために「+」を使用するつもりだった可能性が高いようです。また、ロック解除のために、入力としてリストすると、gccに変更するつもりはないことがわかりますが、私が間違っていない場合は、変更します。これも「+」出力としてリストされるべきだと思います。

1
このような高スペックコアのアセンブリでのコーディングには+1。とにかく、これは特権モードに関連していますか?
Dzarda

私はあなたが使用する必要があります確信しているldrexと、strexそれを正しく行うには。スピンロックの使用方法と実装方法を示すWebページを次に示します。ldrexstrex

回答:


14

OSなしでクリティカルセクションを処理する最も難しい部分は、実際にミューテックスを作成することではなく、コードが現在利用できないリソースを使用したい場合にどうなるかを理解することです。読み込み専用命令と条件付きストア専用命令により、整数へのポインタを指定すると新しい値をアトミックに格納するが、ポイント先の整数に含まれていたものを返す「スワップ」関数を非常に簡単に作成できます。

int32_t atomic_swap(int32_t *dest, int32_t new_value)
{
  int32_t old_value;
  do
  {
    old_value = __LDREXW(&dest);
  } while(__STREXW(new_value,&dest);
  return old_value;
}

上記のような関数を指定すると、次のような方法でミューテックスを簡単に入力できます

if (atomic_swap(&mutex, 1)==0)
{
   ... do stuff in mutex ... ;
   mutex = 0; // Leave mutex
}
else
{ 
  ... couldn't get mutex...
}

OSがない場合、主な問題は「ミューテックスを取得できない」コードにあることがよくあります。ミューテックスで保護されたリソースがビジーのときに割り込みが発生した場合、割り込み処理コードにフラグを設定し、実行したいことを示す情報を保存してから、ミューテックスがリリースされようとしているときはいつでもミューテックスチェックを行い、ミューテックスが保持されている間に割り込みが何かをしたいかどうかを確認し、そうであれば、割り込みに代わってアクションを実行します

単に割り込みを無効にすることで、mutexで保護されたリソースを使用したい割り込みの問題を回避することは可能ですが(実際、割り込みを無効にすると、他の種類のmutexの必要性を排除できます)、一般に、必要以上に割り込みを無効にしないことが望ましいです。

有益な妥協案は、上記のフラグを使用することですが、ミューテックスを無効にする割り込みをメインラインコードで無効にし、その直前に前述のフラグをチェックします(ミューテックスを解放した後に割り込みを再度有効にします)。このようなアプローチでは、割り込みを非常に長く無効にする必要はありませんが、メインラインコードがミューテックスを解放した後に割り込みのフラグをテストする場合、フラグが表示される時間とそれが表示される時間の間に危険があるそれに作用し、ミューテックスを取得および解放し、割り込みフラグに作用する他のコードによって先取りされる可能性があります。メインラインコードがミューテックスを解放した後、割り込みのフラグをテストしない場合、

いずれにせよ、最も重要なのは、使用できないときにミューテックスで保護されたリソースを使用しようとするコードが、リソースが解放されると試行を繰り返す手段を持つことです。


7

これは、クリティカルセクションを実行するための手間のかかる方法です。割り込みを無効にします。システムがデータ障害を持っている/処理している場合、機能しない場合があります。また、割り込みレイテンシも増加します。irqflags.h Linuxではこれを扱ういくつかのマクロを持っています。指示多分便利。ただし、状態は保存されず、ネストは許可されません。 レジスタを使用しません。cpsiecpsidcps

以下のためのCortex-Aのシリーズ、ldrex/strexより効率的であり、形成するために働くことができミューテックスクリティカルセクションのためにか、彼らは一緒に使用することができますロックフリークリティカルセクションを取り除くためのアルゴリズム。

ある意味ではldrex/strex、ARMv5のように見えswpます。ただし、実際の実装ははるかに複雑です。作業用キャッシュが必要であり、必要なターゲットメモリはキャッシュ内にあるldrex/strex必要があります。ARMに関するドキュメントは、ldrex/strexCortex-A以外のCPUで機能するメカニズムを必要としているため、かなり曖昧です。ただし、Cortex-Aの場合、ローカルCPUキャッシュを他のCPUと同期させるメカニズムは、ldrex/strex命令の実装に使用されるメカニズムと同じです。Cortex-Aシリーズの場合、リザーブ粒度リザーブドldrex/strexメモリのサイズ)はキャッシュラインと同じです。また、二重リンクリストのように複数の値を変更する場合は、メモリをキャッシュラインに揃える必要があります。

微妙なエラーがあると思います。

mrs %[key], cpsr
orr r1, %[key], #0xC0  ; context switch here?
msr cpsr_c, r1

シーケンスがプリエンプトされないようにする必要があります。そうしないと、割り込みが有効になっている2つの重要な変数が取得される可能性があり、ロックの解除が正しくなくなります。swp命令をキーメモリで使用してARMv5での一貫性を確保できますが、この命令はldrex/strexマルチCPUシステムでより適切に動作するため、Cortex-Aでは推奨されません。

これらはすべて、システムのスケジューリングの種類によって異なります。メインラインと割り込みのみがあるようです。クリティカルセクションを処理するレベル(システム/ユーザースペースなど)に応じて、スケジューラへのフックを設定するために、クリティカルセクションプリミティブが必要になることがよくあります。

また、ARM用のこれらのタイプのプリミティブを備えたオープンソースライブラリ(または優れた軽量スピンロック/セメフォライブラリ)もありますか?

これは、移植性のある方法で記述するのは困難です。すなわち、そのようなライブラリは、ARM CPUの特定のバージョンおよび特定のOSに存在する場合があります。


2

これらの重要なセクションにはいくつかの潜在的な問題があります。これらすべてに警告と解決策がありますが、要約としては:

  • 最適化またはその他のランダムな理由により、コンパイラがこれらのマクロ間でコードを移動することを妨げるものはありません。
  • それらは、コンパイラーがインラインアセンブリーがそのままにすることを期待しているプロセッサー状態の一部を保存および復元します(他に指示がない限り)。
  • シーケンスの途中で割り込みが発生し、読み取り時と書き込み時の状態が変わるのを妨げるものは何もありません。

まず、コンパイラのメモリバリアが必要です。GCCはこれらをclobberとして実装します。基本的に、これはコンパイラに「いいえ、メモリアクセスの結果に影響を与える可能性があるため、このインラインアセンブリ間でメモリアクセスを移動することはできません」と伝える方法です。具体的には、開始マクロと終了マクロの両方で、両方"memory""cc"clobber が必要です。これらは、コンパイラがメモリアクセスを持っている可能性があることを知っているため、インラインアセンブリに対して他のこと(関数呼び出しなど)が並べ替えられるのを防ぎます。クローバーを使用したインラインアセンブリ全体の条件コードレジスタでGCC for ARMのホールド状態を見てきました"memory"ので、間違いなく"cc"クローバーが必要です。

第二に、これらの重要なセクションは、割り込みが有効になっているかどうかだけでなく、はるかに多くを保存および復元します。具体的には、CPSR(現在のプログラムステータスレジスタ)の大部分を保存および復元しています(リンクはCortex-R4用です。A9の素敵な図が見つからなかったためですが、同じである必要があります)。状態の各部分を実際に変更できる微妙な制限がありますが、ここでは必要以上です。

とりわけ、これには条件コード(cmp後続の条件付き命令が結果に作用できるように命令の結果が保存される場所)が含まれます。コンパイラーはこれによって間違いなく混乱するでしょう。これは"cc"、前述のようにclobberを使用して簡単に解決できます。ただし、これによりコードが毎回失敗するため、問題が発生しているようには見えません。しかし、ややこしい時限爆弾のように、ランダムな他のコードを変更すると、コンパイラーが少し違うことをする可能性があります。

これは、Thumb条件付き実行の実装に使用されるITビットの保存/復元も試みます。Thumbコードを実行したことがない場合、これは重要ではありません。私はGCCのインラインアセンブリがITビットをどのように扱うかを理解していませんが、それはそうしないと結論付けています。つまり、コンパイラはインラインアセンブリをITブロックに入れてはならず、アセンブリが常にITブロックの外側で終了することを期待しています。GCCがこれらの仮定に違反するコードを生成するのを見たことはありませんし、かなり最適化されたかなり複雑なインラインアセンブリを行ったので、それらが成り立つと確信しています。これは、おそらく実際にITビットを変更しようとはしないことを意味します。この場合、すべてが正常です。これらのビットを変更しようとすることは、「アーキテクチャ上予測不能」として分類されます、すべての種類の悪いことをすることができますが、おそらく何もしません。

保存/復元されるビットの最後のカテゴリ(実際に割り込みを無効にするものを除く)はモードビットです。これらはおそらく変更されないため、おそらく重要ではありませんが、意図的にモードを変更するコードがある場合、これらの割り込みセクションは問題を引き起こす可能性があります。特権モードとユーザーモードを切り替えることが、これを行う唯一のケースです。

第三には、間CPSRの他の部分変更からの割り込みを防止することは何もありませんMRSし、MSR中をARM_INT_LOCK。そのような変更は上書きされる可能性があります。ほとんどの合理的なシステムでは、非同期割り込みは、割り込み対象のコード(CPSRを含む)の状態を変更しません。そうした場合、コードが何をするのかを推論するのが非常に難しくなります。ただし、それは可能です(FIQ無効ビットを変更することは私には最もありそうです)。

私が指摘したすべての潜在的な問題に対処する方法でこれらを実装する方法は次のとおりです。

#define ARM_INT_KEY_TYPE            unsigned int
#define ARM_INT_LOCK(key_)   \
asm volatile(\
    "mrs %[key], cpsr\n\t"\
    "ands %[key], %[key], #0xC0\n\t"\
    "cpsid if\n\t" : [key]"=r"(key_) :: "memory", "cc" );
#define ARM_INT_UNLOCK(key_) asm volatile (\
    "tst %[key], #0x40\n\t"\
    "beq 0f\n\t"\
    "cpsie f\n\t"\
    "0: tst %[key], #0x80\n\t"\
    "beq 1f\n\t"\
    "cpsie i\n\t"
    "1:\n\t" :: [key]"r" (key_) : "memory", "cc")

-mcpu=cortex-a9少なくともいくつかのGCCバージョン(私のものなど)がデフォルトで、cpsieおよびをサポートしない古いARM CPUにデフォルト設定されているため、必ずコンパイルしてくださいcpsid

私は使用andsだけの代わりandARM_INT_LOCK、これはThumbコードで使用されている場合、それは16ビット命令ですので。"cc"クロバーは、とにかく必要なので、厳密にパフォーマンス/コードサイズの利点です。

0および1は、参照用のローカルラベルです。

これらは、バージョンと同じように使用できるはずです。これARM_INT_LOCKは元のものと同じくらい高速/小型です。残念ながら、ARM_INT_UNLOCKほとんど指示がない限りどこでも安全に行う方法を思いつきませんでした。

システムでIRQおよびFIQを無効にするタイミングに制約がある場合、これを簡略化できます。彼らは常に無効になって一緒にいる場合たとえば、あなたは一つに組み合わせることができcbz+ cpsie ifこのように:

#define ARM_INT_UNLOCK(key_) asm volatile (\
    "cbz %[key], 0f\n\t"\
    "cpsie if\n\t"\
    "0:\n\t" :: [key]"r" (key_) : "memory", "cc")

あるいは、FIQをまったく気にしない場合は、FIQを完全に有効化/無効化するだけの場合と同様です。

あなたが他のこと何も知らない場合は、これまで、あなたはまた、使用することは、両方を除いて、あなたの元のコードと非常によく似て何かを続けることができ、ロックとロック解除の間CPSR内の他の状態ビットのいずれかを変更"memory"し、"cc"切り詰めの両方でARM_INT_LOCKARM_INT_UNLOCK


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.