タスク関数プロトタイプ
タスク関数のプロトタイプは下の型に限定します。ただしユーザーには dfTaskFnc(.) マクロを使って struct StDelayPcContext* 引数を明示的に見せません。コンパイラによってはスタック・オフセットの計算に、この二つの引数が必要になることがあるからです。
void taskSample(struct StDelayPcContext* pStContextAg__, void* const pVdAg__)
タイムアップ・タスクではコンテキスト・スイッチがありません。 pStContextAg__ を使わなくても済みます。でも k-uOS 全体の一様性を優先します。dfTaskFnc(smple,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__);
78K4 ではタスク関数の最初にスタック・フレームのオフセットをセーブしている。このため、PcCntxt_delayWait(.), PcCntxt_doLoopTimeup(.) を呼び出したと次のアドレスにスタック調整用に pop 命令がないことを前提にしている。x86 では add sp, #n があることを前提に記述してある
PcCntxt_call(struct StDelayPcContext** ppStCntxtAg, void(*pfAg)(.))
├───────┤
│EBP │←交換→pfAg また sp を、ここを指させて return する
├───────┤
│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]
├───────┤ │
PcCntxt_call(struct StDelayPcContext** ppStCntxtAg, void(*pfAg)(.)) at 78K
; line 1 : #include <PcCntxt.h>
; line 2 : int PcCntxt_ca( struct StDelayPcContext** ppStCntxtAg, void(*pfA
; g)(struct StDelayPcContext* ) )
; line 3 : {
@@CODE CSEG
_PcCntxt_ca:
$DGL 1,44
push up
movw up,ax
??bf_PcCntxt_ca:
; line 5 :
; line 6 : #asm
$DGL 0,4
; 注意スタックの現在値より小さいアドレスにデータを設定しない
$DGL 0,6
; uup に pfAg の値を入れる
$DGL 0,7
mov a,[sp+7]
$DGL 0,8
mov u,a
$DGL 0,9
movw ax,[sp+5]
$DGL 0,10
movw up,ax
$DGL 0,12
; whl に return address の値を入れる
$DGL 0,13
mov a,[sp+4]
$DGL 0,14
mov w,a
$DGL 0,15
movw ax,[sp+2]
$DGL 0,16
movw hl,ax
$DGL 0,18
;uup, whl をずらしたスタック上の入れ替えた位置に設定する
$DGL 0,19
;add sp, #8
$DGL 0,20
pop ax
$DGL 0,21
pop ax
$DGL 0,22
pop ax
$DGL 0,23
pop ax
$DGL 0,25
push whl
$DGL 0,26
push uup
$DGL 0,27
ret
; line 7 : ; 注意スタックの現在値より小さいアドレスにデータを設定しない
; line 8 :
; line 9 : ; uup に pfAg の値を入れる
; line 10 : mov a,[sp+7]
; line 11 : mov u,a
; line 12 : movw ax,[sp+5]
; line 13 : movw up,ax
; line 14 :
; line 15 : ; whl に return address の値を入れる
; line 16 : mov a,[sp+4]
; line 17 : mov w,a
; line 18 : movw ax,[sp+2]
; line 19 : movw hl,ax
; line 20 :
; line 21 : ;uup, whl をずらしたスタック上の入れ替えた位置に設定する
; line 22 : ;add sp, #8
; line 23 : pop ax
; line 24 : pop ax
; line 25 : pop ax
; line 26 : pop ax
; line 27 :
; line 28 : push whl
; line 29 : push uup
; line 30 : ret
; line 31 : #endasm
; line 32 : return 0; /* damie 0 to avoid errro */
$DGL 0,29
subw bc,bc
?L0002:
; line 33 : }
$DGL 0,30
??ef_PcCntxt_ca:
pop up
ret
??ee_PcCntxt_ca:
@@BASE CSEG BASE
@@CALF CSEG FIXED
END
のように up に ppStCntxtAg を入れてくれます。
│ │
├───────┤
│ up │← sp
├───────┤
[sp+2]│return address│←── PcCntxt_call(StDelayPcContext** ppStTCntxtAg, void(*pfAg)(.))
├───────┤
[sp+5]│ pfAg │
├───────┤
│pVdPrmAt │ uup==pVdDamiAt, scheduler 側の auto 変数
├───────┤
│uup:pStDelayPcContextAg of PcCntxt_checkAndExecute(.) アッセンブラで積む
├───────┤
│ rp3 │PcCntxt_checkAndExecute(.) が rp3 を保存しない。どこかで
├───────┤壊れるかもしれないのでスタックにのせる
│ │
↓
│ │
├───────┤
│ pfAg │
├───────┤← sp
│return address│←── PcCntxt_call(StDelayPcContext** ppStTCntxtAg, void(*pfAg)(.))
├───────┤
│pVdPrmAt │ uup==pVdDamiAt, scheduler 側の auto 変数
├───────┤
│uup:pStDelayPcContextAg of PcCntxt_checkAndExecute(.) アッセンブラで積む
├───────┤
│ rp3 │PcCntxt_checkAndExecute(.) が rp3 を保存しない。どこかで
├───────┤壊れるかもしれないのでスタックにのせる
│ │
--