こんにちは、ナナです。
タスクというオブジェクトの中には「タスク状態」という情報が管理されています。タスクを制御する上で、タスク状態は非常に重要な情報となります。
本記事では次の疑問点を解消する内容となっています。
では、タスク状態とは何なのかから順に学んでいきましょう。
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を呼び出すと時間経過の「待ち状態」になるとされています。
dly_tsk呼び出し時のタスク遷移
さぁ、dly_tskを呼び出したときにタスク状態が具体的にどのように遷移するかを図示します。
void TASK1(VP_INT exinf)
{
while(1)
{
たくさんの仕事; [1]
dly_tsk(1時間); [2],[3],[4]
}
}
このプログラムを実施するとタスク状態は次のように遷移を繰り返します。
このようにタスクというアバターは人と同じように、様々な状態を遷移しながらミッションを行っていきます。マルチタスクでは各タスクがどの状態になっていて、次にどこに遷移しようとしているのかを思い描ける力が必要となります。
そのためにこのタスク状態を理解する必要があるのです。
slp_tskとwup_tskの使い方【他のタスク付属同期機能】
タスク付属同期機能にはdly_tsk以外にもサービスコールが用意されています。その他に使うことが多いのがslp_tskとwup_tskです。
これらのサービスコールも使ってみましょう。使うことで様々なシーンで活用できるアイデアが出てくるものです。
slp_tskの仕様
slp_tskの関数仕様は次の通りです。引数もなく非常にシンプルですね。
ER slp_tsk(void);
ITRON仕様の説明を読むと機能として次の記述があります。
「起床待ち」という新しい待ち状態の登場です。dly_tskの場合は「時間経過待ち」でしたね。
自タスクとはサービスコールを呼んだタスクのことを示します。つまり、slp_tskを呼んだ実行状態のタスクが「起床待ち状態」に遷移すると読み取れます。
このように「待ち状態」というのはいくつかの種類が存在します。その待ち状態の種類によって、待ち状態を解除するための条件が異なるのです。
wup_tskの仕様
続いてwup_tskの関数仕様を見てみましょう。引数としてタスクIDが必要となっています。
ER wup_tsk(ID tskid);
ITRON仕様には次の説明があります。
slp_tskとwup_tskの関係
「起床待ち状態」という用語でこの2つのサービスコールが関連しているのがわかりますね。slpとはsleep(眠る)であり、wupとはwake up(起こす)という意味です。
slp_tskによって眠ったタスクをwup_tskによって起こすことができるということです。よって、この2つのサービスコールは2人のタスクにより成立させることになります。
slp_tskとwup_tskを使ったプログラム練習
それではこれらのサービスコールを使って、タスクを制御してみましょう。
前章で作成したTASK1とTASK2を使って具体的にタスクの動きを可視化してみましょう。それぞれのタスクに次のミッションを実施してください。
シリアル通信の使い方がわからない方は次の記事を参照してください。
次の処理をベースに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;
}