前回まで「組込ソフトウエア開発概説」として組込機器のソフトウエア=ファームウエアの構成要素について一通り書いてきましたが、そちらでは書ききれなかったことや、後から思いついたことなどをときどきスポットでも書いていきます。
今回の話題は、「タスクのスタックサイズをどう決める?」です。
まず、ファームウエアを「iTRON OS + デバイスドライバ・ミドルウエア + 自社開発したアプリケーション」という王道とも言える構成とした場合、ファームウエアの中には「タスク」という一連の処理を繰り返し行うプログラムがたくさんあって、それらは並行して動きます。OSの回(https://qiita.com/Cente_mw/items/fadfd723ed8e8b81ce13 )でも触れましたが、同時に複数の処理を並行実行させたいのであれば、iTRON OSのタスクを使った方がプログラムの見通しと切り分けが良くなります。
タスクについて、もう少し正確にはiTRON仕様書(https://www.tron.org/wp-content/themes/dp-magjam/pdf/specifications/ja/TEF024-S001-04.03.03_ja.pdf )の第3章 3.1 (1)に以下のような説明があります。
プログラムの並行実行の単位を「タスク」と呼ぶ.すなわち,一つのタスク中のプログラムは逐次的に実行されるのに対して,異なるタスクのプログラムは並行して実行が行われる.ただし,並行して実行が行われるというのは,アプリケーションから見た概念的な動作であり,実装上はカーネルの制御のもとで,それぞれのタスクが時分割で実行される.
つまり、並行処理は見た目のお話で、実際には「こっちのタスクをちょっと実行して、その状態や変数内容を一時的にメモリに記録して、次のタスクの状態や変数内容をメモリから読み出して前回止まったところから再開...」ということを繰り返しているわけです。OSなし環境だとこれに近いことを自分で実装することになるので実装難易度が高い、ということをご理解いただけるかと思います。
タスクについてここではこれ以上踏み込みませんが、ファームウエアエンジニアとしてはそれぞれのタスクの情報を一時的に保持するメモリ=スタックのサイズをどうするかが迷いどころだったりします。組込機器向けCPUでは内蔵RAMも256Kbyteなど、それほど大きくありません。無駄な「余裕」は極力排して、将来の機能追加ができるような余裕を残しておきたいところです。ネットワーク通信機能を持った組込機器ではタスクが数十になるケースも多く、何も考えずに全タスクに8Kbyteのスタック割り当てなんてことをすると30タスクあったらそれだけで240Kbyteも使ってしまいます。
基本的には以下の合計に余裕を持たせた値にします。
A タスクプログラムおよびそこから呼び出す関数のオート変数サイズの合計
B OSがタスクで作業領域として使用するメモリサイズ
C 呼び出すiTRON OSのAPIで消費するメモリサイズのうち最大のもの
D 呼び出すデバイスドライバ・ミドルウエアのAPIで消費するメモリサイズのうち最大のもの
Aは自分で数えれば良いですが、B~Dはそれぞれのユーザマニュアルに記載があるはずです。特にない場合はそれほど大きく消費しない(感覚としては高々100byte程度)と考えて良いと思います。それで自社アプリケーションタスクのスタックサイズを例えば1024byteとした場合、それが本当に正しいのか確認しましょう。開発ツールによっては、スタック消費量を算出する機能がありますので探してみてください。
消費スタックサイズの計算方法の基本的な考えとしては、最初から各タスクのIDなどでスタック領域を埋めておき、一通り動作させたあとでスタック領域のどこまで別の値に変わったかを調べます。もちろん、偶然同じ値に上書きしたケースや、確保したがその動作では書き換えしなかったケースは検出できませんが、そういうレアケースは長期間運用テストなどであぶり出しましょう。
★Centeでは...
前回の記事でご紹介したシェルのコマンドの一つとして「tsk」を用意しています。上記の方法でスタックサイズと消費パーセンテージをタスクごとに一覧表示できますので、ファームウエアを実行させながらときどきtskコマンドでスタック消費度合いを確認することができます。これほどまでに我々はスタックサイズにおびえているのですが、それはスタックオーバーフローによる症状が「初見殺し」と言えるほどいやらしいから、という理由も大きいです。
スタックオーバーフローになったらまず間違いなくファームウエアが「落ち」ます。プロテクトメモリではない組込環境では、スタックオーバーフローは隣のタスクのスタックを浸食することになりますので、全く関係のない隣のタスクの処理が不正になって落ちます。
落ちたという現象だけから追っても、あるタスクが突然内部変数が不正になって落ちたことしかわかりません。しかもその関係ないタスクをステップ実行させるなどするとスタックオーバーフローになるタイミングも変わって、やるたびに落ちる行が変わったり、しまいにはステップ実行では落ちなくなったりします。
「何かの処理を追加したら、わけのわからない落ち方をするようになった」という話を聞いたら、我々は真っ先にスタックオーバーフローを疑います。「どのタスクが落ちたか」よりも「その隣のタスクは何か」「そのタスクに処理を追加したか」を追うと、スタックオーバーフローを見つけられるかと思います。
ただ、タスクに処理は追加していないものの、呼び出すライブラリ関数をVer.Upしたらよりスタックを消費するようになったというケースもあるかもしれません。
結局、調査結果の倍くらいのスタックサイズにしておくと安心度は高くなりますが、一方で冒頭に書いた通り必要最小限に抑えたい要求も高く、面倒な問題の一つであることは確かです。
■今日の閑話
ファームウエアがなんらかの原因で無限ループに入ったり例外に飛んで通常動作しなくなることを指して、みなさんはなんと表現しますか?私は前述の通り「落ちる」派です。たぶん他にも「クラッシュ」「フリーズ」「死ぬ」などいろいろな呼び方があるでしょう。ミドルウエアサポートのお仕事をしているとお客様によって表現が違っていますが、まずは具体的にファームウエアのどこでどういう状態になっているかを確認していただくところから始まります。Cente:
https://www.cente.jp/
お問合せはこちら:
https://www.cente.jp/otoiawase/