tslp_tskらしき物の作成 [ColdFire V1]
とは言え、このカーネル(らしき物)はITRONの様に優先度ベースのスケジューリングを行ってもいないし、単にサービスコール名を合わせたに過ぎない物です。
本当はwup_tskやrel_waiと言ったところまで行きたかったのですが、スキルが足りなくて、全然進みませんでした。
tslp_tskは、引数にTMO型の引数を取り、単位をmsとした時間分だけタスクの実行を遅延します。
と、同様のサービスコールにdly_tskが有りますが、こちらの引数はRELTIM型で、サービスコールの動作も異なります。
tslp_tskの動作は、自タスクをRUNNINGからWAITINGに遷移させ、現在時刻からの相対時間分の時間待ちを行いますが、引数には相対時間(正の整数型)以外にTMO_POL(0に定義されている)、
TMO_FEVR(負の整数型 -1に定義されている)を取りえる事ができ、それぞれ動作が異なってきます。
また、他のタスクや非タスクコンテキスト部からの起床要求を受ける事ができ、必要ならば起床要求のキューイングを行いますが、まだそこまで作成していません。
この改造に伴い、前回のラウンドロビンからはスケジューラの構造が随分変わる事となりました。
このカーネルでは一部のサービスコールを除いて(除かれたサービスコールはマクロで実現するとか)trap命令で実現しています。
その為、サービスコールの前処理として機能コードをレジスタに代入する必要が有ります。
ちなみに機能コードはμITRON4.0仕様の機能コードを当てはめています。
#include "mytron.h"
void __slp_tsk(
UB fc, /*機能コード*/
TMO tmout /*タイムアウト時間*/
)
{
__asm {
trap #0;
};
}
"mytron.h"内では以下の様に定義されています。
void __slp_tsk( UB fc, TMO tmout );
#define tslp_tsk( tmout ) __slp_tsk( (UB)FC_TSLP_TSK, (TMO)tmout )
#define slp_tsk() __slp_tsk( (UB)FC_SLP_TSK, TMO_FEVR )
つまりtslp_tskもslp_tskもどちらも同じ関数”__slp_tsk”を呼びますが、関数コールなので自動的に引数はレジスタに代入される事となります。(※但しd0、d1、a0、a1を使ってしまうとスタック渡しに切り替わる)
trap命令の行き先の”exceptions.c”の中の割込みサービスルーチンは、以下の様に改造しました。アセンブラの割込みサービスルーチンには変更が無く、C言語の割込みサービスルーチンのみです。
void *C_isr_handler( UB fc, VP_INT exinf, void *framepointer, void *usp )
{
BOOL DelayedDispatch = FALSE;
volatile unsigned short stackFrameWord = (*(unsigned short *)(framepointer));
volatile unsigned long stackFrameVector = (unsigned long)MCF5XXX_RD_SF_VECTOR(&stackFrameWord);
int loop;
/*割り込みサービスルーチンの開始*/
switch ( stackFrameVector )
{
case VectorNumber_Vtrap0 : /*trap #0 ソフトウエア割り込み*/
DelayedDispatch = ( svc_list[ fc ^ 0xff ].func )( exinf );
break;
case VectorNumber_Vtpm1ovf : /*TPM1のオーバーフロー割り込み*/
DelayedDispatch = TPM1_OVF_ISR(); /*遅延ディスパッチを実行する*/
break;
default :
break;
}
if( DelayedDispatch == TRUE ) /*遅延ディスパッチが必要な時*/
{
/*コンテキストの保存*/
tcb[ RunningTaskID ].sp = usp; /*スタックポインタをタスクコントロールブロックに保存*/
/*次のタスクIDを取得*/
for( loop = 0; loop < MAX_TID_NUMBER; loop++ )
{
if( ++RunningTaskID >= MAX_TID_NUMBER ) RunningTaskID = 0;
if( tcb[ RunningTaskID ].tskstat == TTS_RDY ) break;
}
if( loop == MAX_TID_NUMBER ) /*全てのタスクが待ち状態の時はIDLE LOOPを呼び出す。*/
{
RunningTaskID = MAX_TID_NUMBER;
}
tcb[ RunningTaskID ].tskstat = TTS_RUN; /*該当タスクをRUNNINGに遷移させる*/
/*次のコンテキストの取り出し*/
usp = tcb[ RunningTaskID ].sp; /*タスクコントロールブロックからスタックポインタの取り出し*/
}
return usp;
}
割込みベクター番号に相当する処理を先のswitch-case文で処理していますが、そのcaseの中ではテーブルジャンプを使って該当処理をコールします。
(※μITRONの機能コードは負の値となっています。これをテーブルのインデックスとするには、全bitを反転するだけで済みます。)
ジャンプテーブル
#include "mytron.h"
BOOL illegal( VP_INT exinf ){ return TRUE;}
BOOL exec_tslp_tsk( TMO tmout );
BOOL exec_rot_rdq( PRI tskpri );
const SERVICE_CALL_LIST svc_list[] =
{
{ illegal }, /*(-0x01) 予約*/
{ illegal }, /*(-0x02) 予約*/
{ illegal }, /*(-0x03) 予約*/
{ illegal }, /*(-0x04) 予約*/
{ illegal }, /*FC_CRE_TSK (-0x05)*/ /* cre_tskの機能コード */
{ illegal }, /*FC_DEL_TSK (-0x06)*/ /* del_tskの機能コード */
{ illegal }, /*FC_ACT_TSK (-0x07)*/ /* act_tskの機能コード */
{ illegal }, /*FC_CAN_ACT (-0x08)*/ /* can_actの機能コード */
{ illegal }, /*FC_STA_TSK (-0x09)*/ /* sta_tskの機能コード */
{ illegal }, /*FC_EXT_TSK (-0x0A)*/ /* ext_tskの機能コード */
{ illegal }, /*FC_EXD_TSK (-0x0B)*/ /* exd_tskの機能コード */
{ illegal }, /*FC_TER_TSK (-0x0C)*/ /* ter_tskの機能コード */
{ illegal }, /*FC_CHG_PRI (-0x0D)*/ /* chg_priの機能コード */
{ illegal }, /*FC_GET_PRI (-0x0E)*/ /* get_priの機能コード */
{ illegal }, /*FC_REF_TSK (-0x0F)*/ /* ref_tskの機能コード */
{ illegal }, /*FC_REF_TST (-0x10)*/ /* ref_tstの機能コード */
{ (BOOL (*)( VP_INT ))exec_tslp_tsk }, /*FC_SLP_TSK (-0x11)*/ /* slp_tskの機能コード */
{ (BOOL (*)( VP_INT ))exec_tslp_tsk }, /*FC_TSLP_TSK (-0x12)*/ /* tslp_tskの機能コード */
以下、凄く続く
}
該当処理、つまりtslp_tskの中身は以下の様になっています。
BOOL exec_tslp_tsk(
TMO tmout /*タイムアウト時間*/
)
{
ID tid = ref_tst() - 1;
tcb[ tid ].dwn_cnt = (RELTIM)tmout; /*待ち時間の設定*/
if( tmout == TMO_POL )
{
tcb[ tid ].tskstat = TTS_RDY; /*ポーリングの時はディスパッチを発生させるのみ*/
}
else
{
tcb[ tid ].tskstat = TTS_WAI; /*待ち状態に入る*/
tcb[ tid ].tskwait = TTW_SLP; /*TMO_FEVRが指定されている時は起床待ちとする*/
}
return TRUE;
}
つまり引数の待ち時間を、変更/追加されたタスクコントロールブロックのダウンカウンターに代入し、またその待ち時間のタイプによってREADYに遷移させるのか、WAITINGに遷移させるのかを決めます。
サービスコール”tslp_tsk”は、引数の時間が経過する事を待つ処理なので、その時間経過を検出するのはシステムタイマーを更新している処理で行う事となります。つまりシステムタイマーとして設定しているTPM1の割込みの中です。
/****************************************************************************/
/* TPM1 オーバーフロー割り込み */
/* 一番目の引数はA0に入っている(アドレスだから?)。 */
/* 二番目の引数はA1に入っている。 */
/****************************************************************************/
BOOL TPM1_OVF_ISR( void )
{
int loop;
static int count;
RELTIM old_ltime = systime.ltime;
TPM1SC_TOF = 0; /*clear status*/
/*システムタイマーの更新*/
systime.ltime += kernel_tic_cnt;
if( systime.ltime < old_ltime ) systime.utime++;
/*時間待ちタスクのタイマーカウンターのカウントダウン*/
for( loop = 0; loop < MAX_TID_NUMBER; loop++ )
{
if( (tcb[ loop ].tskstat & TTS_WAI) && tcb[ loop ].dwn_cnt > 0 ) /*待ち状態でカウントダウン実行中のタスクを探す*/
{
if( --tcb[ loop ].dwn_cnt == 0 ) /*カウントダウンの最後を検出*/
{
if( !(tcb[ loop ].tskstat & TTS_SUS) ) /*SUSPENDは設定されていない事*/
{
tcb[ loop ].tskstat = TTS_RDY; /*タイムアウトとなったので、READYに遷移させる*/
}
else
{
tcb[ loop ].tskstat &= ~TTS_WAI; /*単純な待ち状態の解除*/
}
tcb[ loop ].tskwait &= ~(TTW_SLP | TTW_DLY); /*SLEEPまたはDELAYステータスの解除*/
}
}
}
/*LEDチカチカ*/
if( ++count >= 197 )
{
PTAD_PTAD0 ^= 1;
count = 0;
}
return TRUE;
}
新しいタスクコントロールブロック
typedef struct
{
VP sp; /*タスクスタックポインタ*/
FP fn; /*起動関数*/
VP_INT exinf; /*起動時パラメータ(D0に入れられてタスクが起動する)*/
UB tskstat; /* タスクの状態 */
UB actcnt; /* 起動要求キューイング数 */
STAT tskwait; /* 待ち要因 */
RELTIM dwn_cnt; /* 待ち要因のダウンカウンター */
} TASK_CONTROL_BLOCK;
「システムタイマーの更新処理」とか「LEDチカチカ」は良いとして、「時間待ちタスクのタイマーカウンターのカウントダウン」で、タスクコントロールブロックのダウンカウンターを片っ端からカウントダウンを行っています。
その中で指定時間が来たタスクに関しては、現在の待ち状態から、必要に応じて別の状態への遷移を行います。(別タスクや非タスクコンテキスト部からのサスペンドを受ける可能性も有るので、通常の待ち状態とサスペンドの両方が解除されないとREADYには遷移できない。)
従来のラウンドロビンではタスクの状態がREADYかRUNNINGしか取りえなかったのですが、今度はWAITINGまでとなったので、場合によっちゃあ、全てのタスクがWAITINGとなる事も有り得ます。
その為、idl_loopと言う何もしないタスクの代わりの処理を用意します。このIDLE LOOPへの入り方は、タスクの入り方と何ら変わりません。
/****************************************************************************/
/* IDLEタスク */
/* 他に実行すべきタスクが無い時、このタスクが呼ばれる */
/****************************************************************************/
void idl_loop( void )
{
for( ; ; )
{
_Wait; /*derivative.hにマクロが有るので、それを利用*/
}
}
何もする事が無くてここに来たので、WAITモードに入っています。場合によってはもっと深い省電力モードにも入れるでしょう。
tslp_tskが実現できたので、タスクは以下の様に変更しました。
/****************************************************************************/
/* タスク1 */
/* レジスタD0に起動パラメータが入っている。 */
/****************************************************************************/
void task1( VP_INT exinf )
{
int tid = ref_tst();
exinf *= tid;
for( ; ; )
{
tslp_tsk( (RELTIM)exinf );
switch ( tid )
{
case 1 :
PTAD_PTAD1 ^= 1;
break;
case 2 :
PTAD_PTAD2 ^= 1;
break;
case 3 :
PTAD_PTAD3 ^= 1;
break;
case 4 :
PTBD_PTBD0 ^= 1;
break;
case 5 :
PTBD_PTBD1 ^= 1;
break;
case 6 :
PTBD_PTBD2 ^= 1;
break;
default :
PTBD_PTBD3 ^= 1;
break;
}
}
}
※前回とはLEDチカチカするポートが異なっていますが、これはダイアモンドエースで実行させたので、割り付けが異なっているからです。
※相変わらず一つの関数で、こんどは7つのタスクを実行させています。
※定義の一部には、Project HOSのHOSの定義ファイルの一部を利用させてもらっています。
※何故かrot_rdqが上手く行かないです。原因を調べねば。
※タスクコントロールブロックの管理やタイムアウトしたタスクの検索には、このカーネルでは配列を使用しています。これは構造が単純で動作がイメージし易いからです。しかしスケーラビリティは最低です。
このカーネルは、あくまでも両手くらいのタスク数を最大とした前提での話です。
もしスケーラビリティを重視するなら、HOSの様にリンクリストを使う必要が有ります。
ITRONプログラミング入門―H8マイコンとHOSで始める組み込み開発
- 作者: 濱原 和明
- 出版社/メーカー: オーム社
- 発売日: 2005/04/25
- メディア: 単行本
μITRON準拠TOPPERSの実践活用―製品開発にも学習教材にも使えるフリーのOSプラットホーム (TECH I Embedded Software)
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2007/03
- メディア: 単行本
以下のファイルをクリックせず、右クリックメニューからローカルに落として、拡張子をzipに修正してお使い下さい。
プロジェクトファイル一式
※Pac and Goで生成したzipファイルはLhacaでは正常に解凍できませんでした。
※_Waitマクロを使用した場合、デバッカーを外して単独では正常に動作しませんでしたので、このプロジェクトファイルではフルパワーで走っています。タイマーが動いていないのか、割込みが入らないのか判りませんが、まだまだ研究が必要な様です。
SOPT1_WAITEビットは、WAIT状態を有効にするビットであって、WAIT状態に遷移するビットではありません。
HC(S)08と違い、ColdFire V1には、WAITという命令がありません。あるのは、STOP命令だけです。これでは、WAIT状態は実現できないので、「STOP命令が実行された時にはWAIT状態に遷移せよ」という意味のWAITEを新設したのでしょう。
従って、WAITEをセットするだけでは不十分で、STOP命令を実行するとWAIT状態に遷移します。ちなみに、DEFAULTでWAITEビットは、セットされています。
参考文献
AN3460
Low-Power Design Enabled by MC9S08QE128 and MCF51QE128 Microcontrollers
http://www.freescale.com/files/microcontrollers/doc/app_note/AN3460.pdf
by noritan (2008-02-12 09:48)
このオペレーティング・システムが出来上がると、μITRONよりも小さいn(ナノ)ITRONになるのかな。
nITRON?
どこかで聞いたことのある名前だ。
by noritan (2008-02-12 09:53)
> 従って、WAITEをセットするだけでは不十分で、STOP命令を実行
> するとWAIT状態に遷移します。
成る程、ナルホド。WAITモードへの遷移がイマイチ判っていませんでした。
STOP #3;でWAITモードに入ってくれるのかな?。
by hamayan (2008-02-12 10:22)
> nITRON?
> どこかで聞いたことのある名前だ。
nITRON の検索結果 約 504,000 件中 1 - 10 件目 (0.13 秒)
by hamayan (2008-02-12 10:35)
nITRON freescale の検索結果 約 3,630 件中 1 - 10 件目 (0.21 秒)
by noritan (2008-02-12 11:05)
↑嗚呼、検索の先頭にあの人のページが引っかかっているが、でも実際にそれに触れているのはあの人だった、、、。
by hamayan (2008-02-12 11:14)
CFPRM.pdfによると、
STOP #<data>
Immediate Data → SR; STOP
なので、「STOP3モードの3」と「STOP命令のオペランド」は無関係です。普通にSTOPE=1; WAITE=0; として、STOP命令を実行すると一番ユルイSTOPモードであるSTOP3に遷移するはずです。STOPEは、write-onceレジスタなので、ご注意を。また、STOP3では、TPMタイマは停止してしまいますので、マイコンをたたき起こすには、RTCを使用する必要があります。
もう一つ、01/2008の比較的新しい参考文献
AN3586
Run uC/OS-II on MCF51QE128
http://www.freescale.com/files/32bit/doc/app_note/AN3586.pdf
どうも、みんな考えることは同じらしい。こいつもコンテキスト・スイッチでUSPを退避させている形跡が無いので、スーパバイザ・モードのみでの動作を前提としているようです。
by noritan (2008-02-12 11:34)
> Immediate Data → SR; STOP
これがよく判らないのですが、STOP命令を実行する時、SRレジスタの値も変更してしまうと言う事なのでしょうか?。
何もしないSTOP命令って無いのですかね。WAITモードに入りたいだけなのに。
と思ったら、Derivative.hにマクロが有った。
実は皆悩んでいるのか?。
by hamayan (2008-02-12 13:17)