復刻:キーワードガイダンス第15回「インスタンス」2014年05月13日 21時01分20秒

初出:技術評論社「組込みプレスVol.17」(平成21(2009)年11月13日発売)

●モノの実体としてのインスタンス

お弁当屋さんでお弁当を買うことを考えて見ましょう。

メニューには幕の内弁当、から揚げ弁当、コロッケ弁当があるとします。
Aさんは幕の内弁当、Bさんはから揚げ弁当、Cさんはコロッケ弁当を購入しました。
それぞれ持ち帰ることが出来るお弁当の実体は別々に存在します。

一方、幕の内弁当にはから揚げとコロッケが含まれます。
から揚げ弁当にはから揚げ、コロッケ弁当はコロッケがそれぞれお弁当のおかずの主役です。
幕の内弁当に入っているコロッケとコロッケ弁当に入っているコロッケは同じ種類のコロッケで別々に実体が存在します。

「お弁当」という枠組みでの実体とお弁当の中身の「おかず」としての実体はどちらもインスタンスの具体例となります。
また、インスタンスの捕らえ方の範囲について、お弁当という中身の詳細を問わない概念、おかずの組み合わせに着目した概念、お弁当のおかず単体に
着目するような概念など段階があることも分かります。
この概念的な段階を「抽象度」とここでは定義しておきます。

●オブジェクト指向から独立したインスタンス

以上のようなたとえ話はインスタンスという言葉とともにオブジェクト指向の説明として語られることが多いようです。

また、言語処理系、例えばC++やJavaなどに依存して語られることも一般的です。
ですが、インスタンスという言葉をオブジェクト指向とは無関係に使うこともできます。
例えば、リアルタイムOS上のタスクとオブジェクト指向のオブジェクトは概念も実装方式も異なりますが、タスクの実体を指すときにインスタンスという言葉を使った方が大雑把な概念的な理解のきっかけとなります。

「タスクはメソッドだけを実体化したインスタンス」

というようなオブジェクト指向風に大胆に簡略化した説明を行った方が、大筋では会話が成立してしまいます。
本当はリアルタイムOSの仕組み上のTCB(タスク・コントロール・ブロック)の説明とレジスタ退避、スケジューラによるコンテキストスイッチなどの正確な説明を行う必要があるのですが、全体的な設計上の概要把握だけが目的の場合はOSの仕組みまでは理解していなくても話を進められます。

つまり、オブジェクト指向用語が他の実装や設計に対しての「たとえ話」に使えるようになってきたということです。
また、このようなアプローチをすると、リアルタイムOSのタスク生成における「同一機能タスクの複数生成」という概念も説明しやすくなります。
なお、お弁当の例は振る舞いのないデータだけのインスタンスのたとえ話でしたが、タスクの例は振る舞いだけのインスタンスのたとえ話になります。
リアルタイムOSでは振る舞いのインスタンスとデータのインスタンスは別々に管理されています。

●組込みシステム特有の問題

これまでのたとえ話のインスタンスをソフトウェア実装の視点で見れば全てメモリ領域に独立して存在するデータの集合と言うことが出来ます。
おかずを表す変数データとおかずやご飯を格納する「容器」は全てメモリ上のデータやデータの並びになります。
オブジェクト指向言語仕様上はこのメモリ確保と管理の仕組みをどのように表現するかということに重きが置かれています。
ですが、どのメモリにインスタンスを配置するかということはあまり議論されません。
インスタンスを高速なSRAMに配置するのか、普通のDRAMに配置するのか、もしくは静的にインスタンスを生成した上でROMに配置するのか、などの議論です。

これらの議論は組込みシステムでは必要な場合があります。
組込みシステムではお弁当の容器を選択したい場合があるからです。
プラスチック容器、紙の容器、竹の容器、持ち込みの自分用の容器など出来合いのお弁当屋さんの既定容器以外の容器が使える必要があります。
つまり、メモリ配置について意図的に指定する必要があるということです。
単なるクラス全体のメモリ配置の問題だけなのであれば、メモリ配置のためのコンパイラの拡張書式を使えば解決します。
通常、組込みシステム用のコンパイラにはこのような目的の拡張書式があります。

但し、クラスや機能全体ではなく一部の特定部分だけが性能上の問題になる場合には、抽象度を上げた実装をわざわざ解体し、抽象度を低くした上でメモリ配置を変える必要があるかもしれません。

復刻:キーワードガイダンス第14回「多重割り込み」2014年05月11日 02時40分14秒

初出:技術評論社「組込みプレスVol.16」(平成21(2009)年8月28日発売)

●順番待ちの仕組みと処理の順番
変わったハンバーガーショップを考えて見ます。
レジ待ちの列はA列、B列、C列の3列としましょう。
レジ係は1人しかいません。
同時に複数の列にお客が並んだ場合はA列>B列>C列の順に接客します。
レジ係は注文を聞いて会計を済ませて調理係に依頼すると次の客の注文を取ります。
会計済みで注文品受け取り待ちの客には列の脇に避けてもらいます。
注文品は調理係が準備を済ませると受け取り待ちの客に渡します。

●多重割り込みの二つの例
ある時A列とC列に同時に客が並びました。
レジ係はA列に客が並んでいるのでC列の客は待たせたままA列の客の注文をとります。

(a) 注文品の単価と個数のレジ入力
(b) 客からお金を受け取る
(c) 調理係に注文を伝える
(d) 客にお釣りを渡す

上記が済んだら次はC列のレジの処理のはずですが、
気付いたらB列に客が並んでいます。
レジ係はC列の客は待たせたままでB列の注文を取ります。
このように「多重割り込み」には大きく分けて二つの場合があります。

(1) 同時に二つ以上の要因の割り込みが発生した場合
(2) 一つの要因の割り込み処理中に別の要因の割り込みが発生した場合

この例は多重割り込みの二つの例の複合例です。

●割り込み優先度と割り込み禁止区間
割り込み優先度は同時に割り込みが発生した時の処理の順番の約束事に過ぎません。
仮に、優先度が低いC列の会計処理中に優先度の高いA列に客が並んでも割り込み禁止のままの場合は無視するしかありません。
何が何でもC列の処理を中断してA列の処理を「優先する」という意味の優先度ではないのです。

但し、以下のような工夫は可能です。

レジ係の手順を以下のように変更します。
(a) 注文品の単価と個数のレジ入力
(b) 優先度の高い列に目を向けて客がいればそちらの注文取りを先に行う
(c) 客からお金を受け取る
(d) (b)と同じ
(e) 調理係に注文を伝える
(f) (b)と同じ
(g) 客にお釣りを渡す

最初の手順では(a)~(d)の区間を「割り込み禁止区間」としています。
一方、あとの手順では(a)の区間だけを「割り込み禁止区間」とします。
注文を忘れないためのレジ入力が済んだ時点までを割り込み禁止という扱いにしたからです。
一部のワンチップマイコンの割り込みコントローラは割り込みが発生すると最初に割り込み禁止にした上で、その時点で一番優先度の高い割り込み処理に
CPUを割り当てます。
割り込み処理の任意の部分で意図的に割り込み許可にしない限り、優先度の高い割り込みが優先度の低い割り込み処理中に発生しても優先度の高い割り込み処理は実行されません。

●割り込み保留可能時間と割り込み禁止時間
通常、割り込み要因の発生には周期性があります。
その周期未満の時間は割り込みが発生したことを保留にして他の処理を行うことが出来るのです。これを「割り込み保留可能時間」といいます。
「割り込み禁止時間」は「割り込み保留可能時間」を越えないように実装する必要があります。
これは多重割り込みが発生する可能性も含めて全ての組み合わせで条件を満たす必要があります。
なお、割り込み要求はレジスタに保持されますが、その情報をクリアしないと次の割り込みは発生しません。

●同じ要因の多重割り込み
実際にはそれぞれの列に複数の客が並ぶことは出来ません。
もし、割り込み処理中の割り込みと同じ要因の割り込みが発生する場合はシステムの要求スペックをハードウェア仕様が満たしていないことになります。
したがって、ハードウェア設計上そのような処理を行う場合の仕組みについて検討する必要は全くありません。
逆にソフトウェアでは工夫を行って、割り込み禁止時間を短縮するようにします。
ハードウェアで工夫を行う例としては、受信バッファ1段のチップからFIFOバッファを持つようなチップに変更するようなことが考えられます。
FIFOバッファ分の受信データが溜まるまでは割り込みを保留に出来るため、割り込み保留可能時間が延びて、CPUには余裕を持たせられるからです。

復刻:キーワードガイダンス第13回「リード・モディファイ・ライト」2014年05月10日 01時04分18秒

初出:技術評論社「組込みプレスVol.15」(平成21(2009)年5月12日発売)

●内容の不定な上書き
ある会社では社員の行動予定の管理を共有エリアに置いた「予定表」というEXCELファイルにより行っていました。
社員全員の行動予定が記入できるようになっているファイルです。
このファイルの書き換えは基本的にはAさんだけが行う約束です。
社外からは電話をAさんにかけて予定表を書き換えてもらいます。

さて、Aさんはこの予定表の書き換えを以下のように行っていました。

(1) 共有エリアのファイルを自分のパソコンにコピーする。
(2) 自分のパソコンにコピーするのは出社直後の9時過ぎ
(3) 電話連絡は概ね10時前には終わるので、10時までは自分のパソコン上でファイルを修正します。
(4) 念のため10時過ぎまで待って、10時10分頃、自分のパソコンのファイルを共有エリアにコピーします。

●約束違反
そんなある日、営業のBさんが行方不明になりました。
予定表に何も書かれていないのに、出社してこないのです。
もちろん、Aさんにも予定を告げる連絡は入っていません。
この事件は営業担当Bさんの約束違反によるものでした。
パソコンやネットワークに詳しいBさんは会社の外から社内の共有エリアのファイルを読み書きできるようにしていました。

Bさんは客先に出かける予定をAさんに連絡するのを忘れていたため、当日の朝9時半頃に移動中のパソコンでネットワーク接続して、会社の予定表ファイルを書き換えたのです。

Bさんはこれで連絡できたつもりでいました。
ところがAさんはBさんに書き換えられる前のファイルを9時過ぎには自分のパソコンにコピーしていますから、Bさんの予定が入っていないファイルを編集して、10時過ぎには予定表ファイルを上書きしてしまいました。
当然Bさんの予定内容はなくなってしまいました。
このような経緯により、Bさんは行方不明となったのです。
Bさんの約束違反が全ての原因です。

●本当に約束違反か
実はBさんは、中途入社後半年も経っていたのですが、Aさんしか予定表ファイルを修正してはいけないという「約束事」を知らされていませんでした。
先輩社員はみんな「当たり前のこと」として周知すべき項目から抜けていたからです。
とりあえず、Aさんに連絡を入れるように、ということだけは聞かされていました。
そんなBさんは、いつもはAさんに予定の連絡はするものの、それは単なる「慣習」程度にしか考えていなかったのです。
実はBさんはこの半年の間、直接予定表ファイルを修正してしまっていました。
Bさんが予定表ファイルを修正するのは大抵、帰宅前。17時頃でしょうか。
そんな行き違いもあり、事件は発生したのです。

●リード・モディファイ・ライトとは
以上の事例がリード・モディファイ・ライトの全てです。
要するにリード・モディファイ・ライトは以下のように整理できます。

(1) データを別の領域にコピーする(リード)
(2) コピーしたデータの内容を修正する(モディファイ)
(3) 修正した内容のデータを元のデータに上書きする(ライト)

この操作を行うのが一つのタスク、一つの割り込み処理など、同一コンテキストだけであれば問題ありません。
問題が発生するのは複数のタスク、複数の割り込み処理で同一のデータの書き換えを行う必要がある場合です。

●リード・モディファイ・ライトの注意事項
以下の様なデータに対してはアクセス時の十分な注意が必要です。

・CPUの制御レジスタの書き換え
・ワンチップマイコン内蔵のI/O制御用レジスタなどの書き換え
・タスク関で情報伝達するために用意したグローバル変数の書き換え

リード・モディファイ・ライトが回避できないような処理にせざるを得ない場合には以下の対応策が必要です。

・リード・モディファイ・ライトを行う区間を割り込み禁止にする
・セマフォなどを使って、データの排他制御を行う
・アクセス関数を用意して、その関数経由でしかデータを操作できないようにする

リード・モディファイ・ライトが原因の組込みシステムの「原因不明の誤作動」は枚挙に暇がないくらいです。

復刻:キーワードガイダンス第12回「assert禁止」2014年05月09日 00時03分59秒

初出:技術評論社「組込みプレスVol.14」(平成21(2009)年2月21日発売)

●論理エラー検出手法
C言語およびC++言語のプログラミングテクニックの一つとしてassert()マクロの利用があります。
例えば、ポインタを引数とする関数を利用する場合、引数となるポインタがNULLではないということを「期待」しているような場合、以下のようになります。

void funcp ( void * p )
{
  assert( NULL != p );
  //ポインタ正常時処理
}

このように作られた関数に対して引数にNULLを与えて関数コールすると以下のようなエラーメッセージが出力されます。

atest: asserttest.c:22: funcp: Assertion `((void *)0) != p' failed.


assert()マクロはこのように論理的に期待している状態と異なる状態を検出してプログラムの実行を停止させることが出来るのです。

非常に便利な機能であることは確かで、デバッグ後には「-DNDEBUG」をCコンパイラの引数に指定するなどの方法で簡単にassert()が生成するコードを削除することができます。
ソースコード上にassert()を挿入して単体テストの効率を上げるというような発想の下に積極的な活用が薦められることも少なくありません。

●性善説のassert
先の例のようにポインタがNULLになるような条件というものは一過性のデバッグ時期にのみ発生する不具合と言い切ることが出来るでしょうか。
呼び出し側で値を保証すべきであり、呼び出された側ではそのようなチェックを行う必要は本当にないのでしょうか。

これは不具合のないプログラム実装が行われるべき、という考えが前提の「性善説」が基本になっているという言い方もできます。
仮想記憶システムではない環境では複数のタスクから共通のRAM領域へのアクセスが可能であることから、関係のない処理の想定外の不具合によって自関数のみが使用しているはずのポインタ変数がクリアされるような現象が発生しないとは言い切れません。

「いつの間にか」、「突然に」、「想定外に」NULLに変わってしまうというような現象が発生することになります。
assert()でチェックしている「デバッグ時期」には現象が発生せず、assert()を無効にした「リリース後」に発生する不具合かもしれません。

●assertとfail-safe
一方で、組込み機器は実行時に不正な状況に陥ってもシステムを停止させずに安定稼動させる必要があるという命題を持っています。

「デバッグ時にのみ存在する不正な状態」とは限定すべきではなく、「常に不正な状態を回避する実装」が要求されます。
いわゆるfail-safe(フェイルセーフ)の概念が必要で、不正な状況に陥っても単純にプログラムを停止させて「プログラマに知らせる」のではなく、安全な状態でシステムを落ち着かせる必要があります。

この意味においてはassertの概念と組込みシステム開発におけるfail-safeの概念は根本から食い違っていると言えます。
したがって、デバッグ時にのみ論理的に不正な状態を検出するassert()は組込みシステム開発では使用すべきではなく、常に論理的な不正を検出して対応を行うための機能をリリース版として実装する必要があるわけです。

最後に先ほどの関数のassert()を使わない場合のコードは以下のようになります。

void funcp ( void * p )
{
  if( isvalid(p) ) {
    //ポインタ正常時処理
  }
  else {
    //ポインタ不正時処理
  }
}

復刻:キーワードガイダンス第11回「ハイブリッドOS」2014年05月08日 22時55分51秒

初出:技術評論社「組込みプレスVol.13」(平成20(2008)年11月7日発売)

●いいとこどり
「ハイブリッド」という言葉が一般的に使われるようになった代表としては「ハイブリッドカー」(Hybrid Car)が挙げられます。
電気で動作する「モーター」とガソリンなどの燃料を使った「エンジン」(内燃機関)の複数の動力源を使い分けるなど、複数の技術を応用した車のことです。
エンジンとモータを駆動源として切り替えるタイプとエンジンを単なるモータ駆動のための発電機として使うタイプに大きく分けられます。
前者を「パラレル方式」、後者を「シリーズ方式」と呼びます。
ハイブリッドカーはエンジンと電気モータの「いいとこどり」をした車ということができます。
同じように開発効率および移植性の高さとリアルタイム性能の追求という二つの「いいとこどり」を狙った技術が「ハイブリッドOS」です。

●ハイブリッド化の意義
ハイブリッドOSはひとつのプロセッサでμITRONとLinuxを共存させたり、μITRONとWindowsCEを共存させるというようなことが行われます。
つまり、リアルタイム性能が高いとされる、μITRONと他のOSとの組み合わせで語られることが多いようです。
この場合のLinuxまたはWindowsCEなどのOSをここでは便宜的に「ゲストOS」と呼びましょう。
このようなことを行う視点は二つあります。

(1) μITRON資産重視
これはμITRONで開発した資産をそのまま使い、μITRON上で対応していないプロトコルスタックなどの追加開発を行うのが大変という場合。
この大変な開発がゲストOSでは既に解決済みであることが重要です。
TCP/IP関連のプロトコルスタック、ファイルシステムとの連携などについて目を向けられることが多くなります。

(2) リアルタイム性能補完重視
ゲストOSでの豊富な開発資産があるが、製品性能的にリアルタイム性能が要求されるイベント処理を追加実装したい場合。

●ハイブリッド化が必要なシステム
(1)の視点でハイブリッド化を行う場合、既にリアルタイム性能が要求されるデバイス制御を伴った実装が行われているということが重要です。
つまり、ベースシステムがそれほどリアルタイム性能を追求していないμITRONシステムなのであれば、ハイブリッド化するまでもなく、ゲストOS環境に乗り換えれば済む話です。

(2)の視点でも同様の側面があります
リアルタイム性能を引き上げたいというのは既存の機能の性能改善という意味ではなく、独立したハードウェアイベントの処理について語る必要があるというところが重要です。
つまり、例えばTCP/IPの通信性能を上げたいとか、WWWサーバの応答性を上げたいという意味のリアルタイム性能補完ではなく、特定の追加デバイスの割り込み応答性能がLinuxのデバイスドライバでは満たせない、というような場合にのみ有効だということです。
そのような特殊デバイスや特殊イベント処理の対応予定もないのにハイブリッドOSを使う必要はありません。

●ハイブリッドOSの実装の基本は「パラレル方式」
以上のようにハイブリッドOSの利用目的のポイントを考えるとハイブリッドカーでの「シリーズ方式」は選択肢からは外れます。
μITRON上でイベント検知してその応答処理をLinux側のプロセスで処理するというような構成はあまり意味がないからです。
リアルタイム性能が要求されるデバイスに対する処理は全てμITRONで完結させて、WWWサーバ経由での表示処理などの少しくらい遅れても構わない処理のためにその情報をμITRONからLinuxのプロセスに通知するというような使い方になります。
このような目的を踏まえてハイブリッドOSには主に以下の機能が実装されます。

(a) ゲストOS起動機能
(b) 割り込み調停機能
(c) OS間通信機能

割り込み調停機能部分でデバイス単位の割り込み処理対象OSを確定するようにパラレル制御します。