ランデブポート
ランデブによるタスク間の同期
ランデブは、タスク間での同期と通信を行うための機能の一種です。
あるタスクから別のタスクへの処理の依頼と、処理の依頼を受けたタスクから処理を依頼したタスクへの処理結果の返却を、一連の手順としてサポートします(図1)。
このようなタスクが同期する(待ち合わせる)ためのオブジェクトを、ランデブポート(Rendezvous Port)と呼びます。
ランデブを利用する典型的な例としては、クライアント/サーバモデルによるタスク間通信があります。図1で言えば、taskCL
がクライアント、taskSV
がサーバに相当します。それぞれで利用するシステムコールは、クライアント側がtk_cal_por
、サーバ側がtk_acp_por
とtk_rpl_rdv
です。クライアントがサーバを呼び出し(call
)、サーバがクライアントによる呼び出しを受け付け(accept
)て、サーバ側での処理が完了したら結果をクライアントに返答(reply
)します。
実際に図1をプログラミングした例をリスト1に示します。
【リスト1:ランデブの使用例】
#include <basic.h> #include <tk/tkernel.h> #include <tm/tmonitor.h> ID porid; #define ACP_PTN (0xFFFFFFFF) /* 受付側選択条件 */ #define CAL_PTN (0x00000001) /* 呼出側選択条件 */ #define MSGSZ 16 /* メッセージのサイズ */ void taskSV( INT stacd, void *exinf ) { RNO rdvno; while(1){ // tk_dly_tsk( 100 ); /* ランデブ受付発行前に必要な処理 */ tk_acp_por( porid, ACP_PTN, &rdvno, NULL, TMO_FEVR ); /* 受付 */ /* クライアントから依頼された処理 */ tk_rpl_rdv( rdvno, NULL, 0 ); /* 返答 */ /* ランデブ終了後に必要な処理 */ } tk_ext_tsk(); } void taskCL( INT stacd, void *exinf ) { while(1){ /* ランデブ呼出発行前に必要な処理 */ tk_cal_por( porid, CAL_PTN, NULL, 0, TMO_FEVR ); /* 呼出 */ /* ランデブ終了後に必要な処理 */ } tk_ext_tsk(); } EXPORT INT usermain( void ) { T_CPOR cpor = { NULL, TA_TFIFO, 0, 0 }; T_CTSK ctskSV = { NULL, TA_HLNG|TA_RNG0, taskSV, 2, 4*1024 }; T_CTSK ctskCL = { NULL, TA_HLNG|TA_RNG0, taskCL, 3, 4*1024 }; ID tskidSV, tskidCL; tk_chg_pri(TSK_SELF, 1); porid = tk_cre_por( &cpor ); tskidSV = tk_cre_tsk( &ctskSV ); tk_sta_tsk( tskidSV, 0 ); tskidCL = tk_cre_tsk( &ctskCL ); tk_sta_tsk( tskidCL, 0 ); tk_slp_tsk(TMO_FEVR); return 1; }
ところで、リスト1では、taskSV
がランデブポートで待っているところにtaskCL
が処理を依頼することでランデブが成立しています。この時の両方のタスク状態の変化を示すと図2のようになります。
一方、リスト1でコメントアウトされている15行目のtk_dly_tsk(100)
を有効にしてから実行すると、taskSV
がランデブポートでの受付待ちを行う前に、taskCL
が処理の依頼をするようになります。この時の両方のタスク状態の変化を示すと図3のようになります。
ランデブする時は、サーバ側とクライアント側のどちらが先に待ち合わせを開始してもよく、ランデブ成立後の動作に違いはありません。
ランデブによるメッセージの受け渡し
ランデブが成立すると、ランデブを呼出したタスクから受け付けたタスクへ、呼出メッセージが渡されます。
具体的には、呼出側タスクがtk_cal_por
で指定したmsg
以下の領域のcmsgsz
バイトが、受付側タスクがtk_acp_por
で指定したmsg
以下の領域にコピーされます。
この様子を図4に示します。
ランデブによる結果の返信
さて、同期するだけであれば、これまでに説明してきたセマフォやイベントフラグでも実現できますし、メールボックスやメッセージバッファを利用すれば、同期と同時にメッセージを送ることも可能です。
ランデブに相当する機能をこれらの同期・通信機能を組み合わせて実現することも可能ですが、ランデブを利用すると、サーバ側からクライアント側に返答のメッセージを送ることができます。クライアントタスクはサーバの処理が完了して返答メッセージが送られてくるまで待ち状態に入りますので、返答メッセージ用の領域を別途用意する必要がなく、アプリケーションを書きやすくなります。
ランデブ終了時に返答メッセージを送る際の様子を図5に示します。
実際にメッセージの受け渡しを行うプログラムの例をリスト2に示します。
なお、処理の流れのイメージを分りやすくするため、リスト2では簡単なサーバの処理を追加してあります(*1)。
(*1)taskCLからランデブ呼出時のメッセージとして渡された数値を、taskSV
で16進数の文字列に変換し、変換した結果(文字列)を、ランデブ終了時の返答メッセージとしてtaskCL
に送信しています。
【リスト2:メッセージの受け渡しを伴うランデブの使用例】
#include <basic.h> #include <tk/tkernel.h> #include <tm/tmonitor.h> ID porid; #define ACP_PTN (0xFFFFFFFF) /* 受付側選択条件 */ #define CAL_PTN (0x00000001) /* 呼出側選択条件 */ #define MSGSZ 16 /* >= "0x12345678\n" */ IMPORT INT hex2str( char *buf, UW val ); void taskSV( INT stacd, void *exinf ) { RNO rdvno; char msg[MSGSZ]; INT sz; while(1){ // tk_dly_tsk( 100 ); /* ランデブ受付発行前に必要な処理 */ tk_acp_por( porid, ACP_PTN, &rdvno, msg, TMO_FEVR ); /* 受付 */ /* クライアントから依頼された処理 */ sz = hex2str( msg, *(UW*)msg ); tk_rpl_rdv( rdvno, msg, sz ); /* 返答 */ /* ランデブ終了後に必要な処理 */ } tk_ext_tsk(); } void taskCL( INT stacd, void *exinf ) { UW msg[MSGSZ/sizeof(UW)+1]; UW i = 0; while(1){ /* ランデブ呼出発行前に必要な処理 */ msg[0] = i++; /* 呼出メッセージを作成 */ tk_cal_por( porid, CAL_PTN, msg, sizeof(UW), TMO_FEVR ); /* 呼出 */ /* ランデブ終了後に必要な処理 */ tm_putstring( (UB*)msg ); /* 変換結果の出力 */ } tk_ext_tsk(); } EXPORT INT usermain( void ) { T_CPOR cpor = { NULL, TA_TFIFO, sizeof(UW), MSGSZ }; T_CTSK ctskSV = { NULL, TA_HLNG|TA_RNG0, taskSV, 2, 4*1024 }; T_CTSK ctskCL = { NULL, TA_HLNG|TA_RNG0, taskCL, 3, 4*1024 }; ID tskidSV, tskidCL; tk_chg_pri(TSK_SELF, 1); porid = tk_cre_por( &cpor ); tskidSV = tk_cre_tsk( &ctskSV ); tk_sta_tsk( tskidSV, 0 ); tskidCL = tk_cre_tsk( &ctskCL ); tk_sta_tsk( tskidCL, 0 ); tk_slp_tsk(TMO_FEVR); return 1; } /* 数値を16進数の文字列に変換 */ IMPORT INT hex2str( char *buf, UW val ) { INT len; INT i; /* 上位桁にある 0 はスキップ */ for( i = sizeof(UW)*2 - 1; i > 0; i-- ){ if( (val >> 4*i) != 0 ){ break; } } /* 16進数(文字列)に変換 */ *(buf+0) = '0'; *(buf+1) = 'x'; len = 2; do{ *(buf+len) = (val >> 4*i) & 0x0F; *(buf+len) += (*(buf+len) < 10)? '0':'A'-10; len++; }while( i-- > 0 ); *(buf+len++) = '\n'; *(buf+len++) = '\0'; return len; }
まとめ
今回はランデブの基本的な機能について説明しましたが、この他にも、呼出側と受付側でビットパターン(*2)を指定することによってランデブするタスクを選択する機能や、一旦受け付けた処理を別のランデブポートに回送する機能が用意されています。
T-Kernelには、これまでの連載で説明してきたような同期・通信の機能が豊富に用意されています。これらの機能を適切に組み合わせてアプリケーションを開発することで、効率的なプログラムを開発することが可能となります。まずは概略からで構いませんので、各機能についてよく理解してT-Kernelを活用してください。
(*2)リストのACP_PTN
(受付側選択条件)とCAL_PTN
(呼出側選択条件)が、このビットパターンになっています。受付側と呼出側のビットパターンの論理積が0でない場合にランデブが成立します。
こちらも是非
“もっと見る” RTOS編
Toppers/ASP3の使い方
SOLID-OSによる割り込みは、Toppers/ASP3がベースになっており、カーネルの管理下で割り込みの処理を行いますので、「割り込みサービスルーチン(ISR)」または「管理内割り込み」と呼んだりします。それ以外のものは、「割り込みハンドラ」もしくは「カーネル管理外割り込み」と呼びます。
組み込みソフトウェア開発にRTOSを採用する場合の「4つの心得」
リアルタイムOSを使った組み込みソフトウェア開発を行う際、覚えておいたほうがよい「4つの心得」を紹介しましょう。
組み込みOSに最適なのは?リアルタイムOSとLinuxの違い
組み込みシステムの構成を考えていく上で、どのOSを採用するかは、開発初期段階においてとても重要です。一般的には、時間的制約があるシステムの場合はリアルタイムOSが、ネットワークやファイルシステム、高度なグラフィカル表示が必要な場合はLinuxが向いていると言われています。