QEMU源码字符设备
分析QEMU源码中的 virtconsole
字符设备文件,选择
QEMU v2.12.0
版本。
QEMU字符设备有host和guest部分,在当前版本中,使用命令参数
-chardev
来创建host部分,用命令参数 -device
创建guest设备。即: 1
2-chardev HOST-OPTS...,id=CHR-ID
-device DEVNAME,chardev=CHR-ID,DEV-OPTS...DEVNAME
依赖于机器类型,因此,对于 pc
类型, 1
* -serial becomes -device isa-serial,iobase=IOADDR,irq=IRQ,index=IDX
virtioconsole
的启动参数: 1
2-device virtio-serial-pci,class=C,vectors=V,ioeventfd=IOEVENTFD,max_ports=N
-device virtconsole,is_console=NUM,nr=NR,name=NAME
命名规则
- 变量是lower_case_with_underscores;
- 结构类型名称是CamelCase。
- 枚举类型名和函数类型名称也是CamelCase。
- 标量类型名称是lower_case_with_underscores_ending_with_a_t,就像POSIX的uint64_t。
- 以qemu_作前缀的函数是包装标准库函数。
结构体
串口端口结构体 + VirtIOSerialPort + DeviceState dev;
这是一个Object结构体 + QTAILQ_ENTRY(VirtIOSerialPort) next; +
VirtIOSerial *vser; 此变量可以得到 virtio device 和 qdev bus + VirtQueue
*ivq, *ovq;
一个输入virtqueue,用于从guest读数据,一个输出virtqueue,用于向guest写数据
+ char *name; 此名称将发送给guest虚拟机并通过sysfs导出。
guest可以根据此信息创建符号链接。
名称采用反向fqdn格式,如org.qemu.console.0 + uint32_t id;
此ID有助于识别guest和host之间的端口。客户端发送带有此ID的"tag",其中包含它发送的每个数据包,然后host可以找出将此数据发送到哪个关联设备
+ VirtQueueElement *elem; 这是我们从virtqueue中弹出的元素。
消耗guest数据的慢速后端(例如qemu
chardevs的文件后端)可能导致guest阻塞,直到所有输出被刷新。
这是我们不希望的,所以我们记下弹出的最后一个元素,并在后端再次写入后继续使用它。
+ uint32_t iov_idx; 弹出的elem在iov buffer的索引 + uint64_t iov_offset;
弹出的elem在iov buffer的偏移量 + QEMUBH *bh;
在非节流时,我们使用下半部分来调用flush_queued_data。 + bool
guest_connected; 相关的guest设备是否open + bool host_connected;
host上的设备是否为IO打开 + bool throttled; 应用是否向要接受数据
串口端口结构体类 + VirtIOSerialPortClass + DeviceClass parent_class; 这是个DeviceClass结构体 + bool is_console; 此设备是否和guest中的hvc绑定, hvc是啥? + DeviceRealize realize函数; 每个port新设备被bus发现后调用的回调函数 + DeviceUnrealize unrealize函数; 当一个port被热拔出或移除后调用的函数 + set_guest_connected函数; 对于guest event的回调函数,guest打开设备或关闭设备。 + enable_backend函数; 启用或关闭virtio serial port的backend; + guest_ready函数; Guest准备好接受数据 + have_data函数; guest向端口写入数据,数据在此函数中处理; + guest_writable; 每次guest将buffer入队列后,这依赖于guest端和host端已连接。
串口,包含了设备和BUS + struct VirtIOSerial + VirtIODevice parent_obj; + VirtQueue *c_ivq, *c_ovq; + VirtQueue **ivqs, **ovqs; + VirtIOSerialBus bus; + QTAILQ_HEAD(, VirtIOSerialPort) ports; + QLIST_ENTRY(VirtIOSerial) next; + uint32_t *ports_map; + struct VirtIOSerialPostLoad *post_load; + virtio_serial_conf serial; + uint64_t host_features; };
virtconsole
virtconsole属于字符设备chardev,分为backend和frontend,backend位于源码树
hw/char/virt-console.c
中。它对应的frontend前端驱动位于linux/drivers/char/virtio_console.c
。
virtserialport设备
最先注册的Type变量 virtserialport_info
,从名字可以看出这是串口端口类型。 它的Type名称是
TYPE_VIRTIO_CONSOLE_SERIAL_PORT
,即
virtserialport
。 父类型名称是
TYPE_VIRTIO_SERIAL_PORT
,类实例对象Object分配的大小取结构体 VirtConsole
的大小,设置了类对象ObjectClass的初始化函数。 1
2
3
4
5
6
7
8
9
10
11
12
13
14#define TYPE_VIRTIO_CONSOLE_SERIAL_PORT "virtserialport"
typedef struct VirtConsole {
VirtIOSerialPort parent_obj;
CharBackend chr;
guint watch;
} VirtConsole;
static const TypeInfo virtserialport_info = {
.name = TYPE_VIRTIO_CONSOLE_SERIAL_PORT,
.parent = TYPE_VIRTIO_SERIAL_PORT,
.instance_size = sizeof(VirtConsole),
.class_init = virtserialport_class_init,
};
virtserialport_info
在类对象初始化函数里面,
1
2
3
4
5
6
7
8
9
10
11
12
13static void virtserialport_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_CLASS(klass);
k->realize = virtconsole_realize;
k->unrealize = virtconsole_unrealize;
k->have_data = flush_buf;
k->set_guest_connected = set_guest_connected;
k->enable_backend = virtconsole_enable_backend;
k->guest_writable = guest_writable;
dc->props = virtserialport_properties;
}DEVICE_CLASS(klass)
将ObjectClass
转换成名称为 TYPE_DEVICE
的 Object类型,即
DeviceClass
。 1
#define DEVICE_CLASS(klass) OBJECT_CLASS_CHECK(DeviceClass, (klass), TYPE_DEVICE)
VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_CLASS(klass);
中
1 | #define VIRTIO_SERIAL_PORT_CLASS(klass) \ |
VIRTIO_SERIAL_PORT_CLASS
同样将ObjectClass转换成名称为
TYPE_VIRTIO_SERIAL_PORT
的 Object类型,即
VirtIOSerialPortClass
。 +
VirtIOSerialPortClass
的父类是 DeviceClass
。
+ realize
新设备在bus上被发现时的初始化函数; +
unrealize
设备在bus上被热拔货移除后的销毁函数; +
have_data
guest向端口写入数据,数据在此函数中处理; +
set_guest_connected
guest事件的回调函数,用于guest打开; +
enable_backend
启用或关闭virtio serial port的backend; +
guest_writable
每次guest将buffer入队列后 。
而设置DeviceClass的属性props仅仅设置了 chardev
参数,此参数是 struct CharBackend
,此结构体还没仔细研究。
在启动设备时候,命令行参数为:
-device virtserialport,CHR-ID,DEV-OPTS...
。
1
2
3
4static Property virtserialport_properties[] = {
DEFINE_PROP_CHR("chardev", VirtConsole, chr),
DEFINE_PROP_END_OF_LIST(),
};
virtio-serial bus的接口
1 | /* Interface to the virtio-serial bus */ |
VirtConsole
结构体的继承关系是
1 | \include\hw\virtio\virtio-serial.h |
`VirtConsole`
==> `VirtIOSerialPort`
==> `DeviceState`
==> `Ojbect`
virtconsole设备
说完了 virtserialport
端口,在来看看另一设备
virtconsole
。 设备名称是 virtconsole
,它的父类是 TYPE_VIRTIO_CONSOLE_SERIAL_PORT
。
设置了类对象ObjectClass的初始化函数。
1 | static const TypeInfo virtconsole_info = { |
顺腾摸瓜找下去, 1
2
3
4
5
6
7
8
9\hw\char\virtio-serial-bus.c
static const TypeInfo virtio_serial_port_type_info = {
.name = TYPE_VIRTIO_SERIAL_PORT,
.parent = TYPE_DEVICE,
...
};
\include\hw\qdev-core.h
#define TYPE_DEVICE "device"
"virtconsole"
==> TYPE_VIRTIO_CONSOLE_SERIAL_PORT("virtserialport")
==> TYPE_VIRTIO_SERIAL_PORT("virtio-serial-port")
==> TYPE_DEVICE("device")
virtconsole_info
类型的类对象初始化函数如下。
将类对象转换成类型为TYPE_VIRTIO_SERIAL_PORT 的 结构体
VirtIOSerialPortClass
,设置此设备为guest中的hvc。
1
2
3
4
5
6static void virtconsole_class_init(ObjectClass *klass, void *data)
{
VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_CLASS(klass);
k->is_console = true;
}virtserialport
和 virtconsole
模拟的串口,他们使用的设备为PCI设备 virtio-serial-pci
,用于连接virtio serial设备和console port的bus为
virtio-serial-bus
。
virtio-serial-bus
总线类型实例的注册有三个类型信息: virtser_bus_info
、
virtio_serial_port_type_info
、
virtio_device_info
,下面分别展开介绍。 1
2
3
4
5
6
7\hw\char\virtio-serial-bus.c
static void virtio_serial_register_types(void)
{
type_register_static(&virtser_bus_info);
type_register_static(&virtio_serial_port_type_info);
type_register_static(&virtio_device_info);
}
virtser_bus_info
1 | #define TYPE_VIRTIO_SERIAL_BUS "virtio-serial-bus" |
设备 "virtio-serial-bus"
并没有作更多有意思的事情,仅仅在类对象初始化函数中,设置了输出设备信息的函数指针。
对象的大小设置为 VirtIOSerialBus
结构体的大小。
VirtIOSerialBus
结构体 VirtIOSerialBus
是最顶端的
virtio-serial总线,将作为设备运行。 父类型是 BusState
,而
BusState
结构体的父类型也是 Object
。
1 | /* The virtio-serial bus on top of which the ports will ride as devices */ |
virtio_serial_port_type_info
1 | static const TypeInfo virtio_serial_port_type_info = { |
- instance_size: Object的大小为结构体
VirtIOSerialPort
的大小。 - abstract: 此TypeInfo结构体不能被直接实例化
- class_size : ObjectClass的大小为结构体
VirtIOSerialPortClass
的大小。 - class_init : 在父类初始化完成后,执行virtio_serial_port_class_init。
1 | static void virtio_serial_port_class_init(ObjectClass *klass, void *data) |
在类初始化函数中,设置了DeviceClass中总线类型bus_type为前文提到的另一设备
TYPE_VIRTIO_SERIAL_BUS
。设置了静态属性,并实现了虚函数
realize 与 unrealize。
virtio_device_info
virtio_device_info
实现的是virtio串口设备。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15static const TypeInfo virtio_device_info = {
.name = TYPE_VIRTIO_SERIAL,
.parent = TYPE_VIRTIO_DEVICE,
.instance_size = sizeof(VirtIOSerial),
.class_init = virtio_serial_class_init,
.interfaces = (InterfaceInfo[]) {
{ TYPE_HOTPLUG_HANDLER },
{ }
}
};
\include\hw\virtio\virtio-serial.h
#define TYPE_VIRTIO_SERIAL "virtio-serial-device"
#define VIRTIO_SERIAL(obj) \
OBJECT_CHECK(VirtIOSerial, (obj), TYPE_VIRTIO_SERIAL)
virtio-serial-device
的父类是virtio 设备,该对象是
VirtIOSerial
。 在类对象初始化中,实现了类
DeviceClass
、 VirtioDeviceClass
、
HotplugHandlerClass
中的属性和虚函数。
1 | static void virtio_serial_class_init(ObjectClass *klass, void *data) |
此设备貌似在当前文件中与总线串口端口没有任何关系,它在virtio_serial_pci中得到引用。
1 | hw\virtio\virtio-pci.c |
virtio-serial-pci
"virtconsole"
==> TYPE_VIRTIO_CONSOLE_SERIAL_PORT("virtserialport")
==> TYPE_VIRTIO_SERIAL_PORT("virtio-serial-port")
==> TYPE_DEVICE("device")
=> virtio_serial_port_class_init => virtserialport_class_init => virtconsole_class_init
=> virtser_bus_class_init => virtio_serial_class_init => virtio_serial_pci_class_init 启动 => virtio_serial_pci_instance_init => virtio_serial_pci_realize => virtio_serial_device_realize => virtser_port_device_realize => virtconsole_realize