ITRON入門 dly_tskで学ぶタスク状態と遷移

ITRON入門カリキュラム
この記事は約11分で読めます。

こんにちは、ナナです。

タスクというオブジェクトの中には「タスク状態」という情報が管理されています。タスクを制御する上で、タスク状態は非常に重要な情報となります。

本記事では次の疑問点を解消する内容となっています。

本記事で学習できること
  • タスク状態とは何なのか?
  • ITRONが定義するタスク状態とは何か?覚えるべきタスク状態とは?
  • dly_tskによるタスク状態に与える影響とは?
  • slp_tskとwup_tskの使い方とタスク状態への影響とは?

では、タスク状態とは何なのかから順に学んでいきましょう。

スポンサー

ITRONで定義されるタスク状態

タスクを「労働者」と捉えると、タスク状態とは「労働者の状態」を示すことになります。

人というのは時には一生懸命働きますが、休憩したり、何かを待っていたり、仕事の準備をします。このような状態があるのは、タスクという労働者も同じなのです。

タスク状態完全版

ITRON仕様書にはもちろんタスク状態がしっかりと定義されています。

仕様書の「3.2.1 タスク状態」に7つのタスク状態が表現されているのがわかるでしょう。

タスク状態完全版

このような状態の移り変わりを表現した図のことを「状態遷移図」と呼ぶ。

状態遷移図はソフトウェア設計を行う上でシーケンス図と共によく出てくる図なので、読み方・書き方を覚えておくとよい!「UML ステートマシン図」というキーワードで検索するとよい!

タスク状態厳選版

タスク状態って結構たくさんあって、いきなり全てを覚えるのは難しいですよね。そこでタスク状態を厳選したものを用意しました。

タスクというものを制御する上で、まずはこの3つだけでよいので覚えてください。

タスク状態厳選版

他のタスク状態は、ITRONを今後学ぶ中で自然と覚えればよいです。各タスク状態は次の内容となっています。

実行状態

対象のタスクがまさしく実行中であることを示す状態のこと。複数のタスクがシステムに存在していても、とある瞬間で実行状態になれるタスクは1つだけである。

実行可能状態

いつでも実行状態になれる準備は整っているが、実行状態のタスクが動いているため実行されない状態のこと。この状態のタスクは複数同時に存在することがある。

実行状態のタスクが待ち状態などに遷移することで、実行可能状態の中から次に実行状態になるべきタスクが1人選抜される。

待ち状態

なんらかのイベントを待っている状態。そのイベントが発生するまでは決して実行されることはない。この状態のタスクは複数同時に存在することがある。

待ち状態が解除されると実行可能状態に遷移する。実行状態に直接遷移することはない。

ディスパッチとプリエンプトについて

この用語はなかなか聞きなれないですね。次章のタスクスケジューリングにて解説をしますので、今はこのような用語があることだけ覚えてください。

スポンサー

dly_tskサービスコールの動作概要

マルチタスクの章にてタスク処理の中でdly_tsk関数というサービスコールを呼んでいました。

void MAIN(VP_INT exinf)
{
    while(1)
    {
        dly_tsk(1000);
    }

    return;
}

このdly_tskというサービスコールがタスク状態というものに与える影響を学びましょう。

タスク付属同期機能の一つがdly_tsk

タスク付属同期とは「タスク」というオブジェクト自身に付属している同期制御(タイミング制御)の機能のことです。つまり、タスクが存在すれば利用できるサービスコールということです。

ITRON仕様書の「4.2 タスク付属同期」に詳しく説明が書いてあります。

タスク付属同期機能には他にもいくつかのサービスコールがありますが、dly_tskはよく利用される筆頭のサービスコールです。

dly_tskの関数仕様

dly_tsk関数は次のプロトタイプ宣言にて実装されています。

ER dly_tsk(RELTIM dlytim);

引数のdlytimに時間を指定することで呼び出したタスクの動きを一定時間停止させることができます。時間の単位はミリ秒となっています。

dly_tskによるタスク状態への影響

ITRON仕様においてdly_tskの説明には次の記述があります。

dly_tsk(自タスクの遅延)

【機能】

自タスクを時間経過待ち状態に遷移させ、dlytimで指定される時間の間、実行を一時的に停止する。

具体的にはサービスコールが呼び出された時刻からdlytimで指定された相対時間後に待ち解除されるように設定し自タスクを時間経過待ち状態に移行させる。

「自タスクを時間経過待ち状態に遷移」

これこそがタスク状態の遷移を表す説明です。dly_tskを呼び出すと時間経過の「待ち状態」になるとされています。

dly_tsk呼び出し時のタスク遷移

さぁ、dly_tskを呼び出したときにタスク状態が具体的にどのように遷移するかを図示します。

void TASK1(VP_INT exinf)
{
    while(1)
    {
        たくさんの仕事;  [1]
        dly_tsk(1時間); [2],[3],[4]
    }
}

このプログラムを実施するとタスク状態は次のように遷移を繰り返します。

dly_tskによるタスク遷移

このようにタスクというアバターは人と同じように、様々な状態を遷移しながらミッションを行っていきます。マルチタスクでは各タスクがどの状態になっていて、次にどこに遷移しようとしているのかを思い描ける力が必要となります。

そのためにこのタスク状態を理解する必要があるのです。

スポンサー

slp_tskとwup_tskの使い方【他のタスク付属同期機能】

タスク付属同期機能にはdly_tsk以外にもサービスコールが用意されています。その他に使うことが多いのがslp_tskとwup_tskです。

これらのサービスコールも使ってみましょう。使うことで様々なシーンで活用できるアイデアが出てくるものです。

slp_tskの仕様

slp_tskの関数仕様は次の通りです。引数もなく非常にシンプルですね。

ER slp_tsk(void);

ITRON仕様の説明を読むと機能として次の記述があります。

機能

自タスクを起床待ち状態に移行させる。

ただし、自タスクに対する起床要求がキューイングされている場合、具体的には自タスクの起床要求キューイング数が1以上の場合には、起床要求キューイング数から1を減じ、自タスクを待ち状態に移行せずそのまま実行を継続する。

「起床待ち」という新しい待ち状態の登場です。dly_tskの場合は「時間経過待ち」でしたね。

自タスクとはサービスコールを呼んだタスクのことを示します。つまり、slp_tskを呼んだ実行状態のタスクが「起床待ち状態」に遷移すると読み取れます。

このように「待ち状態」というのはいくつかの種類が存在します。その待ち状態の種類によって、待ち状態を解除するための条件が異なるのです。

wup_tskの仕様

続いてwup_tskの関数仕様を見てみましょう。引数としてタスクIDが必要となっています。

ER wup_tsk(ID tskid);

ITRON仕様には次の説明があります。

機能

tskidで指定されるタスクを、起床待ち状態から待ち解除する。

対象タスクが起床待ち状態でない場合には、タスクに対する起床要求をキューイングする。具体的にはタスクの起床要求キューイング数に1を加える。

slp_tskとwup_tskの関係

「起床待ち状態」という用語でこの2つのサービスコールが関連しているのがわかりますね。slpとはsleep(眠る)であり、wupとはwake up(起こす)という意味です。

slp_tskによって眠ったタスクをwup_tskによって起こすことができるということです。よって、この2つのサービスコールは2人のタスクにより成立させることになります。

slp_tskによるタスク遷移

slp_tskとwup_tskを使ったプログラム練習

それではこれらのサービスコールを使って、タスクを制御してみましょう。

前章で作成したTASK1とTASK2を使って具体的にタスクの動きを可視化してみましょう。それぞれのタスクに次のミッションを実施してください。

TASK1:仕事をしては眠る
  1. LEDのオレンジとグリーンを両方点灯する。
  2. for (i=0 ; i < 5000000 ; i++); を実施。約2秒程度の処理。
  3. LEDのオレンジとグリーンを両方消灯する。
  4. シリアル通信で「TASK1: (-_-)zzZZ\n」文字列を送信。眠ったことを示す。
  5. slp_tskを呼び出し起床待ちへ遷移する。
  6. シリアル通信で「TASK1: (-_-;)\n」文字列を送信。起きたことを示す。

この動きを繰り返すものとする。

TASK2:定期的にTASK1を起床させる
  1. 5000ミリ秒待機する。
  2. シリアル通信で「TASK2: ( -_-) Wake Up!\n」文字列を送信。TASK1を起こすことを示す。
  3. TASK1に対してwup_tskを呼び出す。

この動きを繰り返すものとする。

シリアル通信の使い方がわからない方は次の記事を参照してください。

次の処理をベースにmain.cを作り変えてください。

main.c(一部抜粋)

//------------------------------------------------
//  概  要:練習用TASK1関数
//------------------------------------------------
void TASK1(VP_INT exinf)
{
	long i;

    while(1)
    {

    }

    return;
}

//------------------------------------------------
//  概  要:練習用TASK2関数
//------------------------------------------------
void TASK2(VP_INT exinf)
{
    while(1)
    {

    }

    return;
}

注意:wup_tsk関数でTASK1のタスクIDを参照するため、kernel_id.hをインクルードすること。

プログラムができた方はビュートローバーへプログラムを転送し、TeraTermを利用してシリアル通信の文字を表示させながら動きを観察してみましょう。

LEDの点滅と共に次の文字がTeraTermに順に表示されるはずです。

TASK1: (-_-)zzZZ
TASK2: ( -_-) Wake Up!
TASK1: (-_-;)
TASK1: (-_-)zzZZ
TASK2: ( -_-) Wake Up!
TASK1: (-_-;)
TASK1: (-_-)zzZZ
TASK2: ( -_-) Wake Up!
TASK1: (-_-;)

main.c(一部抜粋)

#include "kernel_id.h"

//------------------------------------------------
//  概  要:練習用TASK1関数
//------------------------------------------------
void TASK1(VP_INT exinf)
{
    long i;

    while(1)
    {
        //  仕事中をするためLED点灯
        Led_setLight(D_LED_KIND_ORANGE, D_LED_LIGHT_ON);
        Led_setLight(D_LED_KIND_GREEN, D_LED_LIGHT_ON);

        //  仕事実施。約2秒程度
        for (i=0 ; i < 5000000 ; i++);

        //  仕事が終わってLED消灯
        Led_setLight(D_LED_KIND_GREEN, D_LED_LIGHT_OFF);
        Led_setLight(D_LED_KIND_ORANGE, D_LED_LIGHT_OFF);

        //  眠る顔文字送信
        Sci_putString("TASK1: (-_-)zzZZ\n");

        //  眠る
        slp_tsk();

        //  申し訳なく起きる顔文字送信
        Sci_putString("TASK1: (-_-;)\n");
    }

    return;
}

//------------------------------------------------
//  概  要:練習用TASK2関数
//------------------------------------------------
void TASK2(VP_INT exinf)
{
    while(1)
    {
        //  TASK1を起こす5秒周期
        dly_tsk(5000);

        //  TASK1を起こす顔文字送信
        Sci_putString("TASK2: ( -_-) Wake Up!\n");

        //  TASK1を起床要求
        wup_tsk(TSKID_TASK1);
    }

    return;
}