Skip to content

Instantly share code, notes, and snippets.

@RKX1209
Created November 5, 2017 06:26
Show Gist options
  • Select an option

  • Save RKX1209/f33191ea33ab095d2708e08d687bb8ca to your computer and use it in GitHub Desktop.

Select an option

Save RKX1209/f33191ea33ab095d2708e08d687bb8ca to your computer and use it in GitHub Desktop.
QEMU internals 5

QEMUのなかみ(QEMU internals) 5

QEMU Guest Agent

QEMUはゲストOS内にqemu-gaをインストールする事で、ホストからゲストの情報を取得できる。 qemu-gaはvirtio-serialを通して、ホストとUNIXソケットで双方向通信を行う。ゲスト側は/dev/virtio-ports/org.qemu.guest_agen.0などのデバイスファイルを通し、またホスト側は/tmp/qga.agentなどを通して通信する。(これらはqemu起動時にオプションで指定する)

|sh| ./qemu-ga -m virtio-serial -p /dev/virtio-ports/org.qemu.guest_agent.0 ||<

-mは通信経路(メソッド)で-pはパス。

ゲスト-ホスト間はQMP(json)でやりとりをする。

(qemu-ga.c)

|c| struct GAState { JSONMessageParser parser; GMainLoop *main_loop; GIOChannel *conn_channel; GIOChannel *listen_channel; const char *path; const char method; bool virtio; / fastpath to check for virtio to deal with poll() quirks */ GACommandState *command_state; GLogLevelFlags log_level; FILE *log_file; bool logging_enabled; };

static struct GAState *ga_state;

int main(int argc, char **argv) { ... s = g_malloc0(sizeof(GAState)); s->conn_channel = NULL; s->path = path; s->method = method; s->log_file = log_file; s->log_level = log_level; g_log_set_default_handler(ga_log, s); g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR); s->logging_enabled = true; s->command_state = ga_command_state_new(); ga_command_state_init(s, s->command_state); ga_command_state_init_all(s->command_state); ga_state = s;

module_call_init(MODULE_INIT_QAPI);
init_guest_agent(ga_state); [*]
register_signal_handlers();

g_main_loop_run(ga_state->main_loop);

ga_command_state_cleanup_all(ga_state->command_state);
unlink(pidfile);

return 0;

} ||<

[*]のinit_guest_agentでエージェントを初期化する。

(qemu-ga.c)

|c| static void init_guest_agent(GAState *s) { struct termios tio; int ret, fd;

if (strcmp(s->method, "virtio-serial") == 0) {
    s->virtio = true;
    fd = qemu_open(s->path, O_RDWR | O_NONBLOCK | O_ASYNC);
    ret = conn_channel_add(s, fd); [*]
} 
...
json_message_parser_init(&s->parser, process_event); [*]
s->main_loop = g_main_loop_new(NULL, false);

}

||< virtio-serialの場合、[*]でデバイスファイルを開いた後、glibを使ってIOチャンネルを作成する。

(qemu-ga.c)

|c| static gboolean conn_channel_read(GIOChannel *channel, GIOCondition condition, gpointer data) { GAState *s = data; gchar buf[1024]; gsize count; GError err = NULL; memset(buf, 0, 1024); GIOStatus status = g_io_channel_read_chars(channel, buf, 1024, &count, &err); [] switch (status) { case G_IO_STATUS_NORMAL: g_debug("read data, count: %d, data: %s", (int)count, buf); json_message_parser_feed(&s->parser, (char )buf, (int)count); [] ... } return true; }

static int conn_channel_add(GAState *s, int fd) { GIOChannel *conn_channel; GError *err = NULL;

g_assert(s && !s->conn_channel);
conn_channel = g_io_channel_unix_new(fd);
g_assert(conn_channel);
g_io_channel_set_encoding(conn_channel, NULL, &err);
g_io_add_watch(conn_channel, G_IO_IN | G_IO_HUP,
               conn_channel_read, s); [*]
s->conn_channel = conn_channel;
return 0;

} ||<

[]でゲストがデバイスファイルからreadした際のコールバックをconn_channel_readに設定する。 conn_channel_readでは[]で1024バイト読み、json_message_parser_feed(json-streamer.c)に投げる。この関数はJSONが文法的に正しいか確認するだけ。 また[*]でjson_message_parser_init(json-streamer.c)でJSONパーサーを初期化している。この時JSONパース時に呼ばれるコールバック関数をprocess_eventに設定している。

(qemu-ga.c)

|c| /* handle requests/control events coming in over the channel */ static void process_event(JSONMessageParser *parser, QList *tokens) { GAState *s = container_of(parser, GAState, parser); QObject *obj; QDict *qdict; Error *err = NULL; int ret;

g_assert(s && parser);

g_debug("process_event: called");
obj = json_parser_parse_err(tokens, NULL, &err);
if (err || !obj || qobject_type(obj) != QTYPE_QDICT) {
    qobject_decref(obj);
    qdict = qdict_new();
    if (!err) {
        g_warning("failed to parse event: unknown error");
        error_set(&err, QERR_JSON_PARSING);
    } else {
        g_warning("failed to parse event: %s", error_get_pretty(err));
    }
    qdict_put_obj(qdict, "error", error_get_qobject(err));
    error_free(err);
} else {
    qdict = qobject_to_qdict(obj);
}

g_assert(qdict);

/* handle host->guest commands */
if (qdict_haskey(qdict, "execute")) {
    process_command(s, qdict); [*]
} else {
    ret = conn_channel_send_payload(s->conn_channel, QOBJECT(qdict));
}

QDECREF(qdict);

} ||< [*]のprocess_commandで{"execute": "hoge"} のhogeを実行する。

(qemu-ga.c)

|c| static void process_command(GAState *s, QDict *req) { QObject *rsp = NULL; int ret;

g_assert(req);
g_debug("processing command");
rsp = qmp_dispatch(QOBJECT(req)); [*]
if (rsp) {
    ret = conn_channel_send_payload(s->conn_channel, rsp);
    if (ret) {
        g_warning("error sending payload: %s", strerror(ret));
    }
    qobject_decref(rsp);
} else {
    g_warning("error getting response");
}

} ||< qmp_dispatch(qapi/qmp-dispatch.c)で指定されたコマンドを実行する。

(qapi/qmp-dispatch.c)

|c| static QObject *do_qmp_dispatch(QObject *request, Error **errp) { const char *command; QDict *args, *dict; QmpCommand *cmd; QObject *ret = NULL;

dict = qmp_dispatch_check_obj(request, errp);
if (!dict || error_is_set(errp)) {
    return NULL;
}

command = qdict_get_str(dict, "execute");
cmd = qmp_find_command(command);
if (cmd == NULL) {
    error_set(errp, QERR_COMMAND_NOT_FOUND, command);
    return NULL;
}

if (!qdict_haskey(dict, "arguments")) {
    args = qdict_new();
} else {
    args = qdict_get_qdict(dict, "arguments");
    QINCREF(args);
}

switch (cmd->type) {
case QCT_NORMAL:
    cmd->fn(args, &ret, errp); [*]
    if (!error_is_set(errp) && ret == NULL) {
        ret = QOBJECT(qdict_new());
    }
    break;
}

QDECREF(args);

return ret;

}

QObject *qmp_dispatch(QObject *request) { ret = do_qmp_dispatch(request, &err);

return QOBJECT(rsp);

} ||< [*]で検索したQmpCommandのコールバック関数を呼ぶ。これがコマンドの本体。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment