task queue 群
differed called function
dynamic polling function queue
delay queue
ready queue
looping wait queue
(event queue)
delay queue
TCB RAM データ構造
StTaskBase
┌─────┬───────┬───────────────────────┐
│1 bit │3 bit │ 12 bit │
│m_blReady │m_enTaskState │ m_wdDelay │
└─────┴───────┴───────────────────────┘
enum EnmTaskState { //
EnDormant_=0
// wait() の戻り値は m_enTaskState そのものの値を返す
,EnWait_ // Time up と Restart を区別する
,EnLoopingWait_
,EnTimeup_=3
,EnStart_=4
// Wait or EnLoopingWait_ に対し、リスタートを発行すると EnRestart になる
,EnRestart_
, EnRun_ = 6
, EnLoopRun_
};
struct StTaskBase {
unsigned m_wdDelay:12;
unsigned m_enTaskState:3;
unsigned m_blReady:1; /* MSB */
};
StFrameContext ( StTaskBase を継承しているつもり)
┌───────────┐
│StTaskBase │
├───────────┤
│return address │
│ m_pTimeUpAddress │
├───────────┤
│stack frame Context │
│ int m_ppStCnxtt_sp│
│ int m_inEBX │
│ int m_inESI │
│ int m_inEDI │
└───────────┘
struct StFrameContext {
struct StTaskBase m_stTaskBase;
void (*m_pTimeUpAddress)( struct StFrameContext*);
// VC task context
int m_ppStCnxtt_sp;
int m_inEBX;
int m_inESI;
int m_inEDI;
};
StStckFrmContext ( StFrameContext を継承しているつもり)
┌───────────┐
│StTaskBase │
├───────────┤
│return address │
│ m_pTimeUpAddress │
├───────────┤
│stack frame Context │
│ int m_ppStCnxtt_sp│
│ int m_inEBX │
│ int m_inESI │
│ int m_inEDI │
├───────────┤
│task stack pointer │
│ m_pByStack │
└───────────┘
struct StStckFrmContext {
struct StTaskBase m_stTaskBase;
void (*m_pTimeUpAddress)( struct StFrameContext*);
// VC task context
int m_ppStCnxtt_sp;
int m_inEBX;
int m_inESI;
int m_inEDI;
unsigned char* m_pByStack;
};
TCB ROM データ構造
RAM の消費を少なくするため、TCB データのうち、const にできるものを一つにしました。
--------- StGroupVTable --------
┌───────────────────────────┐
│RAM TCB pointer │TCB RAM pointer
│ StTaskBase* m_pStTaskBase │
├───────────────────────────┤
│タスクへのコンテキスト・スイッチ関数 必須 │
│ void (*m_pfPushFrntBk)(StGroupVTable*) │ StartTask と Delay timeup で使う
├───────────────────────────┤
│ void (*m_pfExecute)(StGroupVTable*) │ DfBool CntxtCue_ExecuteReadyCueStt(.)
├───────────────────────────┤ の中で呼び出す
│タスク・ループへのコンテキスト・スイッチ関数 選択 │ 上の処理するタスクがないときに
│ void(*m_pfLooping)( StGroupVTable*); │executeLoopingCue(.) が呼び出され
├───────────────────────────┤WaitLooping 中のタスクをm_pfLooping() で移る
│ROM TCB list next StGroupVTable pointer 必須 │
│ StGroupVTable** m_ppStGroupVTableNex │Delay/Ready Queue list RAM pointer
├───────────────────────────┤
│Looping Task TCB list next StGroupVTable pointer 選択 │Looping Queue list RAM pointer
│ StGroupVTable** m_ppStGroupVTableNext; │
└───────────────────────────┘
struct StGroupVTable{
struct StTaskBase* m_pStTaskBase;
/* Redy Cue の Front 側にプッシュするタスクは優先順位の高い特別なタスクである */
void(*m_pfPushFrntBk)(const struct StGroupVTable*);/*void(*m_pfDecrementGrp)(struct StTaskBase*);*/
/* m_pfExecute() はタスク・パラメータを制御するので StGroupVTable* 引数が必要になる */
void(*m_pfExecute)(const struct StGroupVTable*);
/* この下は TmupCntxt.h/.c では使わない*/
void(*m_pfLooping)(const struct StGroupVTable*);
const struct StGroupVTable** m_ppStGroupVTableNext;
const struct StGroupVTable** m_ppStGroupLoopingNext;
/* 以下はアプリケーションによって変わってくる。*/
void** m_ppVdPrm;
TyByte m_byPrmArSize; /* パラメータ配列サイズ */
/* Stack には integer alignment がかかる機種がある。*/
/* m_pByStack の初期値を設定するとき、この alignment がかかった値を設定する*/
unsigned char* m_pByTaskStackBottom; /*タスク・スタックつき Frame Context で使う */
/* EnStart_ のとき TskCntxt_execute(.) のなかでタスク・スタックの初期化も行う*/
};
ダイナミック・ポーリング関数
struct StDynmcPllng{
DfBool (*m_pfPolling)(void);
const StDynmcPllng** m_ppStNextDynmcPllng;
const StGroupVTable* m_pStGroupVTable
};
pStOriginRamStt
┌──┐ ┏━━━━━━━━┓ ┏━━┓ ┌────────┐
│RAM ├→┃m_ppStNext ┠→┃RAM ┠→│m_ppStNext ├→
└──┘ ┃m_pfPolling ┃ ┗━━┛ │m_pfPolling │
┃m_pStGroupVTable┃ │m_pStGroupVTable│
┃ ROM ┃ │ ROM │
┗━━┯━━━━━┛ └────────┘
↓
StGroupVTable
#ifdef DfTaskOOP_H_
// DynPlOOP.h is included after including TaskOOP.h
static const struct StDynmcPllng dfStDynmcPllngRom_ ={
dfGnrtDynPllngCdInvlvFnctn
,&dfStDynmcPllngRam_
,&dfCstVTableStt_
};
#else
static const struct StDynmcPllng dfStDynmcPllngRom_ ={
dfGnrtDynPllngCdInvlvFnctn
,&dfStDynmcPllngRam_
,0
};
#endif //DfTaskOOP_H_
StDynmcPllng を使って Dynamic polling 関数のリストを管理する。リストにリンクすることで、ポーリング関数の実行を行うようになる。ただしユーザーが明示的に
void DynmcPll_execute(void)
をメイン・ループから呼び出すことで、ポーリング関数の実行がなされる。
ダイナミック・ポーリング関数はウェート状態のタスクを再開させる条件判断を行わせるルーチンを実行させたり、実行させなかったりすることを目的に使える。再開の条件判断をしないときはリストから外しておくことで、無駄な CPU タイムの消費をなくすようにする。
タスクの再開条件を判断するポーリング・ルーチンはタスクがレディー状態になったときリンクから外すことが多いはずです。ポーリング・ルーチンがタスクをレディ状態にするとき、ポーリング関数の戻り値を false にすることで、ポーリング関数リストからの外すことができます。でもディレーのタイム・アップによりタスクがレディーになったときは、これではポーリング関数のアンリンクが行えません。そのために StDnmcPllin 構造体に m_pStGroupVTable メンバーを入れてあります。このポインタ・メンバーが 0 以外の値であることは、ポーリング関数がタスクと関連付けられていることを意味します。タスクがレディ状態になると自動的にポーリング関数リストからアンリンクされることを意味します。
タスク関数プロトタイプ
タスク関数のプロトタイプは下の型に限定します。ただしユーザーには dfTaskFnc(.) マクロを使って struct StDelayPcContext* 引数を明示的に見せません。コンパイラによってはスタック・オフセットの計算に、この二つの引数が必要になることがあるからです。
void taskSample(struct StDelayPcContext* pStContextAg__, void* const pVdAg__)
common stack context switch
│ │
├───────┤
│return address│of PcCntxt_delayWait(
├───────┤ struct StDelayPcContext* ppStCntxtAg, TyWornd wdDelay)
┌─sp│ ppStCntxtAg │
│ ├───────┤
│オ │ wdDelayAg │
│フ ├───────┤
│セ │auto variable │
│ッ ├───────┤
│ト │ . │
│ ├───────┤
│ │auto variable │
│ ├───────┤
│ │EBP │
│ ├───────┤
│ │PcCntxt_return2Task(.) or task 開始呼び出し側への return address
│ ├───────┤
└→●│pStDelayPcContextAg of PcCntxt_return2Task(.) ppStCntxtAg
├───────┤
│inArg 0 of pVdArDamieAt[0]
│───────┤ │
│inArg 0 of pVdArDamieAt[1]
│───────┤ │
│inArg 0 of pVdArDamieAt[2]
├───────┤ │
スタック・フレームのタスク・コンテキストの一部として保存するとき &ppStCntxtAg - sp を使う。VC++ のときでも bp - sp ではない。bp の位置は &pStCntxtAg - 8 に固定されているので、bp の sp に対する相対位置を計算でだせます。こうしておけば、VC 以外の bp を使わないコンパイラ系でも同様な考え方で記述できます。
sp の位置を call 命令の後の add sp, #n の #n 分だけ減らす方法もある。しかし、これはコンパイラ依存部分を多くする。コンパイラ・オプションの付け方だけでもコードの変更が必要になる確率が高くなる。スタック上の戻りアドレス直前の位置をスタック使います。
スタック・フレーム・オフセットは StPcContext::m_ppStCnxtt_sp 変数に保存する。この名称は、コンパイラごとに変わりうる。コンテキスト・データ自体がコンパイラ毎に変わるからです。
78K4 コンパイラではベース・ポインタを使わずに、コンパイラ・コードを SP からのオフセットのみから定めるコードを作らせることができます。このとき pStCntxtAg の位置は、スタックの最上位に詰まれます。タスク関数のオート変数より上の位置となります。このため、&pStCntxtAg をスタック・フレームのボトムにできません。
sp│pStCntxtAg push whl でスタックにつむ: 3 byte
O↑├───────┤ up=ax:pStDelayPcContextAg
F││auto variable │ whl ==
F││ of task │
S││ │
E││ │
T││ │
│├───────┤←───────────→ taskSample(StTcb stTcbAg__, pVdPrm)
││return address│ and whl = pStDelayPcContextAg of,return2Task(), task()
│├───────┤
●↓│pVdPrmAt │ uup==pVdDamiAt, scheduler 側の auto 変数
├───────┤
│uup:pStDelayPcContextAg of PcCntxt_checkAndExecute(.) アッセンブラで積む
├───────┤
│ rp3 │PcCntxt_checkAndExecute(.) が rp3 を保存しない。どこかで
├───────┤壊れるかもしれないのでスタックにのせる
│ │
スタック・フレームを保存する操作も PcCntxt_delayWait(.) の中では行いません。そのためには &pVdPrmAt を PcCntxt_delayWait() 関数に渡す必要が出てきます。そのぶんだけ、ROM を消費してしまいます。タスク関数の最初にスタック・フレームを保存するコードをマクロで挿入しておきます。これはタスク関数の最初の一回だけで済み、コンテキスト・スイッチのたびにスタックフレームを作らずに済むので、ロムの消費を少なくできます。
# define dfTaskFnc(taskSample, pVdAg__)\
void taskSample(struct StDelayPcContext* pStContextAg__, void* const pVdAg__){\
struct StDelayPcContext** ppStContextAt__;\ /* ROM code size を小さくするため */
ppStContextAt__ = &pStContextAg__;\
PcCntxt_setStackFrame(&pStContextAg__, &pVdAg__);