Get in Touch
  • If you’re interested in a Wind River product, simply answer a few questions and we’ll get in touch right away.
  • If you’d like to speak with someone immediately, call our number for sales inquiries below.

    Toll-free: 800-545-WIND (800-545-9463)

【設計しながら学ぶRTOSの基本編】第3回 TCBの設計

こんにちは、南角 です。
前回、タスクの並行性を実現するために重要なデータであるTCB(Task Control Block)の前提を説明しました。
今回はTCBの構造設計に入りたいと思います。
TCBに必要なデータをざっとあげてみると、
まずCPUコンテキスト保存領域があげられます。タスクが一時停止した後に再開するためのCPUレジスタセットです。しかしこれも必ずしも全部保存する必要があるとは限りません。たとえばシステムの初期化時スタートアップで一度設定されて以後、変更されないようなレジスタ、たとえば割り込みベクタの先頭アドレスを保持するレジスタや、データ領域の真ん中あたりを指して、プログラムがデータのアドレスを指定する時に、そのレジスタとの差分(オフセット、ディスプレースメント)の指定のみで指定できるアドレッシング(コラム1参照) のためのベースレジスタなどです。 これらのレジスタはシステム実行中は変更されないため、TCBに保存しておく必要はありません。 浮動小数点演算用のレジスタはどうしましょうか?

次にタスクの状態を示すフラグでしょうか。おそらくタスクの状態とは、TCBがどのキューにつながっているか、たとえばシステムレディーキューにつながっている場合は、実行可能状態、セマフォの解放待ちキューにつながっている場合は待ち状態、で決まるため、特にTCBの中にフラグを設けなくともよいかもしれませんが、いまは最初の構想の段階なので、とりあえず設けておきましょう。
今キューという言葉が出てきましたが、上で述べたシステムレディーキューやセマフォの解放待ちキューには、複数のタスクがつながっている可能性があるので(そのような仕様にするので)TCBの中には、次のTCBへのポインタ領域(**コラム参照)も必要ですね。
このポインタですが、双方向ポインタにした方が便利な場合もありますが、今回は単純なRTOSということで、単方向ポインタにしましょう。
また、モニタやデバッガなどで見た時すぐわかるように、タスクにニックネームが付けられるようにしましょう。RTOS自体はTCBのアドレスで、タスクを区別しますが、さすがに人間はアドレスだけでは、少しつらいですからね。ただこれも単純なRTOSということでアスキー15文字以内(文字列領域は終端のヌルを含めて16文字分)ということにしましょう。
忘れていました、タスクのスタートアドレスや初期優先度、スタックの初期値(スタックのボトムアドレス)も必要です。タスクをスタートする場合はこれらを使用して実行することにします。
ところで優先度はどうしましょうか?優先度変更(優先度を変更するためのシステムコール)は用意したいので、初期優先度以外に実行中の優先度の領域も必要です。
優先度継承はどうしましょうか?単に優先度継承元のタスクのTCBへのポインタ領域をTCBに設けておけばよいでしょうか?しかし優先度を継承して実行中に、さらに高い優先度を継承する場合や継承した優先度を今度はまた別のタスクが継承する場合(優先度の伝播)、にはどうしましょうか?
あるいは優先度継承はあくまでセマフォに関連するので、優先度継承関連の情報はセマフォ構造体に持たせましょうか?
これも様々な実装が考えられます。
ここも単純なRTOSということで優先度継承は1回限りで、伝播もなしという仕様に割り切りましょう。とりあえずTCBには継承優先度領域と継承元タスクへのポインタ領域を設けることにしておきましょう。
それとスリープカウンタ領域も必要でしょう。スリープとは、RTOSに定期的に入れるタイマー割り込み(システムタイマー割り込みと呼びます)の整数回分以上実行を休止する(スリープする)サービス用に使用します。
スリープ中はタスク(TCB)はスリープキューにつながれます。
定周期起動はどうしましょうか?製品においては、定周期起動もRTOSへの割り込みより優先度の高いタイマーデバイスからの個別の割り込みを使用する場合も多いとは思いますが、今回は製品でない気軽なレベルを考えているので、これ用の領域も設けておきましょう。
カウントと言えば起動カウンタ領域はどうしましょうか?タスクへの起動要求は、対象タスクがDORMANT(休止)状態の場合のみ有効である場合が多いと思います。実行中や待ち状態のタスクへの起動要求は無視されるということです。しかし起動要求のあった回数のみ記録しておけば、タスクの終了時に自動的に再起動させることも可能です。ただしこの場合は、TCBに起動要求カウンタ領域を設けるだけでは、起動時にタスクに引数を渡すことができません。
起動要求そのものをキューイングしておきRTOSが処理するような実装も可能ですが、RTOSによるオーバーヘッドが増す可能性があるので望ましくありません。
この連載の初期の頃に書いたと思いますがmtosという世界初の商用RTOSではタスク起動時のさまざまなオプション(20種類ほどだったでしょうか、コーディネーションモードと呼んでいました)が存在しており、それでユーザーに指定させていました。
今回はタスクへの起動要求は、対象タスクがDORMANT状態の場合のみ有効ということで割り切ることにします。
以上の条件での具体的なTCBの構造は、みなさん、次回までに考えてみてください。

コラム1 - ベースレジスタ

プログラムが命令やデータを使用する場合、その命令やデータがある場所を指定しなければなりません。CPUはさまざまな指定の仕方を用意しています。それがアドレッシングモードです。
ここでデータの場所を指定する場合、そのデータがメモリ上にある場合はそのアドレスを指定するのが基本です。しかしすべての場合に、プログラムからメモリ上のデータ場所を指定するのに毎回命令のアドレスを指定するならば、命令自身以外に、32ビットCPUだとすると4バイトメモリがデータのアドレスを指定するために必要になります。つまり個々の命令のサイズが増えることになります。当然CPUの命令の読み込みであるメモリフェッチの回数も余分に必要になります。そこでたとえば配列などのように一連のデータをアクセスする場合は、配列の先頭を適切なレジスタに格納しておいて、配列内のデータアクセスはそのレジスタからのアドレスの差分で指定すれば、命令のサイズもメモリアクセスの回数も少なくすることができます。関数におけるスタックレームもそうです。関数ごとに引き渡される引数と、関数のローカル変数はスタック上に連続して取られますが、その境目にスタックフレームレジスタ(例えばx86であればBPレジスタ)をおいて、引数のアクセスもローカル変数のアクセスも、そのスタックフレームレジスタとの差分でアクセスすることにより、処理速度の上からも様々な利点が得られます。
さらにこれを進めたものがスタックです。この場合のアドレッシングはベースとなるレジスタの指定さえ不要になります、なぜならばスタックポイントレジスタに決まっているからです。その結果命令サイズをますます小さくできます。 話を戻しますが、組み込みシステム全体としてグローバル変数は大きく分けて初期化変数のDATA領域と未初期化変数のBSS領域がありますが、その真ん中あたりのアドレスを、適切なレジスタに入れておけば、グローバル変数のアドレス指定もそのレジスタとの差分で指定できるため、上に書いたような利点が得られます。
最後に補足しておきますが、このコラムの話は基本的にはCISCを前提とした話です、RISCの場合も当てはまる部分もありますが、今回はRISCに関しては省略します。

コラム2 - 自分自身と同じ構造体へのポインタ

みなさんは、もちろんそんな段階はとっくに卒業していると思いますが、最初にC言語を勉強する時に戸惑うのがポインタです。そして構造体の定義の中に同じ構造体へのポインタがある次のような場合にますます戸惑います。

struct TCB {
struct TCB *next; : : };

なんだか再帰呼び出しみたいに思えます。
ここで上記の定義が

struct TCB {
struct TCB next; : : };

であればもちろんだめです。コンパイラがサイズを決めることができません。この場合は本当に終わらない再帰呼び出しのようなものです。
しかしポインタであれば大丈夫です。コンパイラは、この構造体のサイズがわかればコンパイルを進めることができます。ポインタであればサイズが定まります。この変数に値を格納するのはプログラムの仕事です。

設計しながら学ぶRTOSの基本編 第3回 おわり

■著者プロフィール

南角 茂樹(なんかく しげき)

大阪電気通信大学 総合情報学部 メディアコンピュータシステム学科 准教授、同大学院総合情報学研究科(コンピュータサイエンス専攻)、エイシップ・ソリューションズ株式会社 研究顧問。
慶應義塾大学工学部数理工学科卒業後、大手電機会社を経て現職。組み込みシステムおよびリアルタイムOSを専門とし、著書、解説記事、発表・講演、登録特許等多数。

Thank You

Wind River, a wholly owned subsidiary of Intel Corporation (NASDAQ: INTC), is a world leader in embedded and mobile software. Wind River has been pioneering computing inside embedded devices since 1981 and its technology is found in more than 500 million products