[BitVisor-devel:32] BitVisor RPC patch
Shougo Matsushita
shougo @ softlab.cs.tsukuba.ac.jp
2012年 2月 29日 (水) 16:22:57 JST
BitVisorメーリングリストの皆さん
筑波大学 ソフトウェア研究室の松下正吾です。
このパッチはBitVisorにRPC機能を追加します。別のパッチである動的プロセス生成機能
と組み合わせると、動的に読み込んだプロセスとゲストOS上のプログラムが通信するこ
とが可能となります。BitVisorの応用範囲が広がることが期待されます。
(1) APIの説明
今回追加したRPCの仕様は以下の通りとなります。
BitVisorのソースコードでは、include/rpc.hにあります。
enum rpc_direction {
RPC_DIRECTION_REQUEST,
RPC_DIRECTION_RESULT
};
struct rpc_request {
ulong id;
enum rpc_direction direction;
ulong proc_number;
ulong len;
char args[0];
};
struct rpc_result {
ulong id;
enum rpc_direction direction;
ulong status;
ulong len;
char results[0];
};
int vmmcall_rpc(int desc, struct rpc_request *request, struct
rpc_result *result)
descはプロセスのプログラムを識別する番号です。RPCの要求はrequestで与え、RPCの応
答を受けとるための領域をresultで指定します。struct rpc_requestにおける
proc_numberは呼び出す手続きの番号です。lenにはヘッダとデータを含んだ構造体の大
きさを与えます。可変長となっているargsとresultsには外部で定義した引数と戻り値を
与えます。これはAPI毎の構造体として定義されており、例えば足し算を行うRPCである
rpc_addの場合は下の図のようになります。
struct rpc_add_arg {
int a;
int b;
};
struct rpc_add_result {
int c;
};
struct rpc_resultにおけるstatusは、RPCのステータスコードです。vmmcall_rpcの戻り
値とstatusを分離することで、RPCのエラーと呼び出したAPIのエラーを区別することが
できます。RPCのエラーはvmmcall_rpcの戻り値で表し、APIのエラーはstatusで判別しま
す。よって、RPCの呼び出し側では両方の値をチェックしなければなりません。idは複数
のゲストOSのプログラムからRPCが実行されたとき、それぞれを区別する際に用います。
directionは方向を表し、通信の方向を区別する際に用います。
(2) APIの利用例(ゲストOS側クライアント)
ゲストOSで用いるvmmcallのスタブ関数を以下に示します。スタブ関数のインタフェース
はvmmcallの引数で示したものと同じです。ただし、vmmcallを呼ぶときに、引数を一つ
の構造体でまとめます。
struct rpc_arg {
int desc;
struct rpc_request *request;
struct rpc_result *result;
};
int vmmcall_rpc(int desc,
struct rpc_request *request,
struct rpc_result *result)
{
int r;
volatile struct rpc_arg arg;
/* Set rpc_arg. */
arg.desc = desc;
arg.request = request;
arg.result = result;
asm volatile ("vmcall" : "=a" (r) : "a" (vmmcall_rpc_num), "b" (&arg) :
"memory");
return r;
}
ゲストOS環境で動作するRPCのクライアント側のコードのvmmcall_rpcハイパバイザコー
ルを用いた例を次に示します。完全なソースコードは
guest/rpc-samples-add/add-client.cにあります。
--------------------------------------------------------------------------------------
static int add(int desc, int a /* in */, int b /* in */)
{
struct rpc_request *request;
struct rpc_result *result;
struct rpc_add_arg *arg;
struct rpc_add_result *res;
int r;
request = malloc(sizeof(struct rpc_request)+ sizeof(*arg));
request->proc_number = PROC_ADD;
request->len = sizeof(struct rpc_request)+ sizeof(*arg);
arg = &request->args[0];
arg->a = a;
arg->b = b;
result = malloc(sizeof(struct rpc_result)+ sizeof(*res));
result->status = 0;
result->len = sizeof(sizeof(struct rpc_result)+ sizeof(*res));
r = vmmcall_rpc(desc, request, result);
free(request);
if (r < 0) {
fprintf(stderr, "vmmcall rpc failed(%d).\n", r);
c = -1;
goto error;
}
if (result->status) {
fprintf(stderr, "vmmcall rpc procedure failed(%d).\n", r);
c = -1;
goto error;
}
res = &result->results[0];
c = res->c;
error:
free(result);
return c;
}
--------------------------------------------------------------------------------------
これはプロセスへのRPCを用いて足し算を行うプログラムです。引数descはRPCを行うプ
ロセスを指定する番号です。普通、動的プロセスロード機構の戻り値を保存しておいて
指定します。この関数はまず、vmmcall_rpcハイパバイザコールのための引数領域を確保
します。この例ではADDは2つの引数aとbです。引数の型はstruct rpc_add_argと
rpc_add_resultです。その後、引数の中身をセットします。vmmcall_rpcハイパバイザコ
ールのための戻り値の領域を確保します。vmmcall_rpcハイパバイザコールを呼び出し、
結果を関数の戻り値とします。RPCの戻り値はvmmcall_rpcの戻り値とresult->statusの
両方をチェックしなければなりません。
上記の完全なソースコードは、sample/guest/rpc-add-server/add-client.cにあります。
次のコマンドによりバイナリを生成することができます。
$ make
(3) APIの利用例(BitVisor側サーバ)
プロセスで動作するRPCのサーバ側の例を次に示します。
--------------------------------------------------------------------------------------
#include <rpc.h>
struct msgbuf {
void *base;
int len;
int rw;
};
int _start (int msgcode, int proc_number, struct msgbuf *buf, int bufcnt)
{
if (msgcode != MSG_BUF) {
return -1;
}
return svc(buf, bufcnt);
}
int svc(struct msgbuf *buf, int bufcnt)
{
struct rpc_request *request;
struct rpc_result *result;
int ret;
/* Args check. */
if (bufcnt < 2
|| buf[0].len < sizeof(*request) || buf[0].rw
|| buf[1].len < sizeof(*result) || !buf[1].rw) {
return -1;
}
request = (struct rpc_request *)buf[0].base;
result = (struct rpc_result *)buf[1].base;
switch (request->proc_number) {
case PROC_ADD:
ret = svc_add(request, result);
default:
ret = -1;
}
return ret;
}
int svc_add(struct rpc_request *request,
struct rpc_result *result) {
struct rpc_add_arg *arg;
struct rpc_add_result *res;
/* Args check. */
if (request->len < sizeof(*request)+sizeof(*arg)
|| result->len < sizeof(*result)+sizeof(*res)) {
result->status = -1;
return -1;
}
arg = (struct rpc_add_arg *)(request->args);
res = (struct rpc_add_result *)(result->results);
res->c = arg->a + arg->b;
result->len = sizeof(*result)+sizeof(*res);
return 0;
}
--------------------------------------------------------------------------------------
vmmcall_rpcハイパバイザコールが呼び出されると、BitVisorにより_start()関数が呼ば
れます。_start()関数の引数はどのような呼び出し方で呼び出されたかを表すmsgcode、
手続きの番号を表すproc_number、RPCの引数と戻り値が格納されるbuf、bufの数を表す
bufcntからなります。struct msgbufとはBitVisor内で標準的に使用される構造体です。
_start()関数ではまず呼び出し方法をチェックします。msgcodeがMSG_BUFの場合は、プ
ロセスのプログラムへのRPCを意味します。svc()関数はまず引数のチェックを行います
。引数のrwメンバをチェックすることで、引数のバッファは読み取り専用になっている
か、戻り値のバッファは書き込めるかをチェックします。不正な引数でなかった場合、
引数を取り出します。switch文ではproc_numberを見て処理を振り分けます。
proc_numberがPROC_ADDであった場合は、svc_add()関数を呼び出します。svc_add()関数
では手続き毎の引数チェックを行います。そして引数を取り出し、結果と戻り値の長さ
を戻り値のバッファに代入します。この変更した戻り値の領域は、RPCが終了するときに
vmmcall_rpcハイパバイザコール側がゲストOS側に変更を反映させます。
完全なソースコードはsamples/rpc/bin_add.cにあります。
次のmakeコマンドによりバイナリを生成、署名の生成、BitVisorへのロード
をすることができます。この動的ロードをするためには、別のパッチで実装されている
vmmcall_newprocess機能が必要です。
$ make
$ make sign
$ make load
-------------- next part --------------
テキスト形式以外の添付ファイルを保管しました...
ファイル名: bitvisor-rpc.patch
型: text/x-patch
サイズ: 16394 バイト
説明: 無し
URL: <http://www.bitvisor.org/archives/bitvisor-devel/attachments/20120229/69c8c7bf/attachment.bin>
BitVisor-devel メーリングリストの案内