2026年2月23日月曜日

μITRONプログラマーがZephyrに挑戦! その8

前回からの続きです。

このテーマを最初からご覧になる場合はこちらからどうぞ。


スリープからセマフォへの置き換え

前回、ソフトウェアをμITRONからZephyrへに移植する際に、両者ではスリープのシステムコールの振る舞いに差異があることを説明しました。

これが結構「罠」的なもので、私が執った回避策は、スリープをセマフォに置き換えるという決断でした。

これにより、以下の2点の問題を解決することができます。


1.Zephyrの「k_sleep()」は、その起床原因がタイムアウトなのか、ウェイクアップ要求によるものかの区別がない

2.Zephyrの「k_sleep()」は、待ち状態期間中の「k_wakeup()」のキューイングを行わない


今回は、そのスリープをセマフォに置き換える過程を「zephye-sample」を改造しながら説明したいと思います。

Zephyr vs μITRON


改造したものは「zephyr-sample」リポジトリの「semaphore」ブランチで実装していますので、せっかちな方はそちらをご覧ください。

Cygwinなどで以下のように入力すれば、今の「master」ブランチから「semaphore」ブランチに切り替えられますよ。


$ git checkout -b semaphore origin/semaphore


sample1.c」の102行目以降に注目してください。

オブジェクト実体の定義の下に、新たに作成するセマフォ(スレッドが3つなのでセマフォも3つ)とポインター配列を追記します。

赤い部分が修正点です。

  1. ...
  2. /*
  3.  * オブジェクト実体を定義
  4.  */
  5. struct k_thread thread1;
  6. K_THREAD_STACK_DEFINE(thread_stack1, STACK_SIZE);
  7. struct k_thread thread2;
  8. K_THREAD_STACK_DEFINE(thread_stack2, STACK_SIZE);
  9. struct k_thread thread3;
  10. K_THREAD_STACK_DEFINE(thread_stack3, STACK_SIZE);
  11. struct k_sem sem1;
  12. struct k_timer cychdr1;
  13. /*
  14.  * スリープ用セマフォを定義
  15.  */
  16. struct k_sem sem_slp1;
  17. struct k_sem sem_slp2;
  18. struct k_sem sem_slp3;
  19. struct k_sem* p_sem_slp[3];

  20. /*
  21.  * 並行実行されるスレッドへのメッセージ領域
  22.  */
  23. char message[3];
  24. ...


上記が追記されたことを前提として「sample1.c」の154行目以降に注目してください。

子スレッド関数です。

ここで、実際にスリープをセマフォに置き換えています。

  1. ...
  2. /*
  3.  * 並行実行されるスレッド
  4.  * (idle スレッド)
  5.  */
  6. void thread(void *exinf, void *dummy2, void *dummy3)
  7. {
  8.         volatile uint32_t i;
  9.         int n = 0;
  10.         int threadno = (int)exinf;
  11.         const char *graph[] = { "|", " +", " *" };
  12.         char c;
  13.         int32_t timeout;
  14.         uint32_t stime1, stime2;
  15.         int32_t v;
  16.         int ret;
  17.         while (true) {
  18.                 syslog(
  19.                         "thread%d is running (%03d). %s\r\n",
  20.                         threadno,
  21.                         ++n,
  22.                         graph[threadno-1]
  23.                 );
  24.                 /*
  25.                  * 以下の空ループは,k_busy_wait(400000); としてもよい.
  26.                  */
  27.                 for (i = 0; i < task_loop; i++);
  28.                 /*
  29.                  * イールド処理
  30.                  */
  31.                 v = k_thread_priority_get(k_current_get()) - HIGH_PRIORITY;
  32.                 if (yield_flag[v]) {
  33.                         yield_flag[v] = false;
  34.                         k_yield();
  35.                 }
  36.                 c = message[threadno-1];
  37.                 message[threadno-1] = 0;
  38.                 switch (c) {
  39.                 case 'e':
  40.                         syslog("#%d#return\r\n", threadno);
  41.                         /*
  42.                          * Zephyrにはext_tsk()に相当するAPIは存在しない.
  43.                          * 従ってreturnによってスレッド関数を抜けることでスレッドを終了させる.
  44.                          * (この処理はext_tsk()でも同様)
  45.                          */
  46.                         return;
  47.                 case 's':
  48.                         wakeup_flag[threadno-1] = WAKEUP_BY_TIMEOUT;
  49.                         syslog("#%d#k_sleep(K_FOREVER)\r\n", threadno);
  50.                         /*
  51.                          * k_sleep()をk_sem_take()に置き換える
  52.                          */
  53.                         //k_sleep(K_FOREVER); // コメントアウト!
  54.                         k_sem_take(p_sem_slp[threadno-1], K_FOREVER); // 追記!
  55.                         if (wakeup_flag[threadno-1] == WAKEUP_BY_RELEASE_WAIT) {
  56.                                 /*
  57.                                  * 強制的に起床させられた場合
  58.                                  */
  59.                                 syslog(
  60.                                         "Release Wait reported by "
  61.                                         "`k_sleep(K_MSEC(K_FOREVER))\' in line %d of `%s\'.\r\n",
  62.                                         __LINE__,
  63.                                         __FILE__
  64.                                 );
  65.                         }
  66.                         break;
  67. ...


243行目に注目です。

ソースのコメントの通りなのですが…。

μITRONの「tslp_tsk()」の場合は、タイムアウトだった場合は「E_TMOUT」を返します。

Zephyrの「k_sem_take()」の場合は、タイムアウトだった場合は「-EAGAIN」というエラー値を返します。

(「EAGAIN」の値の頭にマイナス「-」を入れてますので、負の値です。)

普通に待ち解除(セマフォの資源が返された)された場合は「0」が返ります。

このように「k_sleep()」とは異なり「k_sem_take()」の場合は、起床原因が判断できるのです!

  1. ...
  2.                 case 'S':
  3.                         wakeup_flag[threadno-1] = WAKEUP_BY_TIMEOUT;
  4.                         syslog("#%d#k_sleep(K_MSEC(10000))\r\n", threadno);
  5.                         /*
  6.                          * k_sleep()をk_sem_take()に置き換える
  7.                          */
  8.                         //k_sleep(K_MSEC(10000)); // コメントアウト!
  9.                         ret = k_sem_take(p_sem_slp[threadno-1], K_MSEC(10000)); // 追記!
  10.                         if (wakeup_flag[threadno-1] == WAKEUP_BY_RELEASE_WAIT) {
  11.                                 /*
  12.                                  * 強制的に起床させられた場合
  13.                                  */
  14.                                 syslog(
  15.                                         "Release Wait reported by "
  16.                                         "`k_sleep(K_MSEC(10000))\' in line %d of `%s\'.\r\n",
  17.                                         __LINE__,
  18.                                         __FILE__
  19.                                 );
  20.                         } else
  21.                         /*
  22.                          * k_sem_take()はタイムアウトの場合は「-EAGAIN」が返る
  23.                          */
  24.                         //if (wakeup_flag[threadno - 1] == WAKEUP_BY_TIMEOUT) { // コメントアウト!
  25.                         if (ret == -EAGAIN) { //追記!
  26.                                 /*
  27.                                  * タイムアウトで起床した場合
  28.                                  */
  29.                                 syslog(
  30.                                         "Timeout reported by "
  31.                                         "`k_sleep(K_MSEC(10000))\' in line %d of `%s\'.\r\n",
  32.                                         __LINE__,
  33.                                         __FILE__
  34.                                 );
  35.                         }
  36.                         break;
  37. ...


上記が追記されたことを前提として「sample1.c」の339行目以降に注目してください。

メインスレッド関数の冒頭部分です。

まず、新しく作ろうとするセマフォをポインタに代入します。

これは、子スレッドで使いやすいようにするためです。

配列のインデックスが「threadno - 1」となります。

forループで回している「k_sem_init()」というシステムコールは、セマフォの初期期化を行っています。

  1. ...
  2. /*
  3.  * メインルーチン
  4.  * (main スレッド)
  5.  */
  6. int main(void)
  7. {
  8.         T_THREAD_DEFINE_t thread_cfg[] = {
  9.                 {THREAD1, thread_stack1, K_THREAD_STACK_SIZEOF(thread_stack1), (void *)1, MID_PRIORITY},
  10.                 {THREAD2, thread_stack2, K_THREAD_STACK_SIZEOF(thread_stack2), (void *)2, MID_PRIORITY},
  11.                 {THREAD3, thread_stack3, K_THREAD_STACK_SIZEOF(thread_stack3), (void *)3, MID_PRIORITY},
  12.         };
  13.         char c;
  14.         k_tid_t threadid = THREAD1;
  15.         int threadno = 1;
  16.         int threadpri;
  17. #ifndef TASK_LOOP
  18.         volatile uint32_t i;
  19.         uint32_t stime1, stime2;
  20. #endif /* TASK_LOOP */
  21.         /*
  22.          * セマフォの初期化
  23.          *
  24.          * syslog()マクロ内でターミナル出力の排他処理のために使用.
  25.          */
  26.         k_sem_init(SEM1, 1, 1);
  27.         /*
  28.          * スリープ用セマフォを定義
  29.          */
  30.         p_sem_slp[0] = &sem_slp1;
  31.         p_sem_slp[1] = &sem_slp2;
  32.         p_sem_slp[2] = &sem_slp3;
  33.         
  34.         /*
  35.          * スリープ用セマフォを扱いやすいようにポインターの配列に代入
  36.          */
  37.         for (i = 0; i < 3; i++)
  38.                 k_sem_init(p_sem_slp[i], 0, 1);
  39.         syslog("Sample program starts.\r\n");
  40. ...


ここで「k_sem_init()」の説明を少しだけ。

システムコールのプロトタイプは、以下のようになっています。

  • int k_sem_init (struct k_sem * sem,
  •                 unsigned int initial_count,
  •                 unsigned int limit )


第1引数「sem」は、セマフォのポインタです。

第2引数「initial_count」は、セマフォカウントの初期値です。

第3引数「limit」は、セマフォカウントの上限値です。


…なんのこっちゃ?


sem」は、初期化をするセマフォを指定することは分かりますね?

セマフォというのは、ビーチフラッグのように複数のスレッドがカウント(資源とも言う)を取り合います。

用意されたフラッグの本数が「limit」であり、よーいドン!の段階で砂浜に立てらえたフラッグの本数が「initial_count」です。

このプログラムでは「limit = 1」なので用意されたフラッグが1本だけです。

そして「initial_count = 0」なので競技を開始した時点で砂浜にはフラッグが立てられていない状態です。

これでは、選手(スレッド)はスタッフがフラッグを立ててくれるまで待っていなければなりません。

これが、セマフォ資源待ち状態というヤツです。

フラッグが立てられると、一番早い選手がフラッグを奪い、いずれはスタッフに返します。

負けた選手はスタッフがそのフラッグを再び立ててくれるまで、やはり待ち状態となります。

勝った選手は「とったどー!」ということで「k_sem_take()」の返り値で「0」を返し、時間まで待ってもフラッグを取れなかった選手は「待ってたのにスタッフが次のフラッグを立ててくれなかった…スタッフゥー!?」と言って文句、つまり「-EAGAIN」を返します。

このセマフォ資源待ち状態をスリープに利用しようというのが今回の改造のポイントです。

まあ、今回の場合はフラッグ取りに行く選手は一人だけですけどね…。

では、実際にフラッグを立てる部分ですが、「sample1.c」の538行目以降に注目してください。

553行目の「k_sem_give()」というのが、セマフォ資源を1つ返すシステムコールです。

つまり、ビーチフラッグで言うところのスタッフ(メインスレッド)が選手(子スレッド)のためにフラッグを1本立てているところです。

  1. ...
  2.                 case 'w':
  3.                         /*
  4.                          * このサンプルでは,スリープ中のスレッドが起床された原因を判断するため,
  5.                          * wakeup_flag[]と組み合わせて同等の動作を再現する.
  6.                          *
  7.                          * TOPPERS/ASP の場合はwup_tsk()による起床要求を1回までキューイングできる.
  8.                          * (つまり2回以降はE_QOVRが返る)
  9.                          * 一方,Zephyrのk_wakeup()は起床要求のキューイングをしないことに留意する.
  10.                          */
  11.                         syslog("#k_wakeup(%d)\r\n", threadno);
  12.                         wakeup_flag[threadno-1] = WAKEUP_BY_WAKEUP;
  13.                         /*
  14.                          * k_wakeup()k_sem_give()に置き換える
  15.                          */
  16.                         //k_wakeup(threadid); // コメントアウト!
  17.                         k_sem_give(p_sem_slp[threadno-1]); // 追記!
  18.                         break;
  19. ...


これで準備は整いました。

この改造した「zephyr-sample」をビルドし、ターゲットで動かしてみます。

ターゲットをパソコンに接続しTeraTermで見てみましょう。


まずは、起床原因の区別です。

分かりやすいように「thread1」の優先度を他のスレッドよりも上げておきましょう。

ターミナルに対し「1」、「>」と入力します。

これから「thread1」をスリープさせるのですが、その間に「thread2」が起床します。

このように「thread1」だけ優先度を上げておけば、「thread1」が起床した時に「thread2」を押しのけて処理を取り戻すようになるので動作が分かりやすくなります。

TeraTerm - 1


では早速「thread1」をスリープさせます。

ターミナルに対し「S」と入力します。

大文字の「S」ですので、「thread1」に10秒間のスリープを命令したことになります。

この直後から「thread2」に処理が移っていることがわかりますね。

TeraTerm - 2


10秒以内にターミナルに対し「w」と入力し、待ち状態解除を命令します。

まずは、普通に待ち状態が解除された場合の反応です。

待ち状態解除を受けて「thread1」が復活しましたね。

これは「k_sem_take()」が返り値「0」を返した結果です。

TeraTerm - 3


次に、タイムアウトの場合を見てみましょう。

ターミナルに対し大文字の「S」を入力します。

TeraTerm - 4


このあと、何もせずにずっと見ていましょう。

で、10秒経つと…

TeraTerm - 5


…タイムアウトした旨が表示されます。

これは「k_sem_take()」がタイムアウトを示す返り値「-EAGAIN」を返した結果です。

ビーチフラッグの例えを思い出してください。

このようにすることで、選手(スレッド)は自身がフラッグ(資源)を得たのか?あるいは、時間まで待っても取れなかった(タイムアウト)か?を知っていますので待ち解除の原因は明確になります。

これで、起床原因の区別に関しては、一件落着です。


次に、キューイングの件です。

あるタスク(スレッド)に対し、それがスリープ状態ではない時に起床命令を実行した場合の違いです。

早速、すでに今起床している(スリープ状態ではない)「thread1」に対して起床命令を発行してみましょう。

念の為一度ターゲットをリセットし(他のスレッドよりも高くした「thread1」の優先度を元に戻す意味)、ターミナルに対し「1」、「w」と順に入力します。

TeraTerm - 6


この後、スリープ命令を小文字の「s」で発行すると、以下のように「thread1」はスリープになりません。

スリーブをセマフォに置き換えたことにより、キューイングが効くようになった証左です。

TeraTerm - 7


もう一度、スリープ命令を小文字の「s」で1回発行すると、ようやく「thread1」はスリープし、代わりに「thread2」に処理が移ります。

前回の起床命令により、キューイングを使い果たしたからです。

TeraTerm - 8


これにより「zephyr-sample」は、μITRON、すなわちTOPPERS/ASPのサンプルプログラムと全く同じ振る舞いとなりました。

選手(スレッド)が今待ち状態かどうかに関わらず、カウント(フラッグの本数)で待ちを解除するか否かを判断するため、キューイングもできていることになります。


このように、μITRONのデバイスドライバやアプリケーションをZephyrに移植する際には、μITRONのスリーブ処理をZephyr側ではセマフォに置き換えることにより、全く同じ動作を再現することができます。

そして、特にキューイングの件は、スレッドの遷移が難解であることもあり、これが原因の不具合は極めて発見し難いものとなります。(体験済み…。

割り込みハンドラからの起床命令を、何故かスレッドのスリープが取りこぼす…なんて場合は、まずこれが原因であることを疑うべきです。


さて、このμITRONとZephyrとでのスリーブの違いに関しては、実際に自分が痛い目に遭ってしまったので気合を入れて書いておこうと思いました。

逆を言えば他に両者の間に異なることは少なく、μITRONのプログラマーは、比較的短時間でZephyrを操れるようになります。

とはいえ、あとちょっとだけ引っかかった部分もありますので、備忘録として次回以降に書いていこうと思います。


<続く>

2026年2月13日金曜日

TOPPERSライセンス

OSSライセンスのメインページはこちらからどうぞ。

ライセンスの目次はこちらです。


名称:「TOPPERSライセンス」

TOPPERSプロジェクト・ロゴ


タイプ(◯:必要/△:回避可能/✕:不要):

・コピーレフト…×

・ライセンス文の掲示…〇(ソースコード頒布)/△(バイナリ頒布)

・コピーライト(著作権)の掲示…〇(ソースコード頒布)/△(バイナリ頒布)

・その他…


原文:


  • <Name of Software>

  • Copyright (C) <Year> by <Copyright Holder 1>
  • Copyright (C) <Year> by <Copyright Holder 2>
  • ...

  •  The above copyright holders grant permission gratis to use,
  •  duplicate, modify, or redistribute (hereafter called use) this
  •  software (including the one made by modifying this software),
  •  provided that the following four conditions (1) through (4) are
  •  satisfied.

  •  (1) When this software is used in the form of source code, the above
  •      copyright notice, this use conditions, and the disclaimer shown
  •      below must be retained in the source code without modification.

  •  (2) When this software is redistributed in the forms usable for the
  •      development of other software, such as in library form, the above
  •      copyright notice, this use conditions, and the disclaimer shown
  •      below must be shown without modification in the document provided
  •      with the redistributed software, such as the user manual.

  •  (3) When this software is redistributed in the forms unusable for the
  •      development of other software, such as the case when the software
  •      is embedded in a piece of equipment, either of the following two
  •      conditions must be satisfied:

  •    (a) The above copyright notice, this use conditions, and the
  •        disclaimer shown below must be shown without modification in
  •        the document provided with the redistributed software, such as
  •        the user manual.

  •    (b) How the software is to be redistributed must be reported to the
  •        TOPPERS Project according to the procedure described
  •        separately.

  •  (4) The above copyright holders and the TOPPERS Project are exempt
  •      from responsibility for any type of damage directly or indirectly
  •      caused from the use of this software and are indemnified by any
  •      users or end users of this software from any and all causes of
  •      action whatsoever.

  •  THIS SOFTWARE IS PROVIDED "AS IS." THE ABOVE COPYRIGHT HOLDERS AND
  •  THE TOPPERS PROJECT DISCLAIM ANY EXPRESS OR IMPLIED WARRANTIES,
  •  INCLUDING, BUT NOT LIMITED TO, ITS APPLICABILITY TO A PARTICULAR
  •  PURPOSE. IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS AND THE
  •  TOPPERS PROJECT BE LIABLE FOR ANY TYPE OF DAMAGE DIRECTLY OR
  •  INDIRECTLY CAUSED FROM THE USE OF THIS SOFTWARE.


日本語訳:


  • <ソフトウェアの名称>

  • Copyright (C) <開発年> by <著作権者1>
  • Copyright (C) <開発年> by <著作権者2>
  • ...

  •  上記著作権者は,以下の (1)〜(4) の条件を満たす場合に限り,本ソフトウェ
  •  ア(本ソフトウェアを改変したものを含む.以下同じ)を使用・複製・改変・
  •  再配布(以下,利用と呼ぶ)することを無償で許諾する.
  •  (1) 本ソフトウェアをソースコードの形で利用する場合には,上記の著作権
  •      表示この利用条件および下記の無保証規定が,そのままの形でソース
  •      コード中に含まれていること
  •  (2) 本ソフトウェアを,ライブラリ形式など,他のソフトウェア開発に使用
  •      できる形で再配布する場合には,再配布に伴うドキュメント(利用者マ
  •      ニュアルなど)に,上記の著作権表示,この利用条件および下記の無保
  •      証規定を掲載すること.
  •  (3) 本ソフトウェアを,機器に組み込むなど,他のソフトウェア開発に使用
  •      できない形で再配布する場合には,次のいずれかの条件を満たすこと.
  •    (a) 再配布に伴うドキュメント(利用者マニュアルなど)に,上記の著作
  •        権表示この利用条件および下記の無保証規定を掲載すること
  •    (b) 再配布の形態を,別に定める方法によって,TOPPERSプロジェクトに報
  •        告すること.
  •  (4) 本ソフトウェアの利用により直接的または間接的に生じるいかなる損害
  •      からも,上記著作権者およびTOPPERSプロジェクトを免責すること.また,
  •      本ソフトウェアのユーザまたはエンドユーザからのいかなる理由に基づ
  •      く請求からも,上記著作権者およびTOPPERSプロジェクトを免責すること.

  •  本ソフトウェアは,無保証で提供されているものである.上記著作権者およ
  •  びTOPPERSプロジェクトは,本ソフトウェアに関して,特定の使用目的に対す
  •  る適合性も含めて,いかなる保証も行わない.また,本ソフトウェアの利用
  •  により直接的または間接的に生じたいかなる損害に関しても,その責任を負
  •  わない.

解説:

TOPPERS(Toyohashi OPen Platform for Embedded Real-time Systems)プロジェクトは、「TOPPERS/ASP」や「TOPPERS/ATK(AUTOSAR)」など、各種RTOS(リアルタイムOS)、ならびに数々のミドルウェアの研究、開発、教育、および頒布などを行うことにより、日本の産業界に貢献することを目的としている団体です。

名古屋大学発のプロジェクトであり、会長は同大学教授の高田広章先生です。

このプロジェクトの研究成果物は、もちろん商用利用も含めて無償で使用することができますが、そのほとんどの研究成果物に付与されているライセンスが、この「TOPPERSライセンス」です。

そもそも「TOPPERSプロジェクト」は、NPO法人です。

NPO(Non-Profit Organization)とは、福祉、教育、環境など様々な分野で社会貢献活動を行う、非営利の民間団体の総称です。

非営利ですので、研究成果物を積極的に販売して「儲ける」ことを目的としていません。

しかしながら、研究を行ったり組織の運営をしたりするには、当然のことながらお金が必要です。

では、非営利であるNPO法人は、そのお金をどこから調達しているのでしょうか?

答えは、個人、企業からの寄付金や、経済産業省などからによる補助金ということになります。

こと後者の場合は公的資金となりますので、そう簡単に受けられるものではありません。

そのため、TOPPERSプロジェクトの研究成果物がどのように社会に貢献しているのか?…その実績のエビデンスを国に示す必要が生じます。

このライセンスは、そのための条件が盛り込まれている点が他のOSSライセンスとは一線を画します。


適用例:

TOPPERSプロジェクトのほとんどの研究成果物に付与されているライセンスです。

一部、TINETなどのミドルウェアでは、このライセンスと「三条項BSDライセンス」との混合になっているケースもありますので、注意が必要です。


商用利用のために執るべき行動:


ソースコード頒布の場合:

ライセンス条文(1)により、オリジナルのコピーライト(著作権)はそのまま掲示しましょう。

また、同じくライセンス条文(1)により、ライセンス文をそのまま改変することなく配布しましょう。


バイナリ頒布の場合:

ライセンス条文(3)(a)により、オリジナルのコピーライト(著作権)はそのまま掲示しましょう。

また、同じくライセンス条文(3)(a)により、ライセンス文をそのまま改変することなく配布しましょう。

ただし、ライセンス条文(3)(b)により、以下「その他」の条件を満たすことにより、これらは免除されます。


その他:

バイナリ頒布の場合のみ、ライセンス条文(3)(b)により、TOPPERSプロジェクトへ利用実績をレポート(報告)することによって、コピーライト(著作権)、およびライセンス文の掲示/配布が免除されます。

レポートと言っても、それほど煩雑な作業ではありません。

こちらのページにリンクされているWebフォームから送ることができます。

(2026/02/14現在、Webフォームはメンテナンス中です。)

また、TOPPERSプロジェクト事務局へメールでレポートすることもできます。

メールアドレスは以下の通り…(迷惑メール対策のため画像でお知らせします)。

メールアドレス


以下「窓際株式会社」の「窓際社員」さんが、「HogeHoゲートウェイ」という製品に「TOPPERS/ASPカーネル」を利用したことを報告する例です。

緑字の項目が要回答項目です。

一部の項目においては公開/非公開が選択できます。

  • TOPPERSプロジェクト
  • 事務局 担当者様

  • 平素よりお世話になっております。
  • 窓際株式会社の社員と申します。

  • 以下の通り、利用報告を申し上げます。

  • 【利用した開発成果物(公開)※】:TOPPERS/ASP3カーネル

  • 【製品名 (公開/非公開)※】:HogeHoゲートウェイ
  • 【製品名の公開可否※】:公開

  • 【応用分野(公開)※】:通信機器

  • 【製品化した会社名(公開/非公開)※】:窓際株式会社
  • 【製品化した会社名の公開可否※】:公開

  • 【業種(公開)※】:製造業

  • 【補足事項(公開/非公開)※】
  • 【補足事項の公開可否※】:非公開

  • 【氏名(非公開)※】:窓際社員
  • 【電子メール(非公開)※】:shain@madogiwa.co.jp
  • 【 所属・役職(学校・学年)(非公開)※】:開発部係員
  • 【その他(非公開)】

  • 以上

  • 今後とも何卒宜しくお願い申し上げます。

  • ==========================================================
  • 窓際株式会社
  • 窓際 社員
  • 直通電話: 090-xxxx-xxxx
  • メール: shain@madogiwa.co.jp
  • https://www.madogiwakoubou.com/
  • ==========================================================


このような企業や団体からの利用実績のレポートは、TOPPERSプロジェクトにとっては、お金と全く同価値の重要なものです。

私たちもライセンスを正しく守って使用し、日本のOSSの発展に貢献しましょう!

2026年2月5日木曜日

μITRONプログラマーがZephyrに挑戦! その7

前回からの続きです。

このテーマを最初からご覧になる場合はこちらからどうぞ。


μITRONとZephyrとの比較

TOPPERS/ASPのサンプルプロジェクトをZephyrに移植し、動かして見てみることで両者を比較し、Zephyrを学ぼう!という今回の趣旨、ここまでお付き合いくださいまして誠にありがとうございました。

また、この数ヶ月、本業で初めてZephyrを使用した製品の開発を完了させることができました。

今回は、その過程において私のようなμITRONを使用しているプログラマーの視点から、Zephyrを使う上で感じた個人的な違和感をメモに残しておきたいと思います。

例えば、μITRONのプログラムをZephyrに移植する際に障害となった問題(ていうかハマった部分)の回避策も一部紹介させていただきます。

理由は後述しますが、今後自分が関わるプロジェクトにおいて、もう二度とZephyrを使うことはないでしょう。

しかし、万が一、μITRONからZephyrに戻る様な状況(考えたくはないけど、不具合修正とか?)になった時のための備忘録です。

Zephyr vs μITRON


スリープのシステムコールの違い

TOPPERS/ASPのサンプルプロジェクトを移植する中で、一番最初に違和感を感じたのは、スリープのシステムコールです。

要するに、一定期間タスク/スレッドを待ち状態(スリープ)にし、自分よりも優先度の低いものも含め、他のタスク/スレッドへ処理を一時的に明け渡すという最もシンプルな仕組みです。

μITRONでは「tslp_tsk(<待ち時間>)」、一方、Zephyrで同じような働きをするシステムコールは「k_sleep(<待ち時間>)」です。

Zephyrの「k_sleep()」には、μITRONの「tslp_tsk()」では備わっていた以下3つの重要な機能がありません。


1.Zephyrの「k_sleep()」は、その起床原因がタイムアウトなのか、ウェイクアップ要求によるものかの区別がない

μITRONの「tslp_tsk()」は、以下の3つの起床原因を返します。


◯他のタスクや割り込みハンドラからの起床命令による起床(「E_OK」)

◯タイムアウトによる起床(「E_TMOUT」)

◯他のタスクや割り込みハンドラからの待ち状態(スリープ)強制解除による起床(「E_RLWAI」)


一度スリープに入ったタスクは、指定した時間が経過(タイムアウト)するか、または他のタスクや割り込みハンドラから叩き起こされる(ウェイクアップ)かによって起床します。

上記の通り、前者の場合は「E_TMOUT」、後者の場合は「E_OK」が関数の戻り値として返るため、起床原因が判別できます。

E_RLWAI」については、後述します。

一方、Zephyrの「k_wakeup()」の場合は、タイムアウトだろうが、他者からのウェイクアップだろうが、戻り値としては「0」しか返らず、起床原因が判りません。

これが困るのは、以下のような処理のケースです。

デバイスからの割り込みをタスクで処理するためのμITRONのデバイスドライバか何かの一部だと思ってください。

  1. /*
  2.  * メインタスク(MAIN_TASK)
  3.  */
  4. void main_task(intptr_t exinf)
  5. {
  6.     ER ercd;
  7.     
  8.     while (1) {
  9.         /*
  10.          * 5秒間割り込み待ち
  11.          */
  12.         ercd = tslp_tsk(5000);
  13.         if (ercd == E_TMOUT) {
  14.             /*
  15.              * 以降タイムアウトの場合のエラー処理
  16.              */
  17.             ...
  18.             continue;
  19.         }
  20.         /*
  21.          * 時間以内に割り込みが発生!
  22.          * 以降割り込みに応じた処理
  23.          */
  24.         ...
  25.     }
  26. }
  27. /*
  28.  * 割り込みハンドラ
  29.  */
  30. void int_handler(void)
  31. {
  32.     /*
  33.      * 割り込み発生!
  34.      * 割り込みハンドラからタスクへ処理を渡す
  35.      */
  36.     iwup_tsk(MAIN_TASK);
  37. }


タスクは開始されて間もなく「tslp_tsk()」で5秒間割り込みハンドラからの起床命令を待っています。

一方、割り込みハンドラには、タスクをウェイクアップさせるための「iwup_tsk(<タスクID>)」が配置されています。

割り込みハンドラに長々と処理を記述するのは、リアルタイム性を損なうのでご法度です。

ですので、これは何らか割り込みが発生した場合、割り込みハンドラからタスクへ処理が渡されるという非常にありがちな処理です。

前述の通り、Zephyrの場合は、ウェイクアップの原因が正常な割り込み処理の結果なのか?それとも、タイムアウト…すなわち、異常なのか?の区別が付かないことが問題です。

この問題の回避策は、後ほど提示します。


2.Zephyrの「k_sleep()」は、待ち状態期間中の「k_wakeup()」のキューイングを行わない

あるタスク(スレッド)に対し、それがスリープ状態ではない時に起床命令を実行した場合の違いです。

たとえは、以下は普通のμITRONの場合です。

STM32 Nucleo-144( NUCLEO-F207ZG)という評価ボードで動いています。

こもターゲットをパソコンに接続しTeraTermで見ている状態です。

TeraTerm - 1


ここで、以下の操作を行います。

すでに今起床している(スリープ状態ではない)「task1」に対して起床命令を発行します。

これには、ターミナルに対し「1」、「w」と順に入力します。

TeraTerm - 2


この後、スリープ命令を「s」で発行すると、以下のように「task1」はスリープになりません。

もし「task1」がスリープに入ったのなら「task2」に処理が切り替わるはずです。

でも、引き続き「task1」が続行していることが判ります。

TeraTerm - 3


もう一度、スリープ命令を「s」で1回発行すると、ようやく「task1」はスリープし、代わりに「task2」に処理が移ります。

TeraTerm - 4


このように、μITRONにおいては、そのタスクがスリープ状態ではない時でも自分に対して発行された起床命令を覚えていて(キューイング)、その後のスリープ命令を無視します。

これを「タスク起動要求キューイング」と呼んだりします。

キューイングの数はμITRONの実装によって異なりますが、TOPPERS/ASPの場合は通常1回です。


一方のZephyrのスリープ/起床命令には、この仕組みがありません。

以下は今回の「zephyr-sample」の場合です。

ターゲットをパソコンに接続しTeraTermで見てみましょう。

TeraTerm - 5


今起床している(スリープ状態ではない)「thread1」に対して起床命令を発行します。

これには、ターミナルに対し「1」、「w」と入力します。

TeraTerm - 6


この後、スリープ命令を「s」で1回発行すると…

TeraTerm - 7


…このように、即座に「thread2」に処理が移ります。

つまり、スリープ状態でなかった時に発行された起床命令はガン無視です。

このことは、μITRONからZephyrへソフトウェアを移植する際に非常に発見しづらいバグを生む可能性があります。

タスク起動要求キューイングの目的は、起床命令を取りこぼさないことです。

前述したようなデバイスドライバの処理などにおいて、割り込み割り込みハンドラからの起床命令を取りこぼすことは致命的です。

割り込みハンドラからの起床命令が発行された時点で、それを待つスレッドが必ずしもスリープ状態であるとは限りません。

割り込み頻度が高いデバイスであれば尚更です。

特に、そのデバイスドライバのソースコードが十分に検証されたμITRON用のものであり、それをZephyrに移植しようとした場合、これを知らないと「来るはずの割り込みが来ない!!」と言って、大いに焦ることになります。

…はい、私はその罠に見事にかかりました!

かなり長時間にわたり悩み続けましたよ…。

この問題も、どうやって回避したのかを後ほど提示します。


3.Zephyrの「k_sleep()」は、起床原因として「待ち状態の強制解除」がない

これは、前の2つに比べて重要性はあまり高くないものの、μITRONでは使っている人が多い機能だと思います。

おさらいですが、μITRONの「tslp_tsk()」は、以下の3つの起床原因を返します。


◯他のタスクや割り込みハンドラからの起床命令による起床(「E_OK」)

◯タイムアウトによる起床(「E_TMOUT」)

◯他のタスクや割り込みハンドラからの待ち状態(スリープ)強制解除による起床(「E_RLWAI」)


3つ目の「待ち状態(スリープ)強制解除」の説明です。

待ち状態(スリープ)強制解除は、μITRONでは「(i)wup_tsk(<タスクID>)」の代わりに「(i)rel_wai(<タスクID>)」をコールすることにより発行できます。

共にスリープしているタスクを起床させる機能を持つシステムコールですが、「(i)wup_tsk()」と「(i)rel_wai()」では何が違うのでしょう?

トロンフォーラムの配布する「µITRON4.0仕様」には、これについては以下のような違いが説明されています。

  • rel_waiとwup_tskには次のような違いがある。

  • 1. wup_tskは起床待ち状態からのみ待ち解除するが、rel_waiは任意要因による待ち状態から待ち解除する。

  • 2. 起床待ち状態になっていたタスクから見ると、wup_tskによる待ち解除は正常終了(E_OK)であるのに対して、rel_waiによる強制的な待ち解除はエラー(E_RLWAI)である。

  • 3. wup_tskの場合は、対象タスクが起床待ち状態でない場合には、要求がキューイングされる。それに対してrel_waiの場合は、対象タスクが待ち状態でない場合にはE_OBJエラーとなる。

μITRON4.0仕様 - WG024-S001-04.03.03 - P.124「rel_wai/irel_wai 待ち状態の強制解除」より抜粋


1番目の「任意要因による待ち状態」とは、スリープ=起床待ち以外の待ち状態のことです。

起床待ちは「slp_tsk()」や「tslp_tsk()」でタスクが「待ち」に入った状態です。

それ以外の「待ち」とは、セマフォの資源待ちシステムコールである「wai_sem(<セマフォID>)」やイベント待ちシステムコール「wai_flg(フラグID)」などにより、それぞれの=任意の要因でタスクが「待ち」に入った状態です。

つまり「wup_tsk()」は起床待ち=スリープだけを解除するけれども、一方「rel_wai()」は、それ以外の全ての待ち状態を強制的に解除します…と言っています。

2番目は「slp_tsk()」や「tslp_tsk()」で入った起床待ち=スリープ状態が待ち状態解除された後、これらのシステムコールが返り値を返しますが、その要因が「wup_tsk()」と「rel_wai()」の場合で異なりますよ…と言っています。

「wup_tsk()」の場合は「E_OK」で正常終了、一方「rel_wai()」の場合は「E_RLWAI」がエラーとして返ることを説明しています。

3番目のキューイングに関しては、既に前述の通りです。

「rel_wai()」は、タスク起動要求キューイングをされません。

つまり、該当のタスクが「待ち」状態でない時点でコールしてもキューイングはされず、「E_OBJ」エラーが返ることを説明しています。


一方のZephyrでは、この「待ち状態(スリープ)強制解除」に類するシステムコールが見当たりません。

μITRONの待ち状態(スリープ)強制解除は、意図しない待ち解除が発生するため、強力ですが危険です。

上手く使えばデッドロック防止やエラー処理などにおいて便利な場合がありますが、そもそも、そのような設計をするべきではない…というZephyrの設計思想なのでしょう。

確かにその通りですが、キューイングの有り無しは別として、起床原因の切り分けをするために便利なので(「E_OK」なのか?「E_RLWAI」なのか?)、よく使っているμITRONプログラマーもいるかもしれません。

私は、結構気軽に使っちゃってます。

μITRONのプログラムをZephyrに移植する際に、もし元ソースで「rel_wai()」が使われていたり、スリープが「E_RLWAI」で待っているような処理を見かけた場合、残念ながらこの問題に対するスマートな解決策はありません。

今回の「zephyr-sample」では、起床命令発行時にグローバル変数を使用して起床原因を設定し、起床した直後のスレッドでそれを判断するという、スマートじゃない方法で回避してます…。

すんません。


さて、μITRONの「tslp_tsk()」が持っているのに、Zephyrの「k_sleep()」にはない3つの機能を紹介させていただきました。

3つ目の待ち状態(スリープ)強制解除に関しては、今のところ良い回避策は思い付きませんが、1つ目の起床原因の区別と、2つ目のキューイングに関しては回避策があります。

どうすれば良いか?ですが…。

結論を言ってしまえば、その回避策とはスリープをセマフォに置き換えることです。

今回の「zephyr-sample」では「k_sleep()」で起床待ち状態に入ったタスクを「k_wakeup()」で起床させています。

それを「k_sem_take()」でセマフォ資源待ち状態に入ったタスクを「k_sem_give()」で起床(正しくはセマフォ資源を返す)させるようにします。

具体的に「zephyr-sample」を改造してみましょう。


…というところで一旦切りましょう。

このスリープの問題、開発していた時に最後までハマった部分なので、詳細に書いておきたいと思いました。

よくよく調べれば簡単な話だったんですけどね。


<続く>

2025年12月30日火曜日

TOPPERS/ASP - PIC24F版 その3

前回からの続きです。

このテーマを最初からご覧になる場合はこちらからどうぞ。


開発環境の構築(Cygwin編)

続きまして「Cygwin」のインストールを行います。

今回は開発環境にナウい「Visual Studio Code」を使うと言いましたが、TOPPERS/ASPを使用するにあたってはどうしてもこの「Cygwin」が必要になります。

「Cygwin」の中の「make」や「perl」といったツールを使いたいからです。

実作業は、このページ(TOPPERS/ASPのビルドからデバッグまで~Cygwinの導入)を参考にしてください。


「Cygwin」がインストールできたら、ここまでの作業が上手くいっているかどうか確認しておきましょう。

次のページ(TOPPERS/ASPのビルドからデバッグまで~サンプルプロジェクトのビルド)を参照してください。

ただし、今回使用するソースコードはこのページの冒頭の「NUCLEO-F401RE STM32 Nucleo-64」開発ボード用のTOPPERS/ASPカーネル簡易パッケージではなく、「Explorer 16/32 Deveopment Kit」用のものを使います。

ソースコードのダウンロードはこちらからどうぞ。

また、「Github」を使いたい方は以下のコマンドでソースコードのクローンを行います。


$ git clone https://github.com/RyutaroMorita/asp_pic24f_gcc.git

Cygwinターミナル - 1


ダウンロードとGithub、いずれの場合も「asp_pic24f_gcc」というディレクトリの名前を「asp_1.9.2」などと改名すると、上記のページと同じ状況になります。

また、今回は「OBJ」ディレクトリを作成する必要はありません。

これは既に用意されていますので、そのまま「OBJ」ディレクトリに移動し「make~」コマンドを実行してください。


<補足>

「make depend」の実行時に以下のようなエラーが出ることがあります。

Cygwinターミナル - 2


これは、「extract24.exe」というユーティリティの実行権限がないためプログラムが実行できないという意味です。

このユーティリティは「make depend」を実行する過程で呼ばれます。

これを解決するには、以下のコマンドでこのユーティリティに実行権限を与えます。

以下のコマンドを実行します。


$ chmod 755 ../arch/pic24f_gcc/utils/extract24.exe


じゃあ、この「extract24.exe」って何なの?…ということですが、これは私の自作のユーティリティです。

PIC24FマイコンのバイナリをTOPPERS/ASPのコンフィギュレーターに通すときに絶対に必要だったもので、これはPIC24のアーキテクチャに起因し…と、説明が長くなりそうなので、機会があれば「まとめ」の記事にでも説明したいと思います。


開発環境の構築(Visual Studio Code編)

お次は「Visual Studio Code」のインストールを行います。

すでに統合開発環境(IDE)として、前回「MPLAB X IDE」をインストールしました。

もちろん、これを使って開発を進めても問題ありません。

しかしながら、実際にコードを書いてみると判りますが「Visual Studio Code」は軽量で非常に優秀なエディターです。

強力な検索機能や、ハイライト表示、コード補完機能(構造体変数に「.」と打つと候補のリストビューが出てきたりするヤツね!)など、生産性は大きく向上すると思います。

こういったものは個人の好み(ていうか信仰)がありますので、お好みのエディターをご使用ください。

とりま、この記事ではエディターとして「Visual Studio Code」、デバッガモニタとして「MPLAB X IDE」を使用するものとして説明します。

さて、その「Visual Studio Code」のインストール方法ですが、このページ(μITRONプログラマーがZephyrに挑戦! その3)を参考にしてください。

今回のPIC24マイコンとは全く違う記事ですが、インストーラーのダウンロードから日本語設定まで説明してます。


Visual Studio Codeの使い方

さて、インストールして日本語化まで行った「Visual Studio Code」、開いている方は、一旦閉じちゃってください。

そして、ダウンロードして一度ビルドまで通した「Explorer 16/32 Development Kit」用のTOPPERS/ASPのソースディレクトリを見てみましょう。

名前は「asp_pic24f_gcc」(改名した場合は「asp_1.9.2」ですね)というディレクトリでしたね。

記事通りであれば以下のパスです。


C:\cygwin64\home\<ユーザー名>\asp_pic24f_gcc

TOPPERS/ASPソースツリーのディレクトリ - 1


エクスプローラをちょっとだけ下にスクロールしてやりますと「asp_pic24f_gcc.code-workspace」というファイルがあると思います。

こいつをダブルクリックしてください。

TOPPERS/ASPソースツリーのディレクトリ - 2


以下のように「Visual Studio Code」が起動し「Explorer 16/32 Deveopment Kit」用のTOPPERS/ASPワークスペースが開かれます。

「Visual Studio Code」 - 1


この過程で以下のようなポップアップが出るかもしれません。

その場合は「はい、作成者を信頼します」をクリックしてください。

信頼して!

「Visual Studio Code」 - 2


次に、このソースツリーに対し、ビルドやクリーンなど色々と操作するためのターミナルを表示させましょう。

それには「Visual Studio Code」上方に配置されている「表示」メニューから「ターミナル」をクリックします。

「Visual Studio Code」 - 3


すると「Visual Studio Code」下方にターミナルが表示されました。

「Visual Studio Code」 - 4


このターミナルは、すでにインストールした「Cygwinターミナル」そのものです。

ここでは「Cygwinターミナル」と全く同じ操作を行うことができます。


例えば、ソースツリーの「asp_pic24f_gcc」(改名した場合は「asp_1.9.2」)ディレクトリの中の「OBJ」へ移動するために、以下のコマンドを入力します。

ルートは「C:\cygwin64\home\<ユーザー名>」です。


$ cd ./asp_pic24f_gcc/OBJ


サンプルプロジェクト「OBJ」は「Cygwinターミナル」で先程、確認のためにビルドしていますよね?

それを一旦キレイにクリアしましょう。

以下のコマンドを入力します。


$ make realclean


実行結果は以下の通り。

「OBJ」ディレクトリの中にあった「~.d」とか「~.o」などのビルド生成物がクリアされ、ソースコードのみが残った状態になります。

「Visual Studio Code」 - 5


これで作業中に「Visual Studio Code」と「Cygwinターミナル」の二窓をしなくて良くなります。

※「二窓」とは、Windowsで複数のアプリを同時に起動させるという意味のオジサン言葉である。昔は複数のアプリ(特にブラウザ)を立ち上げるとWindowsが頻繁にフリーズしたため、オジサンたちにとってはリスキーな行為であるというトラウマが根強く残る。


ソースコードの編集は、一般的な統合開発環境(IDE)と同様に、リストビューからファイルを選び、メインビューでソースコードを表示して、それを編集するスタイルとなります。

例えば、今回のビルド対象であるサンプルプロジェクト「OBJ」のメインファイルを編集したい場合は「Visual Studio Code」左側のエクスプローラーから「OBJ」ディレクトリ以下の「sample1.c」をダブルクリックします。

中央のメインビューにソースコードが表示されたでしょうか?

「Visual Studio Code」 - 6


編集が終わったらソースコードを保存して「Visual Studio Code」下方のターミナルで以下のコマンドでビルドを行います。


$ make depend

$ make


全く「Cygwinターミナル」と一緒ですね!


以上で、開発環境の構築は終わりました。

次回からは、このPIC24版TOPPERS/ASPのサンプルプロジェクトを実際に実機(Explorer 16/32 Development Kit)で動かしていく準備をしていきましょう。


本業が地獄みたいな状態で、3ヶ月以上もブログの更新を止めてしまいました。

年末のご挨拶をしたいので何とか年内に更新を!と…。


皆様、どうか良いお年を!

来年も皆様にとって素晴らしい年になりますように!


<続く>

μITRONプログラマーがZephyrに挑戦! その8

前回からの続き です。 このテーマを最初からご覧になる場合は こちら からどうぞ。 スリープからセマフォへの置き換え 前回、ソフトウェアをμITRONからZephyrへに移植する際に、両者ではスリープのシステムコールの振る舞いに差異があることを説明しました。 これが結構「罠」的な...