ランデブ

ランデブポート

ランデブによるタスク間の同期

ランデブは、タスク間での同期と通信を行うための機能の一種です。

あるタスクから別のタスクへの処理の依頼と、処理の依頼を受けたタスクから処理を依頼したタスクへの処理結果の返却を、一連の手順としてサポートします(図1)。

図1

【図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のようになります。

図2

【図2:サーバが先に待っている場合】

一方、リスト1でコメントアウトされている15行目の tk_dly_tsk(100) を有効にしてから実行すると、taskSVがランデブポートでの受付待ちを行う前に、taskCLが処理の依頼をするようになります。この時の両方のタスク状態の変化を示すと図3のようになります。

図3

【図3:クライアントが先に待っている場合】

ランデブする時は、サーバ側とクライアント側のどちらが先に待ち合わせを開始してもよく、ランデブ成立後の動作に違いはありません。

ランデブによるメッセージの受け渡し

ランデブが成立すると、ランデブを呼出したタスクから受け付けたタスクへ、呼出メッセージが渡されます。

具体的には、呼出側タスクがtk_cal_porで指定したmsg以下の領域のcmsgszバイトが、受付側タスクがtk_acp_porで指定したmsg以下の領域にコピーされます。

この様子を図4に示します。

図4

【図4:ランデブ成立時の呼出メッセージの受け渡し】

ランデブによる結果の返信

さて、同期するだけであれば、これまでに説明してきたセマフォやイベントフラグでも実現できますし、メールボックスやメッセージバッファを利用すれば、同期と同時にメッセージを送ることも可能です。

ランデブに相当する機能をこれらの同期・通信機能を組み合わせて実現することも可能ですが、ランデブを利用すると、サーバ側からクライアント側に返答のメッセージを送ることができます。クライアントタスクはサーバの処理が完了して返答メッセージが送られてくるまで待ち状態に入りますので、返答メッセージ用の領域を別途用意する必要がなく、アプリケーションを書きやすくなります。

ランデブ終了時に返答メッセージを送る際の様子を図5に示します。

図5

【図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でない場合にランデブが成立します。