南角先生の組込み講座

【設計しながら学ぶRTOSの基本編】第4回 TCBの具体的設計とレディキュー

こんにちは、南角 です。
少し間が空いてしまいましたが、具体的なTCBの構造を考えてみましょう。
RTOSにとっては最も重要なデータの一つが、システム内の実行可能なタスクの情報をどのように保持するかです。RTOSはこの実行可能なタスクの中の優先度が最も高いタスクを走らせます。
タスクの数が固定で、比較的少ない場合はTCBを配列にしておき、TCBにある"状態"領域が実行可能を示しているタスク(前回のはなしでいうとフラグですね)を選択すればよいと思います。
しかし今回はもっとタスクの数が多くても対応できるように、実行可能なTCBをキューで保持することにしましょう。このキューがRTOSにとっては最も重要なデータ構造のひとつであるシステムレディキューです。
前回にも述べたように実行可能なタスクをキューで保持する場合は、たとえばセマフォ解放待ちなど待ち状態のタスクもキューで保持するのが一般的です。そのほかスリープ(VxWorksではtaskDelay()を発行したタスク)状態のタスクもキューで保持されます。
しかしシステムレディキューのみは性能の面から他のキューとは別の構造とするRTOSが多いです。それも考えてみましょう。

まずTCBの定義です。

struct TCB{
struct TCB *next;
//次のTCBへのポインタ
unsigned short status;
//タスクの状態
unsigned short pri;
//タスクの初期優先度
unsigned short currentpri;
//現在の優先度(実行時優先度)
unsigned short pad;
//ダミー
unsigned long sleep;
//スリープカウント
unsigned int *sp;
//スタックボトム(スタックの初期値)
void (* entry)(void);
//タスクエントリー
struct CPU_CONTEXT cpu;
//CPUコンテキスト(レジスタなど)
struct AUX *auxp;
//FPUレジスタなどそのほかのタスク固有領域
};


というところでしょうか?
ダミーはこのCPUが32ビットCPUという前提です。コンパイラが勝手に挿入してくれる場合もあります。16ビットCPUであれば不要です。CPUのメモリアクセス効率を落とさないためのものです。
厳密にはコメント記号の「//」もCではなくC++コンパイラの書き方です。
(とはいえCコンパイラも進歩しつづけているため、最新の規格ではOKになったような気もしますが、このあたりは読者の方の環境によっても異なるため、各自で調整してください)

TCBの定義より前にstruct CPU_CONTEXT としてCPUのアーキテクチャ(レジスタ)の定義がされているという前提です。
たとえば以下のようになります。

struct CPU_CONTEXT {
void (* pc)(); int *sp; int a0; int a1; : : int r0; int r1; :
unsigned int psw; : : };

CISC系CPUを前提にしているため、汎用レジスタには主にデータや場合によっては命令のアドレスを格納するアドレスレジスタと、主にデータそのものを格納するデータレジスタがあるという前提です。
その意味ではa0やa1はポインタ型で定義してもよいのかも知れませんが、ここでは整数型で定義しておきます。
実行を一時停止させられたタスクの情報は、主にこのCPU_CONTEXT領域に保存されます。
タスクを再開させる場合も主にこのCPU_CONTEXT領域のデータを使用します。
taskSpawn()などタスクを最初から起動する場合はentry, sp priなどの初期値を使用します。
ここで注意しておきたいのはpswとpcがアトミックデータ(切り離せないデータ)だということです。
つまり保存する場合も復元する場合も同時に使用しなくてはならないということです。別の言い方をすると1命令で保存および復元しなくてはならないということです。
この理由は宿題としておきます。

次にシステムレディキューですが、便宜上システムにおけるタスクの優先度が4レベルだとすると、次の図で示すような構造になります。
システムレディキューの一般的な構造です。
優先度ごとに実行可能タスク(TCB)をつなぐためのキューを備えています。
そして、図における最初の0と1からなっている部分はビットマップで各ビットが一つの優先度に対応しており、1がそのレベルのタスクがいる(そのレベルの実行可能状態のタスクが存在する)ことを示しています。
そして、便宜上横に並べて書いていますが1,0,3などと書いている部分はそのレベルの実行可能タスクがいくついるか、言い換えるとそのレベルで待っている実行可能タスクの数です。
これは冗長なデータでもあり、無くても構いません。
なぜこのような構造にするのかに関してはこれも宿題にしておきます。次回までに考えておいてください。

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