リアルタイムOSのスタックの使い方

スタック消費量の計算をしないといけないということで、

コンパイラの設定をいじくってスタック消費量を出せるようにしていた。

思ったよりめんどくさい……というかある事情により超めんどくさいことになっている。


最初はスタック消費量なんてどんぶり勘定でいけると思っていた。

どんぶりサイズのスタックを用意しておいて、消費量の見積もりが湯飲み一杯分なので、

割り込みなどで見積もりに入っていないスタック消費があっても余裕余裕と。

ただ、思っていたより消費量が多い処理もあって、真面目に考えないといけないかもと。

果たして、割り込みなどで消費するスタック量はなんぼほどなのか。


そんなわけで使っているリアルタイムOSの資料を見ていたら、

なるほどこんな風にレジスタの退避をしているのかと。

OSでレジスタ退避が必要となるシチュエーションは大きく3つ、

自発的なディスパッチ、割り込み、割り込み後の非自発的なディスパッチである。

その上でレジスタは呼び出し先の関数で壊してよいスクラッチレジスタと、それ以外に大別される。

Cでレジスタを使ってやらかしてる

自発的なディスパッチ処理というのは、システムコールをきっかけに発生するディスパッチで、

tslp_tsk(SLEEP_TIME);
foo();

と、SLEEP_TIMEだけタスクの実行を休むシステムコールを実行して、

復帰後にはfoo(); という処理をする場合、この間にスクラッチレジスタが吹き飛ぶことを前提としてコンパイルが行われる。

なので、他のタスクに切り替える前に保存するのはスクラッチレジスタ以外だけでよいと。


この逆に割り込みで保存する必要があるのは割り込み復帰に必要なレジスタとスクラッチレジスタである。

なぜならば割り込みハンドラの中でスクラッチレジスタ以外を使う場合、

割り込みハンドラの責任で退避・復帰を行うからである。

一般的に割り込み時のレジスタ退避はそういう考えで行われるみたいですね。

で、割り込みハンドラを抜けて元の処理に戻る場合は、

スクラッチレジスタを復帰して、割り込み復帰に必要なレジスタを復元するわけだ。


ところがOSでは割り込みをきっかけにしてタスクの切替を行うことも多い。

タイマ割り込みをきっかけにして優先度の高いタスクが動き出す場合など。

このような場合はスクラッチレジスタ以外も退避する必要がある。

結果的には全レジスタを退避させることになるのだが、

スクラッチレジスタとそれ以外をグループ分けしてモジュール化することで、

多重割り込みなどにも効率的に対応できる仕組みになっている。


今のリアルタイムOSを使用する前に、他のOSのソースコードを見ていたことがあり、

こういう処理ってアセンブラで書くしかないこともあるんだけど、

なんでこういう処理にやっているのか理解しにくくて困った覚えがある。

現在使っているOSでは退避と復帰が一対一対応するようにアセンブラで書かれていて、

いろいろな構成やシチュエーションに対応できるようにモジュール化されていて、

解説も相まってかなり読みやすいなと思った。ありがたいですね。


で、退避させるレジスタのサイズを見たら、案外多いんだよね。

割り込み復帰に必要なレジスタ+スクラッチレジスタ+それ以外のレジスタとフルセットで退避させるとこんなに行くのかと。

冒頭に書いたようなどんぶり勘定が成り立つなら全く問題ないが、

真面目に考えないといけないものもあるかもなぁと。


あと、多重割り込みですよね。これがくせ者である。

割り込みがあるごとにレジスタ退避に割り込みハンドラ実行にスタックを使う。

1回1回が少量ならデフォルトのスタック確保で十分だが、

思っているより多い……となってしまった。

というか、これは設計思想が合ってない気がするな。

移植という性質もあり、今さらどうしょうもないんだけどさ。


で、これを書いているときに計算の設定ミスに気づいてしまった。

もうちょっと厳密に計算できるが、計算結果が増えるか減るかわからん。