何しろSRAMが2KBしかないとすれば、TCB(Thread Control Block)が消費するであろう100バイトすら“lots” of memory扱いになるのも無理ないところである。ただし、そういうMemory-Constrained Networked Embedded DeviceのプログラミングにはEvent-driven Modelの方が適している(Photo02)(図3)。その一方で、これをPreemptiveの構成でやろうとするとState Machineを作るような構造になるのもまた事実だ(図4)。
もちろん、大規模なシステムであればこれらのコストはさほど大きくないが、何しろMemory-Constrained Networked Embedded Device相手だから、この際多少の安全性の欠如には目を瞑っても、もっと手軽にEvent-Drivenを記述したい、というのがProtothreadsの開発動機である(図5)。
では具体的には? という例が図6になる。あるThread(というか、実はThreadである必要もなく、ProcessでもTaskでもいいのだが)がスタートしたら、後は前処理→PT_WAIT_UNTILで待機→……という簡単な方法でEvent Drivenが記述できる。
もう少し具体的なサンプルがリスト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); } }
通常ならこれは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.