Spresense SDK×Visual Studio Code|動画で学ぶマルチコアのデバッグ方法


図

今回の初心者講座では、複数のコア(マルチコア/メインコア+サブコア)上で実際に動作するアプリケーションの基礎を徹底解説。さらには、統合開発環境(IDE)を使ってマルチコア上で動作するアプリケーションを開発し、SPRESENSE上で実行。ICEデバッガにより可視化することで、実際のコアの内部状態までも詳解します。

なお、例題には設計・実装が最もシンプルな「サブコアからメインコアへメッセージを送る」アプリケーションを取り挙げます。動作もソースコードも非常にコンパクトなアプリケーションですが、本講座では「関数を呼び出したので動いた」という表面的な解説ではなく、さらに一歩先へと踏み込み、プロセッサ(CPUコア)の真髄へとフォーカス。指定したプログラムをサブコアが処理する仕組みから、コア間におけるデータの交換や同期の仕組みとその方法、プログラムを高速化する際に注意すべきポイントなど、様々な技術を紹介いたします。やや遠回りに感じますが、音声解析や動画像処理の高速化、複数タスクの共存と行った実アプリケーションを設計する際に、大きく役立つ知識です。是非、最後までお付き合いください。

本アプリケーションは、SPRESENSE Main Board、ICEデバッガ(NXP LPC-Link2)、無償のVisual Studio Codeによる統合開発環境で簡単に実行できます。手元で実際に操作しながら、マルチコアの設計技術を体得していきましょう。

図

▼目次

ASMP上で動作するアプリケーションの構造

まず、ASMP上で動作するアプリケーションの構造について紹介いたします。ASMPのシステムは、システム全体を統括する万能なメインコアと、特定用途(演算処理など)に特化したサブコアから構成されるため、アプリケーションもまた、メインコア用のプログラム/実行ファイルと、サブコア用のプログラム/実行ファイルがそれぞれ必要となります。

本節では、実機を操作する前段階として、プログラムの構成やビルド方法を解説。次に、プログラムがどのようにしてASMP上で実行されていくのかを図解し、最後に、マルチコア化による「デメリット(注意点)」を紹介いたします。プログラムの構造や、高速化でつまずきやすいポイントを事前に把握しておくことで、「マルチコアに最適なプログラム」をスムーズに設計することができるようになるでしょう。

マルチコア・アプリケーションのビルド

ASMPアーキテクチャでは、メインコアの機能と、サブコアの機能が異なります。そのため、メインコアとサブコアそれぞれに実行ファイルを準備する必要があります。ASMPアーキテクチャを採用しているSPRESENSEも同様です。なお、実行ファイルのフォーマットは一般的に「ELF形式」が使われています。

ELF形式|Executable and Linkable Format

SPRESENSEのビルド環境では、図1の流れでプログラムを実行ファイルへと変換します。まず、①サブコアで実行するプログラムをコンパイルし、サブコア用の実行ファイルを生成。次に、②RTOS(NuttX)のプログラムをコンパイルし、メインコア上で動作するOSの実行ファイルを生成します。最後に、③メインコアで実行するプログラムをコンパイルしアプリケーションの実行ファイルを生成。RTOSと結合し、OSとアプリケーションが統合されたnuttx.spk(④)を生成します。

実行の際は、①のバイナリファイルと、④OSとアプリケーションが統合されたnuttx.spkをSPRESENSE上のフラッシュメモリへ書き込みます。再起動後、各実行タイミングにあわせて、RTOS、アプリケーション、サブコアのプログラムが開始されます。

図
図1:ソースコード、Makefile、実行ファイルの関係

メインコア用のプログラムを追加する

メインコア用のプログラムを増やしたい(実行できるコマンドを増やしたい)場合は、工程③で扱う「メインコア用のプログラムを追加」し、メインコア用の実行ファイルを追加するようMakefileにルールを追加します。なお、Makefileはどのプログラムをどのように変換し実行ファイルを生成するかのルールを定めた設定ファイルです。makeコマンドから利用されます。

サブコア用のプログラムを追加する

サブコアを活用した高度なアプリケーションを開発したい場合は、工程①で扱う、サブコア用のプログラムを追加し、Makefileにルールを追記します。面倒な作業に感じるかもしれませんが、Visual Studio Code と Spresense SDKを統合した統合開発環境(IDE)を使うことで、1クリックでプログラムを登録することができます。是非ご活用ください(図2)。

図
図2:Spresense × Visual Studio Codeにより簡単に各コア用のプログラムを追加可能

サブコア上で動作する実行ファイルを生成するためのMakefileは、下記のリンクよりテンプレートを入手することができます。このMakefileでは、まずサブコア上に展開される実行ファイル(ELF形式)を生成し、実行ファイルをディスクイメージ(romfs.img)に格納。実行時にNuttXのファイルシステム/mnt上に展開される構造を生成します。


Worker Task Makefile|examples/asmp
Worker Task Library Makefile|examples/asmp

マルチコア環境でのプログラムの実行

プログラムの呼び出し順序

続いてプログラムが開始される順序(図3)について、解説いたします。

SPRESENSEでは、まずRTOSがシェルプロンプト(nsh)を起動します。①ターミナルにコマンドが入力されると、コマンドに対応する②メインコア用のプログラムがRTOSにより開始されます(Spresense SDKの設定により①と②の動作を自動化することも可能です)。

プログラムがサブコアを利用するタイミングになると、③メインコア上のプログラムがRTOSのASMPインタフェース(API)を操作。RTOSが④ファイルシステム上にあるサブコアの実行ファイルをロード、⑤メモリに展開します。最後にRTOSがサブコアへ⑥実行するプログラムのアドレスを通知し、⑦サブコア上にて、メインコアの期待するプログラムが開始されます。

図
図3:各実行ファイルが開始するタイミング

SpresenseのRTOS(NuttX)では、メインコアはサブコアを「タスク」として管理します。タスクを制御するためのAPIは以下に定義されています。

タスク(mptask)|API仕様

メインコアとサブコアの連携

メインコア上のプログラムと、サブコア上のプログラムは、SPRESENSEに搭載された同期用ハードウェアによって、連携します。「メッセージキュー(mpmq)」「ミューテックス・ロック(mpmutex)」「共有メモリ(mpshm)」が同期用のハードウェアです。全てのハードウェアは、サブコアのプログラムを開始される前に、メインコアがによって初期化される必要があります。それぞれのハードウェアは任意の管理番号によって識別され、メインコアとサブコアが同じ管理番号を使うことにより、通信・連携を行います。

同期用ハードウェアを利用する、最もシンプルなサンプル・アプリケーションは、Spresense SDKに同梱された「examples/asmp」です。本アプリケーションは、図4に示すように、同期用ハードウェアを全て操作し、共有メモリを介して、サブコアが生成したデータ(文字列)を、メインコアへ送付。メインコアのプログラムがRTOSを介してUARTドライバを操作し、USB-UARTポートから文字列を出力します。

図
図4:メインコアとサブコアが同期用ハードウェアにより連携する様子

CPUコアの間で通信・同期するための機能は下記APIとして提供されています。


メッセージキュー(mpmq)|API仕様
ミューテックス・ロック(mpmutex)|API仕様
共有メモリ(mpshm)|API仕様

マルチコア化によるオーバーヘッド

マルチコア(メインコアとサブコアが連携する)システムは、シングルコア(メインコアのみ)システムに比べ「メインコアとサブコア間の通信処理」が追加されています(図5)。これがマルチコア化による通信オーバヘッドです。

開発者は、オーバーヘッド(デメリット)を意識し、「オーバーヘッドに打ち勝てる処理の分配」や「待ち状態のときに何を先行処理するか」などの検討/議論し、マルチコアの並列性/柔軟性といったメリットを引き出します。小さなオーバーヘッドに負けない、大きな実行性能改善です。

図
図5:マルチコア化によるオーバーヘッド

一例として、画像処理プログラムをマルチコア化し、高速化にチャレンジする際の手順を図6に示します。本アプリケーションは、カメラで映像を撮影し、画像処理を行い、処理結果をLCDへ表示します(カメラによる撮影機能をf1(x)、画像処理の機能をf2(x)、LCDへの出力機能をf3(x)として図示します)。全てをメインコアで実行すると、実行時間は(A)となります。

次に、画像処理を並列化しようと考え、画像処理のみをCPU#1へ移動させたとしましょう。この場合、処理時間は(B)となります。メインコアとサブコアが連携のために同期する処理S1(x)とS2(x)が増え、実行時間は長くなります(アプリケーションは遅くなります)。これがマルチコア化によるオーバーヘッドです。

その後、設計・検討の結果、画像処理f2(x)を5コアで並列化できるようになると、アプリケーションの実行時間は(C)となり、最初のアプリケーション(A)より高速になります。

図
図6:マルチコア化による高速化の手順と効果

マルチコア・プログラミング(特にASMPのように演算CPUコアを活用するケース)では「マルチコアを使えるので高速化ができる」のではなく「並列化による性能改善と、同期処理による処理の増加の両方を加味した上で」プログラムを設計する必要があります(このトレードオフについては、後の初心者講座にて取り挙げます)。

メインコアとサブコアが連携するプログラムを作成し、デバッガを使って解析する

Spresense SDKとVisual Studio Codeを使うことにより、クリックのみで、サブコアを利用するタスクを生成することができます(図7:そのままビルド可能です)。生成されるプログラムには、図4に示したマルチコアとサブコアの連携処理が含まれます。

図
図7:Spresense SDKによるASMP対応プログラムの自動生成機能

図
図4(再掲):メインコアとサブコアが同期用ハードウェアにより連携する様子

ポイントとなる処理(ICEデバッガで確認した機能)

本動画では、メインコアがサブコアを制御する様子を紹介しています。メインコアから、サブコア上のタスクの生成と、メインコアとサブコアが連携するための同期用リソース、それらのリソースを活用している処理を以下に抜粋します。

タスクの生成、同期用リソースの生成

	/* タスクの生成(WORKER_FILEにELF形式の実行ファイルを指定) */
	ret = mptask_init(&mptask, WORKER_FILE);
	/* タスクをサブコアへ割り当てる */
	ret = mptask_assign(&mptask);
	
	/* ミューテックス・ロック・リソースを生成し、タスクに割り当てる */
	ret = mpmutex_init(&mutex, SAMPLE_WORKERKEY_MUTEX);
	ret = mptask_bindobj(&mptask, &mutex);
	
	/* メッセージキュー・リソースを生成し、タスクに割り当てる */
	ret = mpmq_init(&mq, SAMPLE_WORKERKEY_MQ, mptask_getcpuid(&mptask));
	ret = mptask_bindobj(&mptask, &mq);
	
	/* 共有メモリ・リソースを生成し、タスクに割り当てる */
	ret = mpshm_init(&shm, SAMPLE_WORKERKEY_SHM, 1024);
	ret = mptask_bindobj(&mptask, &shm);
	
	/* メインコアのアドレスに共有メモリを割り当てる */
	buf = mpshm_attach(&shm, 0);
	
	/* タスクを開始する */
	ret = mptask_exec(&mptask);

同期用リソースを使ったメインコアとサブコアの連携

	/* メッセージキューを使って、サブコアへデータを送信する */
	ret = mpmq_send(&mq, MSG_ID_SAMPLE_WORKER, (uint32_t)data);
	/* メッセージキューを使って、サブコアからデータを取得する */
	ret = mpmq_receive(&mq, &msgdata);
	/* ミューテックス・ロックを使って、共有メモリへの同時アクセスを防ぐ */
	mpmutex_lock(&mutex);
	/* 共有メモリは mpshm_attach で割付済み */
	message("Worker said: %s\n", buf);
	/* ミューテックス・ロックを解除する */
	mpmutex_unlock(&mutex);

まとめ

今回は、ASMPアーキテクチャ/マルチコア上でアプリケーションを構築する際に、欠かすことのできない技術を紹介しました。是非、実機を準備し、お手元で動作をご確認ください。次回は、サブコアをデバッグするテクニックについて紹介いたします。是非、ご期待ください!

Q&A

サブコア用のプログラムが格納されない場合

ASMP用にビルドされたサブコア用実行ファイルは、SPRESENSEに搭載されたSPI接続のフラッシュメモリ(/mnt/spif)に格納されます。

フラッシュメモリはサンプルアプリケーションが生成するデータの格納先としても利用されることから、他のアプリケーションが生成したデータによりフラッシュメモリの空きがなく、図7のようにサブコア用の実行ファイルが格納されない場合があります。

もし、サブコア用の実行ファイルが見つからない場合、df -uコマンドにて空き容量を確認し、不要なファイルをrm /mnt/spif/ファイル名コマンドで削除してください。

ASMPのサブコアのELFファイルがインストールされない|stack Overflow

図
図8:/mnt/spifにサブコア用プログラムが格納される