概要
TOPPERS/ASPでアプリケーションを実装した後、やっぱり気になるのはスタックの状況です。商用iTRONのツールの中には当たり前のようにあるスタック状況確認機能ですが、標準システムコールにはありません。
調べてみると非依存部にはスタックの先頭アドレスとサイズしかなく、依存部のタスクコンテキストブロックにスタックポインタがあります。
もしスタック状況を計算するようなシステムコールを標準として作りたいとすると、依存部に対する仕様(スタックポインタはXXXのように用意すること・・・のように。)が必要になるわけです。
スレッド型OSでスタック状況を確認できないのはなんとも気持ちが悪いものです。
そこで、今回はquick-and-dirtyでスタック状況を確認できるメカニズムを作ってみました。
動作
小規模組み込みシステムデバッグ用シェル - Natural Tiny Shell (NT-Shell)と組み合わせた場合の動作は以下のようになります。>taskinfo
TSKID STACK ADDR (HEAD:TAIL) STACK USED (USED/TOTAL)
=========================================================
1 0x10000b88:0x10000f87 132/1024
2 0x10000f88:0x10001787 124/2048
3 0x10001788:0x10001f87 108/2048
4 0x10001f88:0x10002787 100/2048
5 0x10002788:0x10002f87 204/2048
=========================================================
各タスクのスタックアドレスと使用量が一目瞭然で安心です。
上記の出力結果とプロセッサのメモリマップを見て「フムフム」と納得できます。
今回テストで動作させているシステムにはNXPセミコンダクターズのLPC1769が搭載されています。
このプロセッサの内蔵SRAMのアドレスにスタックポインタがあることがわかります。
inf_tskシステムコールを使う
inf_tskシステムコールを使うのは簡単です。以下のようにタスクIDを指定してシステムコールを呼ぶだけです。
int i;
T_ITSK itsk;
syslog(LOG_NOTICE, "TSKID\tSTACK ADDR (HEAD:TAIL)\tSTACK USED (USED/TOTAL)");
syslog(LOG_NOTICE, "=========================================================");
for (i = 0; i < TNUM_TSKID; i++) {
const int tskid = 1 + i;
inf_tsk(tskid, &itsk);
syslog(LOG_NOTICE, " %2d\t0x%x:0x%x\t%5d/%5d",
tskid,
itsk.stk_head, itsk.stk_tail,
itsk.stk_used, itsk.stk_total);
}
syslog(LOG_NOTICE, "=========================================================");
inf_tskの実装
今回設計したシステムコールはinf_tskという名前です。(あー、そんな名前で作ちゃって!)ref_tskを参考に実装を追加しました。
まず、include/kernel.hに定義を追加します。
extern ER inf_tsk(ID tskid, T_ITSK *pk_itsk) throw();
次に、システムコールで取得するデータを格納するための構造を用意します。
これもinclude/kernel.hです。
typedef struct t_itsk {
PRI tsk_pri_curr; /* 現在のプライオリティ */
PRI tsk_pri_base; /* ベースプライオリティ */
SIZE stk_used; /* タスクスタック使用量 */
SIZE stk_total; /* タスクスタック総量 */
void *stk_head; /* タスクスタック先頭アドレス */
void *stk_tail; /* タスクスタック最終アドレス */
} T_ITSK;
ここにref_tskが持つタスク状態などを追加すると良いかもしれません。
次にシステムコール本体の実装を加えます。
今回は新たにtask_info.cというシステムコール実装用のソースを用意しました。
kernel/task_info.cがシステムコールの実装です。
/*
* タスクの情報取得機能
*/
#include "kernel_impl.h"
#include "check.h"
#include "task.h"
#include "wait.h"
#include "semaphore.h"
#include "eventflag.h"
#include "dataqueue.h"
#include "pridataq.h"
#include "mailbox.h"
#include "mempfix.h"
#include "time_event.h"
/*
* タスクの情報取得機能
*/
#ifdef TOPPERS_inf_tsk
ER
inf_tsk(ID tskid, T_ITSK *pk_itsk)
{
TCB *p_tcb;
ER ercd = E_OK;
uint_t tstat;
p_tcb = get_tcb(tskid);
t_lock_cpu();
tstat = p_tcb->tstat;
if (TSTAT_DORMANT(tstat)) {
pk_itsk->stk_used = 0;
} else {
pk_itsk->stk_used = p_tcb->p_tinib->stksz
- (p_tcb->tskctxb.sp - p_tcb->p_tinib->stk);
}
pk_itsk->stk_total = p_tcb->p_tinib->stksz;
pk_itsk->tsk_pri_curr = EXT_TSKPRI(p_tcb->priority);
pk_itsk->tsk_pri_base = EXT_TSKPRI(p_tcb->priority);
pk_itsk->stk_head = p_tcb->p_tinib->stk;
pk_itsk->stk_tail = p_tcb->p_tinib->stk + p_tcb->p_tinib->stksz - 1;
t_unlock_cpu();
return ercd;
}
#endif /* TOPPERS_inf_tsk */
後はMakefile.kernelにtask_info.cをコンパイルするような記述を追加してできあがりです。
実装をご覧になってわかる通り、p_tcb->tskctxbは依存部にあるタスクコンテキストブロックで、本来ここで参照してはいけません。
まとめ
今回の実装はTOPPERS/ASPから見ると「最悪」と言えます。が、システムを実現する立場からすると「安心」が得られます。
スタックオーバーフローによってシステムが意味のわからない挙動になってしまっては、せっかくのRTOSを使った楽しい開発が台無しです。少しの機転でシステムの状態を視覚的に把握することができ、開発を少しでも楽にできれば嬉しいですよね?
謝辞
この実装はマイコン工作実験日記さんのアイデアに触発されています。素晴らしいアイデアに感謝しております。