8.1.1 qemu內部命令架構
(1) 管理模塊的char device
qemu採用monitor來管理內部命令, 當用戶在qemu虛擬機界面上輸入Ctrl+ALT+2將進入qemu monitor; 然後按CTRL+ALT+1將恢復當正常的虛擬機窗口。 monitor的初始化代碼如下:
main(vl.c) ==>
if (qemu_opts_foreach(qemu_find_opts("mon"),mon_init_func, NULL, 1) != 0)
exit(1);
static intmon_init_func(QemuOpts *opts, void *opaque) {
......
chr = qemu_chr_find(chardev); //chr用來控制用戶輸入輸出
monitor_init(chr, flags);
return 0;
}
monitor 需要與char device綁定,用於管理命令的輸入與信息輸出。
目前qemu 的在vl.c中會用到的chardev示例如下:
if (foreach_device_config(DEV_SERIAL,serial_parse) < 0)
exit(1);
if (foreach_device_config(DEV_PARALLEL,parallel_parse) < 0)
exit(1);
if (foreach_device_config(DEV_VIRTCON,virtcon_parse) < 0)
exit(1);
if (foreach_device_config(DEV_DEBUGCON,debugcon_parse) < 0)
exit(1);
virtcon_parse ==> qemu_chr_new(label, devname, NULL);
CharDriverState*qemu_chr_new(const char *label, const char *filename, void (*init)(structCharDriverState *s))
{
......
if (strstart(filename,"chardev:", &p)) { //若filename對應的chardev已存在
return qemu_chr_find(p);
}
//根據filename設置opt屬性
opts = qemu_chr_parse_compat(label, filename);
if (!opts)
return NULL;
chr = qemu_chr_new_from_opts(opts, init);
if (chr && qemu_opt_get_bool(opts,"mux", 0)) {
monitor_init(chr,MONITOR_USE_READLINE);
}
qemu_opts_del(opts);
return chr;
}
qemu_chr_new_from_opts ==》 for(i = 0; i < ARRAY_SIZE(backend_table); i++) {
if (strcmp(backend_table[i].name,qemu_opt_get(opts, "backend")) == 0)
break;
}
chr = backend_table[i].open(opts);
backend_table定義在qemu-char.c中:
static const struct {
const char *name;
CharDriverState *(*open)(QemuOpts *opts);
} backend_table[] = {
{ .name = "null", .open = qemu_chr_open_null },
{ .name = "socket", .open = qemu_chr_open_socket },
{ .name = "udp", .open = qemu_chr_open_udp },
{ .name = "msmouse", .open = qemu_chr_open_msmouse },
{ .name = "vc", .open = text_console_init },
#ifdef _WIN32
。。。。。。。
#else
{ .name = "file", .open = qemu_chr_open_file_out },
{ .name = "pipe", .open = qemu_chr_open_pipe },
{ .name = "pty", .open = qemu_chr_open_pty },
{ .name = "stdio", .open = qemu_chr_open_stdio },
#endif
。。。。。。。。。
{ .name ="tty", .open =qemu_chr_open_tty },
}
chr =backend_table[i].open(opts);回建立具體的chardevice
chrdev創建後monitor_init會
voidmonitor_init(CharDriverState *chr, int flags)
{
if (is_first_init) {
monitor_protocol_event_init();
is_first_init = 0;
}
mon = g_malloc0(sizeof(*mon));
mon->chr = chr;
mon->flags = flags;
if (flags & MONITOR_USE_READLINE) {
mon->rs = readline_init(mon,monitor_find_completion);
monitor_read_command(mon, 0);
}
if (monitor_ctrl_mode(mon)) { //當啓動參數設置ctrl後,會採用qmp支持的monitor
mon->mc = g_malloc0(sizeof(MonitorControl));
/* Control mode requires specialhandlers */
qemu_chr_add_handlers(chr,monitor_can_read, monitor_control_read,
monitor_control_event, mon);
qemu_chr_fe_set_echo(chr, true);
json_message_parser_init(&mon->mc->parser,handle_qmp_command);
} else {
qemu_chr_add_handlers(chr,monitor_can_read, monitor_read,
monitor_event,mon);
}
QLIST_INSERT_HEAD(&mon_list, mon,entry);
if(!default_mon || (flags & MONITOR_IS_DEFAULT))
default_mon = mon;
sortcmdlist();
}
最簡單的stdio char 爲例:
qemu_chr_open_stdio ==》 qemu_set_fd_handler2(0, stdio_read_poll,stdio_read, NULL, chr);
stdio_read ==》 qemu_chr_be_write ==》s->chr_read
對於monitor 會調用monitor_read
static void monitor_read(void*opaque, const uint8_t *buf, int size)
{
。。。。。
if (cur_mon->rs) {
for (i = 0; i < size; i++)
readline_handle_byte(cur_mon->rs, buf[i]); //將數據讀到buffer
} else {
if (size == 0 || buf[size - 1] != 0)
monitor_printf(cur_mon,"corrupted command\n");
else
handle_user_command(cur_mon, (char*)buf); //收完數據則調用要執行的命令
}
cur_mon = old_mon;
}
(2) 管理模塊架構
handle_user_command(monitor.c)
cmd = monitor_parse_command(mon, cmdline,qdict); //根據名稱與arg查找命令的實現結構
if (handler_is_async(cmd)) {
user_async_cmd_handler(mon, cmd,qdict);
} else if (handler_is_qobject(cmd)) {
QObject *data = NULL;
cmd->mhandler.cmd_new(mon, qdict,&data);
if (data) {
cmd->user_print(mon, data);
qobject_decref(data);
}
} else {
cmd->mhandler.cmd(mon, qdict);
.......
monitor_find_command ==> search_dispatch_table(mon_cmds,cmdname);
static mon_cmd_t mon_cmds[] ={
#include"hmp-commands.h"
{ NULL, NULL, },
};
hmp-commands.h 在編譯時由hmp-commands.hx 模板生成
下面是其中的一個代碼片段:
{
.name = "q|quit",
.args_type = "",
.params = "",
.help = "quit the emulator",
.user_print = monitor_user_noop,
.mhandler.cmd= hmp_quit,
},
mhandler.cmd爲命令的實現函數
/root/qemu-kvm/scripts/hxtool -h < /root/qemu-kvm/hmp-commands.hx > hmp-commands.h
8.1.2 qmp命令
qmp的全稱爲qemu machine protocol,其作用是讓外部的程序來控制qemu. 如libvirt用qmp來向qemu發送管理消息。qmp採用了json數據交換格式。JSON採用完全獨立於語言的文本格式,但是也使用了類似於C語言家族的習慣
(1) json
JSON建構於兩種結構:
“名稱/值”對的集合(A collection of name/value pairs)。不同的語言中,它被理解爲對象(object),紀錄(record),結構(struct),字典(dictionary),哈希表(hash table),有鍵列表(keyed list),或者關聯數組 (associative array)。
值的有序列表(An ordered list ofvalues)。在大部分語言中,它被理解爲數組(array)。
這些都是常見的數據結構。事實上大部分現代計算機語言都以某種形式支持它們。這使得一種數據格式在同樣基於這些結構的編程語言之間交換成爲可能。
JSON具有以下這些形式:
a. 對象是一個無序的“‘名稱/值’對”集合。一個對象以“{”(左括號)開始,“}”(右括號)結束。每個“名稱”後跟一個“:”(冒號);“‘名稱/值’ 對”之間使用“,”(逗號)分隔。
b. 數組是值(value)的有序集合。一個數組以“[”(左中括號)開始,“]”(右中括號)結束。值之間使用“,”(逗號)分隔。
c. 值(value)可以是雙引號括起來的字符串(string)、數值(number)、true、false、 null、對象(object)或者數組(array)。這些結構可以嵌套。
d.字符串(string)是由雙引號包圍的任意數量Unicode字符的集合,使用反斜線轉義。一個字符(character)即一個單獨的字符串(character string)。
e.字符串(string)與C或者Java的字符串非常相似。
f.數值(number)也與C或者Java的數值非常相似。除去未曾使用的八進制與十六進制格式。除去一些編碼細節。
g.空白可以加入到任何符號之間。
qemu中解析代碼爲:json-lexer.c; json-parser.c;json-streamer.c. 本文不分析這些代碼
(2)QMP協議構成
最基本的使用是通過命令行參數,例子:
1. 啓動虛擬機
# qemu [...] -qmptcp:localhost:4444,server,nowait
2. 運行 telnet
$ telnet localhost 4444
運行後會打印如下信息:
{"QMP":{"version": {"qemu": {"micro": 0,"minor": 6, "major": 1}, "package":""}, "capabilities": []}}
3. 發送 qmp_capabilities 命令e
{ "execute":"qmp_capabilities" }
當qmp 連接建立後,qmp 會發送問候 message,並且進入negotiation模式, 這時只支持qmp_capabliities命令;當收到qmp_capabilities命令後,會進入能處理一般命令的模式
4. 可以開始發送其他命令,如:
{ "execute":"query-commands" }
另一種應用場景是qemu-ga, qemu-qga是一個運行在虛擬機內部的普通應用程序(可執行文件名稱默認爲qemu-ga,服務名稱默認爲qemu-guest-agent),其目的是實現一種宿主機和虛擬機進行交互的方式,這種方式不依賴於網絡,而是依賴於virtio-serial(默認首選方式)或者isa-serial,而QEMU則提供了串口設備的模擬及數據交換的通道,最終呈現出來的是一個串口設備(虛擬機內部)和一個unix socket文件(宿主機上)。
qga通過讀寫串口設備與宿主機上的socket通道進行交互,宿主機上可以使用普通的unix socket讀寫方式對socket文件進行讀寫,最終實現與qga的交互,交互的協議與qmp(QEMU Monitor Protocol)相同(簡單來說就是使用JSON格式進行數據交換),串口設備的速率通常都較低,所以比較適合小數據量的交換. 其源碼爲qemu-ga.c.
下面試qmp命令的數據示例:
S: {
"QMP": {
"version": {
"qemu": {
"micro": 0,
"minor": 6,
"major": 1
},
"package":""
},
"capabilities": [
]
}
}
C: { "execute": "qmp_capabilities" }
S: { "return": {}}
The {"return": {} } response is QMP's success response. An errorresponse will contain the "error" keyword instead of"return".
C: { "execute": "eject", "arguments": {"device": "ide1-cd0" } }
S: { "return": {}}
C: { "execute": "query-status" }
S: {
"return": {
"status":"running",
"singlestep":false,
"running": true
}
}
S: { "event": "BLOCK_IO_ERROR",
"data": {"device": "ide0-hd1",
"operation": "write",
"action":"stop" },
"timestamp": {"seconds": 1265044230, "microseconds": 450486 } }
(3) QMP命令實例分析
與hmp命令類似, qmp的模板頭文件爲qmp-commands-old.h:
static const mon_cmd_tqmp_cmds[] = {
#include"qmp-commands-old.h"
{ /* NULL */ },
};
typedef struct mon_cmd_t {
const char *name;
const char *args_type;
const char *params;
const char *help;
void (*user_print)(Monitor *mon, constQObject *data);
union {
void (*info)(Monitor *mon);
void (*cmd)(Monitor *mon, const QDict*qdict);
int (*cmd_new)(Monitor *mon, const QDict *params, QObject **ret_data);
int (*cmd_async)(Monitor *mon, const QDict *params,
MonitorCompletion*cb, void *opaque);
} mhandler;
int flags;
} mon_cmd_t;
例:int do_drive_del(Monitor*mon, const QDict *qdict, QObject **ret_data);
QDict時qemu爲參數封裝的數據類型, 其訪問api定義在:
qdict.h; 實現代碼爲:qdict.c,qinit.c,qbool.c,qlist.c,qfloat.c,qstring.c
8.1.3 設備的添加與熱插拔
本節分析qemu如何實現設備的動態添加:
{
.name = "device_add",
.args_type = "device:O",
.params = "driver[,prop=value][,...]",
.help = "add device, like -device on thecommand line",
.user_print =monitor_user_noop,
.mhandler.cmd_new =do_device_add,
},
do_device_add ==》 qdev_device_add(qdev-monitor.c)
a) 得到要建立設備的driver和類別信息:
driver = qemu_opt_get(opts,"driver");
obj = object_class_by_name(driver);
k = DEVICE_CLASS(obj);
b) 得到要建立設備的bus信息
path = qemu_opt_get(opts, "bus");
if (path != NULL)
bus = qbus_find(path);
else
bus =qbus_find_recursive(sysbus_get_default(), NULL, k->bus_type);
if (!bus)
bus = sysbus_get_default();
c) 創建設備
qdev = DEVICE(object_new(driver));
qdev_set_parent_bus(qdev, bus);
d) 爲設備添加id屬性: object_property_add_child
e) 初始化設備:qdev_init(qdev);
對於pci設備還支持動態添加:
pci_device_hot_add(pci_hotplug.c):
const char *pci_addr = qdict_get_str(qdict,"pci_addr");
const char *type = qdict_get_str(qdict,"type");
const char *opts = qdict_get_try_str(qdict,"opts");
if(strcmp(type, "nic") == 0)
dev = qemu_pci_hot_add_nic(mon,pci_addr, opts);
} else if (strcmp(type,"storage") == 0)
dev = qemu_pci_hot_add_storage(mon,pci_addr, opts);
cpu的動態添加:
do_cpu_set_nr:
status = qdict_get_str(qdict,"state");
value = qdict_get_int(qdict,"cpu");
//a.調用pc_new_cpu(global_cpu_model);啓動一個vcpu線程
//b. pm_update_sci更新虛擬機acpi信息
qemu_system_cpu_hot_add(value, state);