ルモーリン

LPCネイティブで排他処理を作る

投稿:2014-08-09

これまでFreeRTOSのAPIを利用して排他処理を作っていました。 長さ1で設定したキューにパケットを押し込めたタスクが占有、競合した別のタスクは2個目のパケットがキューに入らずタスク停止という仕掛けです。 LPC(Cortex-M3)でアトミックなテスト&モディファイ命令を見つけて置き換えるつもりでした。
ユーザーマニュアルを読んで見つけられません。 汎用機と同じ命令がマイコンにないのは仕方ない。
代わりに見つけたのがldrexとstrexです。 exclusiveという事からそれっぽいと気付いただけで私の英語読解力ではマニュアルの説明が分かりませんでした。 とりあえず検索して色々な方々のブログを拝見、おおよその使い方が分かりました。
  1. 共用メモリからロード
  2. 占有できたつもりになって更新予定の値を生成
  3. 共用メモリにストア
  4. ストア成功なら完了、失敗なら最初からやり直し
衝撃だったのが4番目の「失敗なら最初からやり直し」です。失敗前提の命令を初めて見ました。
1個のフラグをタスク1個が占有するケースは色々とありそうなので簡単に使えるよう関数にしました。
// 排他処理開始
void ksrk_exclusive_entry(uint32_t *pFlag)
{
	// 排他で進入フラグをセットが成功するまで待機
	uint32_t result = 1;
	while (result)
	{
		uint32_t EntryExclusive_old = 0;
		__asm volatile ("ldrex %0, [%1]" : "=r" (EntryExclusive_old) : "r" (pFlag));
		uint32_t EntryExclusive_new = 1;
		__asm volatile ("strex %0, %1, [%2]" : "=r" (result) : "r" (EntryExclusive_new), "r" (pFlag));

		// セット失敗か先行タスクがセット済の場合
		if (result || EntryExclusive_old)
		{
			// 他のタスクにCPUを譲る
			vTaskDelay(0);
			// ラウンドロビンか、他のタスクからCPUを譲ってもらうと戻ってくる
		}
	}
}

// 排他処理終了
void ksrk_exclusive_exit(uint32_t *pFlag)
{
	// 排他で進入フラグをクリアが成功するまで待機
	uint32_t result = 1;
	while (result)
	{
		uint32_t EntryExclusive_old = 0;
		__asm volatile ("ldrex %0, [%1]" : "=r" (EntryExclusive_old) : "r" (pFlag));
		uint32_t EntryExclusive_new = 0;
		__asm volatile ("strex %0, %1, [%2]" : "=r" (result) : "r" (EntryExclusive_new), "r" (pFlag));

		// 自分でセットしてあるハズ
		KSRK_ASSERT(EntryExclusive_old);

		// クリア失敗の場合
		if (result)
		{
			// 他のタスクにCPUを譲る
			vTaskDelay(0);
			// ラウンドロビンか、他のタスクからCPUを譲ってもらうと戻ってくる
		}
	}
}
FreeRTOSに排他制御の機能があっておそらく使える物だとおもうのですが 実装を拝見すると割り込み止めたりしていて「どのマイコンでもこんな感じなら動作するよねー」的なコードでした。 種々のマイコンでソースを共用するFreeRTOSの宿命ですね。 趣味でLPC1769を遊び倒す者からすると「それなら別にLPCじゃなくてもよくね?」という思いがあって、 やっぱり「このコードはLPC以外じゃ通用しないだろ、うひゃひゃ。」というのを書いていきたい。
そういえばLinuxカーネルの初期バージョンは80386用に書かれていて、 最初の移植がAmigaだけど、Amigaユーザは移植じゃなくて680xx用のコードを書いてしまい、 その調子で他のアーキテクチャに移植してしまうと保守できそうになくて、 リーナスさんが移植しやすいコードに改善したとか聞いた事があるなあ。 専用のコードを書くってAmigaユーザの宿命なんだろうか。
ご参考→Linuxの強味
特にありません。