设备模拟目的
我们好像不会干一件事而毫无目的,就算不停刷微信朋友圈也是为了打发你无聊的时间。
其实最装B的回答是:设备模拟的目的就是模拟设备。这话是屁话,不过也能说明些什么,确实是模拟设备,用软件的方式提供硬件设备具备的功能。
对于和PC机交互的硬件设备,主要要干两件事,一是提供IRQ中断,二是响应IO输入输出。IO包括PIO/MMIO/DMA等(DMA算不算IO?) 以i8254.c实现的pit为例,主要提供了IRQ注入和PIO响应,见初始化函数pit_initfn:
static const MemoryRegionOps pit_ioport_ops = { .read = pit_ioport_read, .write = pit_ioport_write, .impl =
{ .min_access_size = 1, .max_access_size = 1, }, .endianness = DEVICE_LITTLE_ENDIAN, }; static int pit_initfn(PITCommonState *pit) { PITChannelState *s; s = &pit->channels[0]; /* the timer 0 is connected to an IRQ */ //这里有个irq_timer,用于qemu_set_irq提供中断注入 s->irq_timer = qemu_new_timer_ns(vm_clock, pit_irq_timer, s); qdev_init_gpio_out(&pit->dev.qdev, &s->irq, 1); memory_region_init_io(&pit->ioports, &pit_ioport_ops, pit, \1); return 0; }
Static const MemoryRegionOps pit_ioport_ops={ .read=pit_ioport_read,
.write=pit_ioport_write, .impl={
.min_access_size=1, .max_access_size=1, },
.endianness=DEVICE_LITTLE_ENDIAN, };
Static int pit_initfn(PITCommonState * pit) {
PITChannelState*s;
s=&pit->channels[0];
/* the timer 0 is connected to an IRQ */
//这里有个irq_timer,用于qemu_set_irq提供中断注入
s->irq_timer=qemu_new_timer_ns(vm_clock,pit_irq_timer,s); qdev_init_gpio_out(&pit->dev.qdev,&s->irq,1);
memory_region_init_io(&pit->ioports,&pit_ioport_ops,pit,\,4);
qdev_init_gpio_in(&pit->dev.qdev,pit_irq_control,1); return0; }
这里的pit_ioport_ops,主要注册GUEST操作系统读写PIO时候的回调函数。
模块注册
QEMU要模拟模块那么多,以程序员的喜好,至少得来一套管理这些模拟设备模块的接口,以示设计良好。 QEMU将被模拟的模块分为了四类:
typedef enum { MODULE_INIT_BLOCK, MODULE_INIT_MACHINE, MODULE_INIT_QAPI, MODULE_INIT_QOM, MODULE_INIT_MAX } module_init_type;
typedefenum{ MODULE_INIT_BLOCK,
MODULE_INIT_MACHINE, MODULE_INIT_QAPI, MODULE_INIT_QOM, MODULE_INIT_MAX }module_init_type;
?
BLOCK
比如磁盘IO就属于BLOCK类型,e.g: block_init(bdrv_qcow2_init); block_init(iscsi_block_init);
?
MACHINE
PC虚拟machine_init(pc_machine_init); XEN半虚拟化machine_init(xenpv_machine_init); MIPS虚拟machine_init(mips_machine_init);
?
QAPI
QEMU GUEST AGENT模块里面会执行QAPI注册的回调
?
QOM
QOM树大枝多,儿孙满堂,应该是这里面最复杂的一个,我们重点介绍。 e.g:
ObjectClass -> PCIDeviceClass
//显卡type_init(cirrus_vga_register_types),
//网卡 type_init(rtl8139_register_types) IDEDeviceClass //IDE硬盘或CD-ROM type_init(ide_register_types) ISADeviceClass //鼠标键盘type_init(i8042_register_types),RTC时钟
type_init(pit_register) SysBusDeviceClass //MMIO IDE(IDE设备直接连接CPU bus而不是连接IDE
controller)type_init(mmio_ide_register_types) CPUClass -> X86CPUClass //X86 CPU架构 -> CRISCPUClass
ObjectClass-> PCIDeviceClass //显卡type_init(cirrus_vga_register_types),网卡type_init(rtl8139_register_types)
IDEDeviceClass //IDE硬盘或CD-ROM 1
type_init(ide_register_types) 2
ISADeviceClass //鼠标键盘3
type_init(i8042_register_types),RTC时钟type_init(pit_register) 4
SysBusDeviceClass//MMIO IDE(IDE设备5
直接连接CPU bus而不是连接IDE
controller)type_init(mmio_ide_register_types) CPUClass -> X86CPUClass //X86 CPU架构 -> CRISCPUClass
注册QOM设备的时候,使用QEMU提供的宏,type_init宏进行注册: #define type_init(function) module_init(function,
MODULE_INIT_QOM) #define module_init(function, type) \\ static void __attribute__((constructor)) do_qemu_init_ ##
function(void) { \\ register_module_init(function, type); \\ }
#define type_init(function) module_init(function, MODULE_INIT_QOM) 1
#define module_init(function, type) \\ 2
staticvoid__attribute__((constructor))do_qemu_init_## 3
function(void) { \\ 4
register_module_init(function,type);\\ 5 }
这和写LINUX驱动类似,一般写在一个模块实现文件的最底部,以pit为例,写的是type_init(pit_register_types)展开后为: static void __attribute__((constructor)) do_qemu_init_pit_register_types(void) {
register_module_init(pit_register_types, MODULE_INIT_QOM);
}
staticvoid__attribute__((constructor))do_qemu_init_pit_register_types(void) {
register_module_init(pit_register_types,MODULE_INIT_QOM); }
那么,这个do_qemu_init_pit_register_types何时调用? 在gcc里面,给函数加上__attribute__((destructor)),表示此函数需要在main开始前自动调用,测试调用顺序是: 全局对象构造函数 -> __attribute__((constructor)) -> main -> 全局对象析构函数 -> __attribute__((destructor))。
调用register_module_init就是将pit_register_types回调函数插入util\\module.c里定义的init_type_list[MODULE_INIT_QOM]链表内。 void register_module_init(void (*fn)(void), module_init_type type) { ModuleEntry *e; ModuleTypeList *l; e =
g_malloc0(sizeof(*e)); e->init = fn; //init指针被设置为fn l = find_type(type); QTAILQ_INSERT_TAIL(l, e, node); }
voidregister_module_init(void(*fn)(void),module_init_typetype) {
ModuleEntry*e; ModuleTypeList*l;
e=g_malloc0(sizeof(*e));
e->init=fn;//init指针被设置为fn l=find_type(type);
QTAILQ_INSERT_TAIL(l,e,node); }
通过下面main函数的部分代码可以看出,模块初始化顺序是QOM->MACHINE->BLOCK,至于QAPI,在这个流程里没看到。 void main() {