Symmetric multiprocessing (SMP) with Raspberry Pi Pico (RP2040)

前回投稿のライブラリを改良して Raspberry Pi Pico 用のSMPタスクスイッチングライブラリを作成してみた。

世界最速のタスク・スイッチ・ライブラリ (Task Switch Library for ARM-G++ [M0/M0+/M1])

コアの排他制御処理の分だけスイッチング速度は少し遅くなってしまったが、それでも1.0usは楽に切っている。

RP2040(125MHz): 82 cycles (0.656us)

[SMPで動作しているところ]
※PicoのSMPでは2タスクが同時に実行状態になる。

前回投稿のライブラリを改良しただけなので実装は比較的楽だったのだがPico特有の落とし穴もあった。arm系にはSysTickと呼ばれるシステムクロックで動作するタイマーがありマイクロ秒以下の時間測定には必要なものなのだがなぜかSDKでは未サポートらしい。とりあえずSysTickを使えるようにしてみたところSMPでは誤動作してしまうという現象が...

SysTickはコア毎に存在しており、実行コア側のSysTickにしかアクセスできない。SMPではタスクが実行されるコアが不定=どのコアのSysTickにアクセスするかわからないということが原因だった。

例えば、次のコードではyield()実行前後で別のコアに切り替わることがある。micros()がSysTickで実装されていて各SysTickの同期がとれていないと仮定した場合、どのコアからアクセスされるかによりmicros()が返す時間情報が前後してしまうことになる。

この問題は、各SysTickを同時スタートすることで解決できる。

具体的な実装は次のメソッドのコードを見てほしい。ちなみに対策後に上の例のmicros()が返す時間情報が前後することがないか1日中ぶん回してみたが問題はなさそうだ。

void Task::begin([[maybe_unused]] bool multicore)
void Task::core1()

また、SysTickと同じくNVIC(割り込みコントローラ)もコア毎なので割り込み処理には特別な注意が必要となる。Picoだと割り込み処理を含むHWドライバー等をSMP対応するのは大変そうというか可能なら割り込みは使わない方が無難な気も...

ちなみに、優先度ベースのRTOSをSMP対応すると優先度が高いタスクの実行中に優先度が低いタスクが同時実行されることがありコア数が多くなるほど優先度ベースである意味が薄れてくることになる。力は技に勝るというべきか...(-_-;)

プログラムの書き込みは以前に投稿したUSBスイッチを使うとUSBの抜き差しなしで出来るので凄く便利だ。参考まで。

USBデバイスの電源を切るスイッチ

[pico_smp.cpp]

[timer_sample.cpp]

[update]
2022-01-13
Alarmクラスの仕様変更とそれに伴う修正を行った。

2022-01-12
タイマークラスの仕様変更と、Sync::sleep()の戻り値でタイムアウト判定が出来るようにしてみた。

2022-01-11
全体的な最適化とsleep()のタイムアウト機能がタスクとの関連性がないというヘンな仕様だったので修正。

2022-01-10
Pico-SDKのタイマーライブラリはコア依存があり扱いが面倒であったため、コアに依然せずに使えるマルチコア対応のタイマーライブラリを作成してみた。これに伴い、Pico-SDKのタイマーライブラリを利用していたコードを削除するとともに時間指定する関数は全てマイクロ秒で統一してみた。
新たなタイマーライブラリはTimer::begin()を呼び出したコア側でのみ割り込み処理を行うが、実行コアを気にせずに使えることやアラーム数が無制限であること、及び、正確なリピート時間を特徴としている。

2021-12-14
SysTick::micros()を割り込み禁止状態からの呼び出しにも対応。

2021-12-10
タスク休止/起床方式のdelay()/delayMicroseconds()のオーバーヘッドが大きく時間が少し不正確な感じなので名称を変更しsleep()/sleepMicroseconds()として新たに関数を追加。今まで通りポーリング方式のdelay()/delayMicroseconds()も利用可能。待つ時間が長い場合はsleep、短い時間であればdelayのように使い分けするのが良さそうだ。

2021-12-09
Task::delay()/Task::dealyMicroseconds()をポーリングによる実装からPico-SDKのalarm機能を利用してタスク休止/起床する実装に改良してみた。おまけとしてget_alarm_pool()を追加。get_alarm_pool()によりSMP環境で実行コアに対して適切なalarm_poolを取得することができるようになる。それと、Sync::sleep()がコンパイラ最適化によりインライン展開されると誤動作するためインライン展開させないように修正。

[Library]