LoginSignup
4
1

More than 1 year has passed since last update.

TOPPERS/ASPをRustから使う(sample1)

Last updated at Posted at 2021-07-06

はじめに

カーネルとのI/F作成が終わったので動作確認をするためのsample1を書いていく。
sample1はタスク間通信以外のAPIはバランス良くサポートしていて、動かしてAPIの仕様を確認するには丁度いいプログラムである。

とはいえ、初めてμITRON4.0仕様に初めて触れる人には慣れない要素も多いと思われる(私は学生のときにこのプログラムに触れたとき、何がなんだかわからなかったし)。

そこで何回かに分けて、Rustでの実装を見ながら解説していく。

main_task/taskの役割

ざっとmain_taskの役割を述べておくと、以下。

  • プログラム全体の準備
  • シリアル通信によるホストとのやり取り
  • 一部のコマンドのtaskへの受け渡し
  • ターゲットを取る(何らかのIDを引数に取る)サービスコールの発行
  • システム全体に関与するサービスコールの発行

一方、並列動作するtaskの役割は、自タスクを対象とするサービスコールの発行である。

main_task冒頭

ログ出力の設定

最初から飛ばしていく感じになるが、main_taskではsyslogの関数を呼び出している。

#[no_mangle]
#[allow(unreachable_code)]
extern "C" fn main_task(exinf: i32) {
    let mut ercd: Er;

    svc_perror!(Syslog::syslog_msk_log(
        Syslog::syslog_log_upto(LOG_INFO),
        Syslog::syslog_log_upto(LOG_EMERG)
    ));

まずsyslogとは、TOPPERS/ASPが提供している、ログを出力するための仕組みである。これはμITRON4.0ではなく、TOPPERS/ASP(を始めとするTOPPERS系カーネル全般)の機能である。
具体的には、シリアルポートを介してascii文字列で出力する。

syslogは2つの出力方法を持っていて、1つ目の引数はログバッファ経由で(大雑把に言えばゆっくりと)タスクにて出力するレベルを示す。
2つ目の引数は直接、すぐに(大雑把に言えばすぐに)出力するレベルを示す。

レベルは以下の7つである。上のものほど緊急度が高い。

  • LOG_EMERG
  • LOG_ALERT
  • LOG_CRIT
  • LOG_ERROR
  • LOG_WARNING
  • LOG_NOTICE
  • LOG_INFO
  • LOG_DEBUG

この引数指定の場合、LOG_ALERT〜LOG_INFOはゆっくりと、LOG_EMERGはすぐに出力すると言うことになる。
LOG_DEBUGは出力されない。
これをうまく使うと、詳しく調べたいときは出力のレベルを上げ、おおまかでいいときはレベルを下げて、文字列量のコントロールできるようになる。

プログラムの開始の出力

早速syslogを使う部分である。LOG_NOTICEなのでゆっくりと出力される。
print!(Cだとprintf)などと同じように、引数を出力できる(ただしsyslogでは5つが最大である)。

toppers_syssvc_syslog!(
        LOG_NOTICE,
        "Sample program starts (exinf = %d).",
        exinf as u32
    );

シリアルポートのオープン

main_taskや後に述べるtaskがユーザから文字を受け取るための、シリアルポートをオープンする。
(シリアルポートについては本エントリでは取り扱わない。)
TASK_PORTIDで指定したシリアルポートをオープンし、失敗ならエラーを出力するが、移植する人以外はこのエラーに出会うことはまずないだろう。

その後、シリアルポートの設定を行っている。
第2引数が設定本体である。ホストOSの設定によっては書き換えてもよい。

  • IOCTL_CRLF : LF送信時、LFの前にCRを付加、ホストOSがWindows向け?かなと思っている
  • IOCTL_FCSND/FCRCV : フロー制御の設定。
   ercd = Serial::serial_opn_por(TASK_PORTID);

    if ercd < 0 && mercd(ercd) != E_OBJ {
        toppers_syssvc_syslog!(
            LOG_ERROR,
            "%s (%d) reported by `serial_opn_por'.",
            ercd.to_u8ptr() as u32,
            sercd(ercd) as u32
        );
    }

    svc_perror!(Serial::serial_ctl_por(
        TASK_PORTID,
        IOCTL_CRLF | IOCTL_FCSND | IOCTL_FCRCV
    ));

0.4秒の間隔を測る処理

後に述べるtask側とタスク例外処理ルーチンでのソフトループカウント数を測る。

get_timでシステム開始からの経過ミリ秒が取れるので、LOOP_REF/(time2 - time1)が1ミリ秒のカウント数となるので、400倍すれば0.4ミリ秒相当のカウントになる。

    /* 0.4秒経過させるためのループ回数の算出 */
    let mut time1: Systim = 0;
    let mut time2: Systim = 0;

    svc_perror!(Asp::get_tim(), ercd, time1);
    empty_loop(LOOP_REF);
    svc_perror!(Asp::get_tim(),  ercd,time2);

    unsafe {
        TASK_LOOP = LOOP_REF * 400 / (time2 - time1);
        TEX_LOOP = TASK_LOOP / 4;
    }

empty_loopは以下のようにしている。

C言語版の場合はvolatile修飾をした変数を用いているため簡便に書けるが、Rustはread_volatile/write_volatileを使用した実装になるので若干冗長になる。
Cの方が簡単に書ける珍しい例かもしれない。

変数iはカウントしかしないので_を付け、volatileアクセスのためにunsafeブロックになっている。

fn empty_loop(count: u32) {
    let mut _i: u32 = 0;
    unsafe {
        loop {
            write_volatile(&mut _i, _i + 1);
            if read_volatile(&_i) > count {
                break;
            }
        }
    }
}

taskの開始

T/O taskをact_tskで並列動作開始する。

    /*
     *  タスクの起動
     */
    svc_perror!(Asp::act_tsk(TASK1));
    svc_perror!(Asp::act_tsk(TASK2));
    svc_perror!(Asp::act_tsk(TASK3));

main_task開始後の動作

このあとmain_taskはシリアル通信での受信待ちに移り、入力をしない限りはtaskがCPU時間を専有するようになる。

ここまで順調ならばtask(ID:TASK1)が動き出し、この様になるはずである。

スクリーンショット 2021-06-22 0.02.13.png

taskの並列動作

ちょっとした鬼門かもしれない。私にとってそうだったと言うだけかもしれないけど。

taskはプログラムとしては1つの記述であるが、3つ実体がある。
taskはコンフィギュレータの記述によって3つ生成されているからだ。

CRE_TSK(TASK1, { TA_NULL, 1, task, MID_PRIORITY, STACK_SIZE, NULL });
CRE_TSK(TASK2, { TA_NULL, 2, task, MID_PRIORITY, STACK_SIZE, NULL });
CRE_TSK(TASK3, { TA_NULL, 3, task, MID_PRIORITY, STACK_SIZE, NULL });

タスクを生成するというのはプログラムの動作環境を生成するといえばいいのだろうか。
TOPPERS/ASPでは各タスクには独立したスタックメモリとレジスタセットが用意される。第3引数で共通してタスク先頭番地にtaskを指定しているが、異なる動作環境なのでお互いが干渉することなく動くことができる。

task冒頭の処理

taskの動作は単純である。

  1. メッセージを表示する。
  2. 0.4秒の空ループ
  3. main_taskからのコマンドがあるかチェック
  4. コマンドがあればその処理をする
  5. なければ何もせず1.へ
toppers_syssvc_syslog!(
    LOG_NOTICE,
    "task%d is running (%03d).   %s",
    tskno as u32,
    n,
    graph[(tskno - 1) as usize] as u32
);
n += 1;

unsafe {
    empty_loop(TASK_LOOP);
    c = MESSAGE[(tskno - 1) as usize];
    MESSAGE[(tskno - 1) as usize] = 0 as char;
}

変数nがループした回数、graphはその横に出るどのタスクが動いているかを示すマークを示す。

TASK1の場合
task1 is running (000).   |
TASK2の場合
task2 is running (000).     +
TASK3の場合
task3 is running (000).       *

空ループにはmain_taskと同じempty_loopを使っている。引数は、main_task冒頭の処理で求めた0.4秒分に相当する回数である。

その後、グローバル変数MESSAGEからコマンドを受け取ってcに格納する。コマンド動作については後述する。

コマンド動作

taskで実行するコマンド

コマンドの引き渡し

e,s,S,d,y,Y,z,Zのコマンドはmain_taskを介してtaskに渡される。どのタスクに渡すかは、'1','2','3'でその前に指定しておく。

'1' => {
    tskno = 1;
    tskid = TASK1;
}
'2' => {
    tskno = 2;
    tskid = TASK2;
}
'3' => {
    tskno = 3;
    tskid = TASK3;
}

tsknoがこのコマンドで書き換わるので、グローバル変数MESSAGEへのアクセス

'e' | 's' | 'S' | 'd' | 'y' | 'Y' | 'z' | 'Z' => unsafe {
    MESSAGE[tskno - 1] = c;
},

で配列の何番目に変わるかが変わってくる。Rustではグローバル変数へのアクセスは危険とみなされる(のだと思う)のでunsafeで括る必要がある。

'e'コマンド

タスクを終了する。初期状態からこのコマンドを打つと、TASK1が自タスクを終了するので、TASK2の実行が開始される。

1,e
task1 is running (001).   |
task1 is running (002).   |
task1 is running (003).   |
task1 is running (004).   |
#1#ext_tsk()
task2 is running (000).     +
task2 is running (001).     +

'a'コマンドと組合わせるとタスクの起動と終了を試せる。

1,e,1,a,2,e,3,e
task1 is running (003).   |
task1 is running (004).   |
task1 is running (005).   |
#1#ext_tsk()
task2 is running (000).     +
task2 is running (001).     +
task2 is running (002).     +
task2 is running (003).     +
task2 is running (004).     +
#act_tsk(1)
task2 is running (005).     +
task2 is running (006).     +
#2#ext_tsk()
task3 is running (000).       *
task3 is running (001).       *
#3#ext_tsk()
task1 is running (000).   |
task1 is running (001).   |

's'コマンド

自タスクを休止状態とする。初期状態からこのコマンドを打つと、TASK2が休止状態に入るので、TASK2の実行が開始される。

task1 is running (001).   |
task1 is running (002).   |
task1 is running (003).   |
#1#slp_tsk()
task2 is running (000).     +
task2 is running (001).     +
task2 is running (002).     +

's'コマンドと組み合わせると、タスクの休止と起床を試せる。

1,s,2,s,3,s,1,w
task1 is running (002).   |
task1 is running (003).   |
task1 is running (004).   |
#1#slp_tsk()
task2 is running (000).     +
task2 is running (001).     +
#2#slp_tsk()
task3 is running (000).       *
task3 is running (001).       *
task3 is running (002).       *
#3#slp_tsk()
#wup_tsk(1)
task1 is running (005).   |
task1 is running (006).   |
task1 is running (007).   |

'e'と'a'との組み合わせとの違いは、タスクが再開される場所である。
タスクの起動はtask先頭から再開されるのでカウントが000に戻るのに対し、タスクの再開はslp_tskの直後から再開されるのでカウントが休止前から連続したままになる。

'S'コマンド

タスクを休止するが、10秒後にタイムアウトして休止から復帰する。そのため's'コマンドなしでタスクの動作が再開する。

1,S,2,S,3,S
task1 is running (011).   |
task1 is running (012).   |
task1 is running (013).   |
#1#tslp_tsk(10000)
task2 is running (000).     +
task2 is running (001).     +
#2#tslp_tsk(10000)
task3 is running (000).       *
task3 is running (001).       *
#3#tslp_tsk(10000)
E_TMOUT (-1) reported by `Asp::tslp_tsk(10000)' in line 151 of `src/main.rs'.
task1 is running (014).   |
task1 is running (015).   |

また、タイムアウトはエラー扱いなので、E_TMOUTエラーが発生したことが表示されるのも違いである。

'd'コマンド

タスクの動作を遅延する。遅延するとは、自タスクの事情で指定時間だけ休止するということである。
そのため、's'コマンドと違って終了時のエラーコードがE_OKとなり、エラー表示にならない。
(よってエラーコードを無視するのであればtslp_tskと同じ動作に見え、ref_tskでないと見分けがつかない)

1,d,2,d,3,d
task1 is running (002).   |
task1 is running (003).   |
task1 is running (004).   |
#1#dly_tsk(10000)
task2 is running (000).     +
task2 is running (001).     +
#2#dly_tsk(10000)
task3 is running (000).       *
#3#dly_tsk(10000)
task1 is running (005).   |
task1 is running (006).   |
task1 is running (007).   |

'y'コマンドと'Y'コマンド

タスク例外機能を禁止もしくは許可する。タスク例外はタスクに発行することで一時的にタスクの実行を止め、あらかじめ指定していた関数を実行する機能である。

動作確認は'x'と'X'コマンドとの組み合わせになる。タスク例外はビットパターンを指定することができ、'x'は0x1','X'は0x2が渡される。

'y'のあとに’x’’X'を打っても実行が保留されるので、'Y'を打つまでは実行されない。
また、'y'で禁止している間に'x'と'X'を打つとビットORされて0x3を受け取る。

task1 is running (005).   |
task1 is running (006).   |
task1 is running (007).   |
#ras_tex(1, 0x0001U)
task1 receives exception 0x0001.
task1 is running (008).   |
task1 is running (009).   |
#ras_tex(1, 0x0002U)
task1 receives exception 0x0002.
task1 is running (010).   |
task1 is running (011).   |
task1 is running (012).   |
task1 is running (013).   |
task1 is running (014).   |
#1#dis_tex()
task1 is running (015).   |
task1 is running (016).   |
task1 is running (017).   |
task1 is running (018).   |
task1 is running (019).   |
task1 is running (020).   |
task1 is running (021).   |
#ras_tex(1, 0x0001U)
task1 is running (022).   |
task1 is running (023).   |
#ras_tex(1, 0x0002U)
task1 is running (024).   |
task1 is running (025).   |
#1#ena_tex()
task1 receives exception 0x0003.
task1 is running (026).   |

'z'コマンドとCPU例外

task上でCPU例外を引き起こす。

CPU例外の説明は難しいのだが、例えば0割や浮動小数点の例外といった、プログラムの実行起因で現在のプログラムが中断され他のプログラムに制御が移る事象である(一方、端子の変化とかタイマの満了といった外部起因のものが割込み)。

基本的に使用するCPUごとに種類が違っていて、TOPPERS/ASPの実装でもターゲット毎に実装している。またCPUによって上の定義も厳密でなく、Cortex-M4FでもOS用タイマはCPU例外の扱いだったりする。

Cortex-M4Fでは不正命令例外という、CPU命令の使い方誤りを意図的に行って、CPU例外を引き起こす。Rustでの実装はターゲットに依存するということで別モジュール化している。
また、CPU例外をサポートするかも含めてターゲットに依存するため、C言語で言うifdefにあたる、cfgを使用している。

fn raise_cpu_exception() {
    #[cfg(feature = "target_support_cpu_exception")]
    {
        sample1_raise_cpu_exception();
    }
}
sample1_helper.rs
pub fn sample1_raise_cpu_exception(){
    unsafe {
        asm!("mcr p15, 0, r1, c2, c0, 0");
    }
}

TOPPERS/ASPでは、CPU例外が起こるとDEF_EXCで登録されたハンドラ、この場合はcpuexc_handlerが実行される。

cpuexc_handlerではCPU例外が発生したときのカーネル状態を表示する。
その後にCPU例外を引き起こしたタスクにタスク例外を通知する。

    svc_perror!(Asp::iget_tid(), _ercd, tskid);
    svc_perror!(Asp::iras_tex(tskid, 0x8000u32));

異常を通知されたタスクはext_tskを使用してタスクを終了する。

tex_routine
if (texptn & 0x8000u32) != 0 {
    toppers_syssvc_syslog!(LOG_INFO, "#%d#ext_tsk()", tskno as u32);
    svc_perror!(Asp::ext_tsk());
    toppers_assert!(false);
}

異常の動きをまとめると、以下のログが理解できると思う。

1,z
task1 is running (005).   |
task1 is running (006).   |
task1 is running (007).   |
#1#raise CPU exception
CPU exception handler (p_excinf = 0x200029c0).
sns_loc = 0 sns_dsp = 0 sns_tex = 0
xsns_dpn = 0 xsns_xpn = 0
task1 receives exception 0x8000.
#1#ext_tsk()
task2 is running (000).     +
task2 is running (001).     +

'Z'コマンド

CPUロック状態でCPU例外を引き起こす。
CPUロック状態は割込み禁止に近く、TOPPERS/ASPが管理している割込みやタスクの切り替えといった機能を禁止する状態である。

この場合、cpuexc_handlerはext_kerを呼び出してシステムを終了する。

cpuexc_handler
if Asp::xsns_xpn(p_excinf) == true {
    toppers_syssvc_syslog!(LOG_NOTICE, "Sample program ends with exception.",);
    svc_perror!(Asp::ext_ker());
    toppers_assert!(false);
}
task1 is running (004).   |
task1 is running (005).   |
task1 is running (006).   |
-- buffered messages --
#1#raise CPU exception
CPU exception handler (p_excinf = 0x200029c0).
sns_loc = 1 sns_dsp = 0 sns_tex = 0
xsns_dpn = 1 xsns_xpn = 1
Sample program ends with exception.

main_taskで実行するコマンド

'a'コマンド

タスクを起動する。'e'と組み合わせてタスクの起動と終了をテストできる。

起動をできるタスクは初期状態で起動済みなので、'e'コマンドでタスクを終了してから試すとよい。

1,e,2,e,3,e,1,a,e,2,a
task1 is running (003).   |
task1 is running (004).   |
task1 is running (005).   |
#1#ext_tsk()
task2 is running (000).     +
#2#ext_tsk()
task3 is running (000).       *
#3#ext_tsk()
#act_tsk(1)
task1 is running (000).   |
task1 is running (001).   |
task1 is running (002).   |
task1 is running (003).   |
#1#ext_tsk()
#act_tsk(2)
task2 is running (000).     +
task2 is running (001).     +
task2 is running (002).     +

TOPPERS/ASPでは、タスクは1回まで起動をキューイングできるので、起動済みのタスクにも1回だけ
発行することができる。2回以上はE_QOVRエラーとなる。

1,a,a
task1 is running (002).   |
task1 is running (003).   |
#act_tsk(1)
task1 is running (004).   |
#act_tsk(1)
E_QOVR (-1) reported by `Asp::act_tsk(tskid)' in line 340 of `src/main.rs'.
task1 is running (005).   |

'A'コマンド

タスクの起動要求をキャンセルする。つまり、'a'で起動をキューイングした分をなくす。

1,a,A,e
task1 is running (010).   |
task1 is running (011).   |
task1 is running (012).   |
#act_tsk(1)                   <- 起動した分が
task1 is running (013).   |
#can_act(1)                   <- なくなるので
can_act(1) returns 1
task1 is running (014).   |
task1 is running (015).   |
task1 is running (016).   |
#1#ext_tsk()
task2 is running (000).     +  <- task1は終了

't'コマンド

taskをmain_taskから終了する。'e'コマンドはtaskが自発的に終了するのに対し、't'は外部から終了する。

メッセージの出方は'e'と似たものになるが、main_taskから発行できるので休止状態のタスクにも終了要求を出せる。その場合はE_OBJエラーとなる。

1,t,2,t,3,t,1,a,2,t
task1 is running (006).   |
task1 is running (007).   |
task1 is running (008).   |
#ter_tsk(1)
task2 is running (000).     +
task2 is running (001).     +
task2 is running (002).     +
task2 is running (003).     +
#ter_tsk(2)
task3 is running (000).       *
task3 is running (001).       *
#ter_tsk(3)
#act_tsk(1)
task1 is running (000).   |
task1 is running (001).   |
task1 is running (002).   |
task1 is running (003).   |
#ter_tsk(2)
E_OBJ (-1) reported by `Asp::ter_tsk(tskid)' in line 363 of `src/main.rs'.
task1 is running (004).   |
task1 is running (005).   |

'>','=','<'コマンド

タスクの優先度を変更する(低いほうが優先度高)。リアルタイムOSらしい動作。起動時はtask1/2/3ともに10になっている。

コマンド 優先度 優先度の高低
> 9
= 10
< 11
3,>,,1,>,2,=
task1 is running (001).   |
task1 is running (002).   |
task1 is running (003).   |
chg_pri(3, HIGH_PRIORITY)      <- TASK3の優先度が上がる
task3 is running (000).       *
task3 is running (001).       *
task3 is running (002).       *
task3 is running (003).       *
chg_pri(3, LOW_PRIORITY)       <- TASK3の優先度が下がる
task1 is running (004).   |
task1 is running (005).   |
task1 is running (006).   |
task1 is running (007).   |
chg_pri(2, HIGH_PRIORITY)      <- TASK2の優先度が上がる
task2 is running (000).     +
task2 is running (001).     +
task2 is running (002).     +
task2 is running (003).     +
chg_pri(1, HIGH_PRIORITY)      <- TASK1の優先度が上がるがTASK2と同じ
task2 is running (004).     +
task2 is running (005).     +
task2 is running (006).     +
task2 is running (007).     +
task2 is running (008).     +
#chg_pri(2, MID_PRIORITY)      <- TASK1の優先度が下がる
task1 is running (008).   |
task1 is running (009).   |

'G'コマンド

タスク優先度を取得する。'>','=','<'で指定している優先度が表示される。

1,G,>,G,
task1 is running (004).   |
task1 is running (005).   |
task1 is running (006).   |
#get_pri(1, &tskpri)
priority of task 1 is 10
task1 is running (007).   |
task1 is running (008).   |
chg_pri(1, HIGH_PRIORITY)
task1 is running (009).   |
#get_pri(1, &tskpri)
priority of task 1 is 9
task1 is running (010).   |
task1 is running (011).   |
task1 is running (012).   |
chg_pri(1, LOW_PRIORITY)
task2 is running (000).     +
#get_pri(1, &tskpri)
priority of task 1 is 11
task2 is running (001).     +

'w'コマンド

タスクを起床する。タスクの休止とセットで用いる。
's'コマンドを参照

'l'コマンド

タスクの待ち状態を強制解除する。強制とは、待ちを解除する点では他の要因(例えばwup_tsk)と同じだが、E_RLWAIが戻り値として変える。
sample1ではテストできないが、セマフォ待ちやデータキュー待ちなども解除できる。

1,s,2,s,3,s,1,l
task1 is running (002).   |
task1 is running (003).   |
task1 is running (004).   |
#1#slp_tsk()
task2 is running (000).     +
task2 is running (001).     +
task2 is running (002).     +
#2#slp_tsk()
task3 is running (000).       *
task3 is running (001).       *
task3 is running (002).       *
#3#slp_tsk()
#rel_wai(1)
E_RLWAI (-1) reported by `Asp::slp_tsk()' in line 147 of `src/main.rs'.
task1 is running (005).   |
task1 is running (006).   |

'u','m'コマンド

タスクを強制待ちにする。通常の待ちとは別の管理となり、独立して管理される。
通常の待ちと重なった状態を2重待ち状態という。

1,e,2,e,3,s,u,w,m
task1 is running (002).   |
task1 is running (003).   |
task1 is running (004).   |
#1#ext_tsk()
task2 is running (000).     +
task2 is running (001).     +
#2#ext_tsk()
task3 is running (000).       *
task3 is running (001).       *
task3 is running (002).       *
task3 is running (003).       *
task3 is running (004).       *
task3 is running (005).       *
task3 is running (006).       *
task3 is running (007).       *
#3#slp_tsk()                    <- 通常の待ち
#sus_tsk(3)                     <- 二重待ち
#wup_tsk(3)                     <- 待ちは解除されるが、まだ強制待ち
#rsm_tsk(3)                     <- 強制待ちも解除
task3 is running (008).       *
task3 is running (009).       *
task3 is running (010).       *

'x','X'コマンド

タスク例外要因のセット。'y'コマンドと'Y'コマンドを参照

'r'コマンド

タスクキューを回転させる。タスクキューは同一タスク優先度のタスクの実行順を管理する仕組みである。
タスクキューの中のタスク優先順位(※優先度と優先順位の違いに注意)はFIFO順であるが、この順序を変える。例えばsample1は初期状態で優先度10の中にTASK1〜TASK3が登録されていて、act_tskされた順にタスクキューに繋がれるから、TASK1が最初に動く。
これを回転させるというのは、TASK1を末尾にしてTASK2を先頭にすることである。

r,r,r
task1 is running (002).   |
task1 is running (003).   |
task1 is running (004).   |
#rot_rdq(three priorities)       <- 先頭がTASK2に
task2 is running (000).     +
task2 is running (001).     +
#rot_rdq(three priorities)       <- 先頭がTASK3に
task3 is running (000).       *
task3 is running (001).       *
task3 is running (002).       *
#rot_rdq(three priorities)
task1 is running (005).   |      <- 先頭がTASK1に戻る

'c','C'コマンド

周期ハンドラを起動/停止する。周期ハンドラは予め静的に指定された周期で関数を実行する機能である。
コンフィギュレーションファイルで以下のとおり登録しており、2秒おきにcyclic_handlerを実行することが指定されている。

sample1.cfg
CRE_CYC(CYCHDR1, { TA_NULL, 0, cyclic_handler, 2000, 0 });

cyclic_handlerでは'r'コマンドと同様にタスクキューの回転を行っている(非タスクコンテキストなのでirot_rdqである)。一定周期でタスクキューを回転させることでラウンドロビン方式のスケジューリングを模擬することが可能になる。

cyclic_handler
#[no_mangle]
extern "C" fn cyclic_handler(_exinf: u32) {
    svc_perror!(Asp::irot_rdq(HIGH_PRIORITY));
    svc_perror!(Asp::irot_rdq(MID_PRIORITY));
    svc_perror!(Asp::irot_rdq(LOW_PRIORITY));
}

'C'コマンドは周期ハンドラの停止である。

c,C
(これ見る限りempty_loopの周期400msじゃないなと思いながら)
task1 is running (003).   |
task1 is running (004).   |
task1 is running (005).   |
#sta_cyc(1)
task2 is running (000).     +       <- 回転している
task2 is running (001).     +
task2 is running (002).     +
task3 is running (000).       * <- 回転している
task3 is running (001).       *       
task3 is running (002).       *
task1 is running (006).   |     <- 回転している
task1 is running (007).   |
task2 is running (003).     +   <- 回転している
task2 is running (004).     +
task3 is running (003).       *
#stp_cyc(1)
task3 is running (004).       *
task3 is running (005).       *

'b','B'コマンド

アラームハンドラを開始/停止する。アラームハンドラは指定した時間1回だけ指定した時間に処理を実行する機能である。
周期ハンドラと違い、動的に実行までの時間を指定できる。

sample1.cfg
CRE_ALM(ALMHDR1, { TA_NULL, 0, alarm_handler });

アラームハンドラでは周期ハンドラと同様、タスクキューを回転させる。

alarm_handler
#[no_mangle]
extern "C" fn alarm_handler(_exinf: u32) {
    svc_perror!(Asp::irot_rdq(HIGH_PRIORITY));
    svc_perror!(Asp::irot_rdq(MID_PRIORITY));
    svc_perror!(Asp::irot_rdq(LOW_PRIORITY));
}

'B'コマンドはアラームを解除する前に行うことでアラームハンドラの実行をキャンセルできる。

b,b,B
task1 is running (005).   |
task1 is running (006).   |
#sta_alm(1, 5000)
task1 is running (007).   |
task1 is running (008).   |
task1 is running (009).   |
task1 is running (010).   |
task1 is running (011).   |
task1 is running (012).   |
task2 is running (000).     + <- 5秒経って回転
task2 is running (001).     +
#sta_alm(1, 5000)
task2 is running (002).     +
task2 is running (003).     +
#stp_alm(1, 5000)             <- キャンセルすると
task2 is running (004).     +
task2 is running (005).     +
task2 is running (006).     +
task2 is running (007).     +
task2 is running (008).     + <- 回転しない

'V'コマンド

マイクロ秒単位の時間を2回取得する。ターゲットによってサポート有無が異なるため、アトリビュートで実行の有無を切り替えている。

V
task1 is running (002).   |
utime1 = 2814087, utime2 = 2814089
task1 is running (003).   |

'Q'コマンド、Ctrl-C

サンプルプログラム及びカーネルを終了する。ext_kerを発行するとOSの機能を停止し、終了ハンドラを実行してからプログラムを終了する。
組込みにおいてプログラムを終了することはあまりないが、安全に停止する場合には重要である。

終了の動作はターゲットで異なる。デバッガ実行を前提としてデバッガに制御を返すターゲットもあるし、単に割込み禁止で無限ループすターゲットもある。Cortex-M4は何もしない処理に落ちているので若干心配である。

Q
task1 is running (004).   |
task1 is running (005).   |
-- buffered messages --
Sample program ends.

パニックハンドラの実行

C言語版にない機能として、パニックハンドラがある。
ログを緊急レベルで実行して無限ループするようにしている。

注)
ここの挙動はまだきちんと調べていない。他のタスクは実行できるのか、そうでないのか。。。

#[panic_handler]
fn panic(_panic: &PanicInfo<'_>) -> ! {
    toppers_syssvc_syslog!(LOG_EMERG, "Panic!.",);
    loop {}
}

終わりに

非常に長くなってしまった。
サンプルプログラムとは言えないくらい、充実した内容になっている。このプログラムの挙動がわかる頃にはきっとあなたもRTOSの基本はわかっていることだろう。

Happy TOPPERS Life!

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1