あなたの天然記念物
ホーム更新雑談Perl鉄ゲタランドナーコースガイド自転車Linuxリンク経歴連絡先

シグナル機構 (2014.08.25)

タスク間や例外→タスクのイベント通知

シグナルと言ってもCライブラリのシグナルじゃなくて、イベント通知の仕掛けです。 あるタスクが最大32種類のシグナルを待ち、別のタスクや例外からのシグナルを受信します。 FreeRTOSのキューでも待てますけれど受信順は送信側がキューに入れた順になりますし、キューの長さを予め決めておくためにそれ以上のシグナルをキューに入れる事が出来ません。 このシグナルは32種類を並列に受け取って好きな順に処理できますし、タスクが受信するまでに同じシグナルが複数個届いていた場合は、1個にまとめて受信します。 なお32種類はunsigned longのビット数です。 これ元ネタはAmigaのアプリケーションが標準的に使うAPIで、内部処理は分かりませんけれど機能面で丸パクリです。 最近までFreeRTOSのキューをAmigaOSのAPIに無理に合わせて使っていました。 ようやく排他制御や(割り込むを含む)例外を把握できたのできちんとしたシグナルを作る事が出来ました。 それでもFreeRTOSの環境下で動作する都合でインコアループするコードが残りましたが、いずれFreeRTOSを使わなくなった際に解消するつもりです。

図解

1つのタスクに対し複数のタスクや例外からシグナルを送信した状態です。 シグナル機構の図解 送信側は32ビットのどれか1つを予め割り当てられています。 送信はタスクが持つ変数にそのビットを立てます。 受信のタスクは変数を参照してシグナルの発生を把握、刈り取ります。 同じ変数を複数のタスクが何の配慮もなく参照/更新すると、更新が失敗しますから排他制御を使って1つのタスクだけが一度に参照/更新をするよう調整してあります。 例外は同じ優先度に設定してあるので(FreeRTOSが使うSysTick、PendSVは更に低いです)例外同士での競合は発生しません。 ただし例外はタスクの更新と競合しますから、タスク側が更新する変数を提示して例外は別の変数を参照/更新します。

ソース

ソースはこんな感じ(抜粋)。 ksrk_waitはシグナルを受け取り、ksrk_triggerはシグナルを送ります。 他の例外をマスクしないで処理します。といってもほぼ全ての例外が同じ優先度なのでメリットはありません(苦笑)。
uint32_t ksrk_wait(uint32_t uSignals)
{
	// 割り込み番号を取得(0=スレッドモード=割り込みなし)
	uint32_t psr = 0;
	__asm volatile ("mrs %0, psr" : "=r" (psr));
	KSRK_ASSERT(0 == (psr & 0xFF));

	KSRK_TASK *pTask = ksrk_get_tcb();

	uint32_t get_signals = 0x00000000;

	// シグナルがない場合
	while (!get_signals)
	{
		// 排他制御開始
		ksrk_exclusive_entry(&pTask->signal_exclusive);

		// インデクスは0のはず
		KSRK_ASSERT(!pTask->signal_index);

		// インデクスが0なので、例外はシグナル1に追加する

		// シグナル0を刈り取り
		uint32_t signals_0 = pTask->signal_0 & uSignals;
		pTask->signal_0 &= ~signals_0;

		// インデクスを0→1に切り替えて例外にシグナル0へ追加させる
		pTask->signal_index = 1;

		// シグナル1を刈り取り
		uint32_t signals_1 = pTask->signal_1 & uSignals;
		pTask->signal_1 &= ~signals_1;

		// インデクスを0に戻し、例外はシグナル1に追加する
		pTask->signal_index = 0;

		// 排他制御終了
		ksrk_exclusive_exit(&pTask->signal_exclusive);

		get_signals = signals_0 | signals_1;

		// シグナルがない場合
		if (!get_signals)
		{
			// 他のタスクにCPUを譲る
			vTaskDelay(0);
			// ラウンドロビンか、他のタスクからCPUを譲ってもらうと戻ってくる
		}
	}

	// 刈り取ったシグナルを返す
	return get_signals;
}

void ksrk_trigger(KSRK_TASK *pTask, uint32_t uSignal)
{
	// タスクとシグナルを指定したはず
	KSRK_ASSERT(pTask);
	KSRK_ASSERT(uSignal);

	// 割り込み番号を取得(0=スレッドモード、他=ハンドラモード)
	uint32_t psr = 0;
	__asm volatile ("mrs %0, psr" : "=r" (psr));

	// スレッドモード(タスク走行中)の場合
	if (!(psr & 0xFF))
	{
		// 排他制御開始
		ksrk_exclusive_entry(&pTask->signal_exclusive);

		// インデクスは0のはず
		KSRK_ASSERT(!pTask->signal_index);

		// シグナル0に追加
		pTask->signal_0 |= uSignal;

		// 排他制御終了
		ksrk_exclusive_exit(&pTask->signal_exclusive);
	}
	else if (!pTask->signal_index)
	{
		// ハンドラモードでインデクス0の場合
		// シグナル1に追加
		pTask->signal_1 |= uSignal;
	}
	else
	{
		// ハンドラモードでインデクス1の場合
		// シグナル0に追加
		pTask->signal_0 |= uSignal;
	}
}

今回の鉄ゲタは…

ありません、ほっ。