タスク設計とリソース管理を極める

図
図:M4を6コア搭載し、扱いやすい開発環境を提供する「SPRSENSE™」

現在、私たちの身の回りにある多くのシステムは、複数の作業を同時に、もしくは短時間に並行して切り替えながら実行するマルチタスクシステムが採用されています。画像処理機能、通信制御機能、組み込みAIによるデータの分析機能など、多種多様な機能(タスク)が繋ぎ合わされることによりシステムとして優れたユーザーエクスペリエンスを達成しています。

今回の初心者講座では、複数のタスクを連携させるための「タスク設計」方法と、タスク間で情報を交換するための領域の扱いである「リソース管理」について解説します。ソニーのSpresenseを具体例にマルチタスクシステムを学びましょう。

▼目次

タスク設計とリソース管理が必要な理由

現在、組み込みシステムへの要求は日々高度化しています。そのため製品開発では、単機能のシステム設計ではなく、多種多様な機能を統合した多機能のシステム設計が必要とされています。また、ユーザビリティの観点より高いレスポンス性も求められており、データを解析しながら、外部との通信やUI制御を同時に実行する並列処理も不可欠です。そのため、タスク設計や、マルチタスクとASMP(マルチコアアーキテクチャ:第1回を参照)を協調動作させるための設計は重要な課題です。

加えて、リソース管理も重要な課題です。リソースとは、それぞれのタスクを実行時に占有し利用するハードウェア資源です。リソースには、メインメモリをはじめ、GPIO、I2C、SPI、タイマー、PWMコントローラ、カメラモジュールなど様々なものがあります。同時に何個のタスクを並列実行できるかという視点からCPUもリソースとして扱われることがあります。もちろんリソースが多ければ多いほど、高度なシステムを構築することができます。しかし、多くのハードウェアを搭載すると原価が高騰するため、製品開発では必要最低限のハードウェアのみを搭載し、これらをいかに有効活用するかという「タスク設計とリソース管理」の技術が求められます。

図

図
図1:Spresenseに統合された1.5MBのSRAMをいかにして効率的に活用するか

タスク設計のコツ

タスクの構成(SupervisorタスクとWorkerタスク)

ASMP上のマルチタスクシステムでは、メインコア上のタスクがシステム全体を管理し、サブコアのタスクはメインコアから指示されたリソースのみを使って動作します。Spresenseの場合、メインコア上で実行されるタスクを「Supervisorタスク」、サブコア上で実行されるタスクを「Workerタスク」と呼びます。図2はプログラムを実行中のSpresenseにデバッガを接続し(図3)、プログラムの先頭で全コアを停止した状態を示しています。6タスクのうち、Thread 1がSupervisorタスク、Thread 2〜6がWorkerタスクとなります。WorkerタスクはSupervisorタスクからの指示を受けるまでは休止し、依頼を受けてはじめて処理を開始。要求された機能を実行します。

Eclipse IDEでのデバッグ方法

図
図2:Spresenseの開発環境「Eclipse CDT」で実行中のプログラムを停止した様子。6個のスレッド(実行中のタスク)を見ることができる

図
図3:Spresenseに接続したICEデバッガ(LPC-Link2)の接続

タスクを定義する際の考え方

Workerタスクに割り当てる機能は、以下の指標に従って定義すると良いでしょう。

大量のリソースを扱う処理を複数コアで分担し合う

例えば、256KBの画像データを扱うアプリケーションを構築する場合、Supervisorタスク1個のみで処理すると256KB全てを直列に処理することとなります。一方で、Workerタスク4個に同じ機能を持たせ、それぞれに画像を4分割したデータを解析させることにより、約1/4の時間で処理することができるため、高いスループットと高い応答性を達成可能です(第6回で実際の実行方法を紹介します)。

図
図4:大きな処理を複数のタスクで分担する

リアルタイム性が求められる機能をコアに占有させる

例えば、ビデオカメラ・アプリケーションは「撮影した映像をディスプレイに表示する」「SDカードに漏れなくデータを記録する」「画像中の顔位置を検出しフォーカスを調整する」といった機能から構成されています。このうちSDカードへデータ書き込み処理は、決してデータを取りこぼしてはいけない、デッドラインが厳密に決まった(ハードリアルタイム)処理です。この要求を満たすためには、必ず処理が一定時間内に完了するよう、Supervisorコアにはカメラモジュールの制御処理とSDカードへの記録処理に専念させ、Workerコアには顔位置検出など比較的リアルタイム性の低い処理を任せる役割分担が適切です。このように機能の特性を考慮することも、タスク設計に必要な指標のひとつです。

図
図5:リアルタイム性の高い機能をタスクとして分割する

リソースを共有するタスクを連携させるための機能

次に、SupervisorタスクとWorkerタスクが連携するためのハードウェアを紹介します。

SPRESENSEでタスクの連携に使うことのできるNuttXのインタフェース

ミューテックス(Mutex, Mutex-Lock, Binary-Semaphore)

SupervisorタスクとWorkerタスク、または、Workerタスク同士が、1つのリソースへ同時にアクセスするとデータの一貫性が崩れてしまいます。これを防ぐためSpresenseは同時に1個のタスクのみが占有できるハードウェアのフラグ「Mutex」を提供しています。各タスクがリソースを参照・変更する前に、事前に定めたMutexの確保にトライ(TryLock)することで、リソースへの同時アクセスを防ぐことができます。

図
図6:ミューテックス

メッセージキュー(Message Queue)

Message Queueは、SupervisorタスクからWorkerタスクへの通知や、WorkerタスクからSupervisorタスクへの返答に利用できる、短いデータを扱うためのFIFO(First-In-First-Out)バッファです。主に、Workerタスクの開始時に、SupervisorタスクからWorkerタスクへパラメータ(メモリのどこにあるデータを解析するか、何バイト解析が必要か、など)を指示するために利用します。なお、SpresenseではWorkerタスクから別のWorkerタスクへのMessage Queueを確立することはできません。

図
図7:メッセージ・キュー

共有メモリ(Shared-Memory)

Shared-Memoryは、タスク間で大きなデータ(画像データや録音データなど)を共有できるハードウェアです。Spresenseのメインメモリ(1.5MBのSRAM)は128KBのメモリタイル12個から構成されており、複数のコアから同じメモリタイルを共有できる仕組みを備えています。これにより、データの詰まったメモリタイルをコアに割り当てるだけで、128KBのデータをタスク間で受け渡すことが出来ます(複数のタイルを同時に付け替えることにより128KB以上の転送も可能です)。

なお、Spresenseは、コアとメモリタイルをクロスバースイッチというハードウェアで結合しています。クロスバースイッチを採用することにより、交差している経路での通信に限定できるため、自在にメモリタイル付け替ながら、従来のSRAMと同等の高いアクセス性能を発揮することができます(メモリタイルへのアクセスが競合した場合のみ、ハードウェアの調停機能が動作し、若干のペナルティが発生します)。

図
図8:共有メモリ

まとめ

今回はタスク設計とリソース管理について解説しました。SpresenseはCortex-M4(FPU搭載)を6コア備え、1.5MBの大容量SRAMを搭載したパワフルなボードコンピュータです。すべてのコア、すべてのメモリタイル、様々なペリフェラルを効率的に活用することで、より高度なアプリケーションを実現することができます。是非、マルチタスクシステムの設計・構築に挑戦してみてください(本初心者講座では、第6回に実際の設計手順を紹介する予定です)。

次回はいよいよSpresenseの開発環境の構築手順と使い方について紹介します。Windows/macOS/Ubuntuのマルチプラットフォームに対応し、Visual Studio Codeの対応も計画されている開発環境です、ご期待ください(図9)。

図
図9:Visual Studio Codeに対応したSpresenseの開発環境(Makers Faier Tokyo 2019にて公開)