Fiberもどきの「Protothreads」は既存RTOSとの組み合わせで力を発揮するリアルタイムOS列伝(43)(2/3 ページ)

» 2024年02月06日 07時00分 公開
[大原雄介MONOist]

「もっと手軽にEvent-Drivenを記述したい」

 何しろSRAMが2KBしかないとすれば、TCB(Thread Control Block)が消費するであろう100バイトすら“lots” of memory扱いになるのも無理ないところである。ただし、そういうMemory-Constrained Networked Embedded DeviceのプログラミングにはEvent-driven Modelの方が適している(Photo02)(図3)。その一方で、これをPreemptiveの構成でやろうとするとState Machineを作るような構造になるのもまた事実だ(図4)。

図3 図3 “stack-based threaded approach”は要するに割り込みが入ったら現在のContextをStackに積んでISRに飛んで割り込みのハンドリングを行い、終わったらStackからContextを復帰させて処理を再開するという従来型の方法のことである[クリックで拡大] 出所:Adam Dunkels氏の論文
図4 図4 これはPreemptiveの宿命であって、Critical Sectionに入る前に他のThreadを止めないと危険なことになる[クリックで拡大] 出所:Adam Dunkels氏の論文

 もちろん、大規模なシステムであればこれらのコストはさほど大きくないが、何しろMemory-Constrained Networked Embedded Device相手だから、この際多少の安全性の欠如には目を瞑っても、もっと手軽にEvent-Drivenを記述したい、というのがProtothreadsの開発動機である(図5)。

図5 図5 メモリ使用量が「おおむね」event-drivenと同程度、という話はあとでまた出てくる[クリックで拡大] 出所:Adam Dunkels氏の論文

 では具体的には? という例が図6になる。あるThread(というか、実はThreadである必要もなく、ProcessでもTaskでもいいのだが)がスタートしたら、後は前処理→PT_WAIT_UNTILで待機→……という簡単な方法でEvent Drivenが記述できる。

図6 図6 “PT_BEGIN()”と“PT_END()”で挟むのがポイント[クリックで拡大] 出所:Adam Dunkels氏の論文

 もう少し具体的なサンプルがリスト1だ。これはexample-small.cとして提供されているもので、main()の中でprotothread1()とprotothread2()を無限ループで呼んでいるだけである。で、protothread1()はprotothread2()がflagを立てるのを待って自分のflagを立て、protothread2()は逆にprotothread1()がflagを立てるのをまって自分のflagを立て、という単純な仕組みである。

/**
 * This is a very small example that shows how to use
 * protothreads. The program consists of two protothreads that wait
 * for each other to toggle a variable.
 */
/* We must always include pt.h in our protothreads code. */
#include "pt.h"
#include <stdio.h> /* For printf(). */
/* Two flags that the two protothread functions use. */
static int protothread1_flag, protothread2_flag;
/**
 * The first protothread function. A protothread function must always
 * return an integer, but must never explicitly return - returning is
 * performed inside the protothread statements.
 *
 * The protothread function is driven by the main loop further down in
 * the code.
 */
static int
protothread1(struct pt *pt)
{
  /* A protothread function must begin with PT_BEGIN() which takes a
     pointer to a struct pt. */
  PT_BEGIN(pt);
  /* We loop forever here. */
  while(1) {
    /* Wait until the other protothread has set its flag. */
    PT_WAIT_UNTIL(pt, protothread2_flag != 0);
    printf("Protothread 1 running\n");
    /* We then reset the other protothread's flag, and set our own
       flag so that the other protothread can run. */
    protothread2_flag = 0;
    protothread1_flag = 1;
    /* And we loop. */
  }
  /* All protothread functions must end with PT_END() which takes a
     pointer to a struct pt. */
  PT_END(pt);
}
/**
 * The second protothread function. This is almost the same as the
 * first one.
 */
static int
protothread2(struct pt *pt)
{
  PT_BEGIN(pt);
  while(1) {
    /* Let the other protothread run. */
    protothread2_flag = 1;
    /* Wait until the other protothread has set its flag. */
    PT_WAIT_UNTIL(pt, protothread1_flag != 0);
    printf("Protothread 2 running\n");
    
    /* We then reset the other protothread's flag. */
    protothread1_flag = 0;
    /* And we loop. */
  }
  PT_END(pt);
}
/**
 * Finally, we have the main loop. Here is where the protothreads are
 * initialized and scheduled. First, however, we define the
 * protothread state variables pt1 and pt2, which hold the state of
 * the two protothreads.
 */
static struct pt pt1, pt2;
int
main(void)
{
  /* Initialize the protothread state variables with PT_INIT(). */
  PT_INIT(&pt1);
  PT_INIT(&pt2);
  
  /*
   * Then we schedule the two protothreads by repeatedly calling their
   * protothread functions and passing a pointer to the protothread
   * state variables as arguments.
   */
  while(1) {
    protothread1(&pt1);
    protothread2(&pt2);
  }
}
リスト1 Protothreadsの「example-small.c」

 通常ならこれはcreate_thread()あたりを使って親プロセスの下にThread 1/2を生成し(Thread 0は親プロセスそのもの)、Thread 1/2同士でSemaphoreないしEvent Flagなりを使って排他制御を行う格好になる(ThreadをサポートしてないOSなら、create_process()で子プロセスを2つ作るか、それもなければTaskを2つ新規生成する)が、Protothreadsでは一切新規のTask/Process/Threadを生成せずにこれを実現できる、という仕組みだ。

Copyright © ITmedia, Inc. All Rights Reserved.