KVM+QEMU世界中的pci总线与virtio总线 module_call_init pc_machine_init

来源:互联网 发布:sql数据库可视化工具 编辑:程序博客网 时间:2024/05/16 19:12

在qemu中增加pci设备并用linux驱动验证

这篇文章的背景是通过9p文件系统在host os与guest os间共享一个目录,或许更专业点的叫法是File system Passthru. 因此需要配置guest os中Linux内核,使之支持下面的选项(.config)

CONFIG_NET_9P=y

CONFIG_NET_9P_VIRTIO=m

CONFIG_9P_FS=y

CONFIG_9P_FS_POSIX_ACL=y

但是本文不讨论如何使用9P文件系统来达成这一目标的具体步骤,而是讨论其幕后实现中的一个技术细节:pci总线与virtio总线的关系。

在上述的背景下,如果我们使用QEMU命令行来启动一个VM,在命令行中加入类似: -device virtio-9p-pci 这样的参数选项时, 那么在Guest OS中不但在/sys/bus/pci/devices中出现一个对应的pci设备,而且在/sys/bus/virtio/devices中也会出现一个virtio类型设备。


我们知道PCI总线是现代计算机体系结构中普遍存在的一个物理总线,而virtio总线则纯粹是软件定义的,在KVM+QEMU的世界里,两者之间到底是何关系?或者假设我们现在想在QEMU+KVM中添加一个新的virtio设备及其驱动,应该怎么做?

 

通常,Guest OS在初始化过程中会扫描pci bus num = 0的host bridge,也就是北桥,通过它来发现其下挂载的一系列pci设备(包括桥设备),所以我们想添加的的新的virtio设备必须对外展现出一个pci的接口。因为host bridge本身就是靠QEMU模拟出来的,所以在QEMU中模拟一个新的pci设备也是很简单的事情。如果QEMU成功模拟了一个挂在host bridge上的pci设备,那么Guest OS将会通过PCI总线扫描发现之,继而通过device_add函数把该设备添加到系统中,这将导致:
1. 在Guest OS中的/sys/bus/pci/devices目录下出现一个新的pci设备(被QEMU模拟出的)  
2. 在Guest OS中与该pci设备对应的驱动程序将会被加载(围绕着PCI总线的bind)。

 

到目前为止,都是PCI的作用范围,跟virtio总线没有什么关系。那么Guest OS中/sys/bus/virtio/devices下面出现的设备及其驱动又是如何产生的?

 

答案是pci设备驱动中定义的virtio_pci_probe().  在drivers/virtio/virtio_pci.c中:

 

/* Qumranet donated their vendor ID for devices 0x1000 thru 0x10FF. */

static DEFINE_PCI_DEVICE_TABLE(virtio_pci_id_table) = {

{ PCI_DEVICE(0x1af4, PCI_ANY_ID) },

{ 0 }

};

 

MODULE_DEVICE_TABLE(pci, virtio_pci_id_table);

 

static struct pci_driver virtio_pci_driver = {

.name                = "virtio-pci",

.id_table        = virtio_pci_id_table,

.probe                = virtio_pci_probe,

.remove        = virtio_pci_remove,

#ifdef CONFIG_PM_SLEEP

.driver.pm        =& virtio_pci_pm_ops,

#endif

};

 

virtio-pci的vendor ID是0x1af4, 这个是当初开发KVM的Qumranet公司PCI Vendor ID。所以,如果在QEMU侧模拟一个pci设备,其vendor ID=0x1af4,那么当Guest OS扫描到该设备时,将会将其加入到系统,于是使得上述的virtio_pci_driver被加载,该过程导致在/sys/bus/pci/devices中出现一个vendor id = 0x1af4的pci设备,同时该设备指向一个名为"virtio-pci"的驱动程序。这期间很重要的一个环节是"virtio-pci"驱动中的.probe成员,也即virtio_pci_probe().

 

在virtio_pci_probe()函数中,它将调用register_virtio_device(),后者将把一个virtio类型(struct  virtio_device)的设备加入到系统,由于该设备所属的总线是virtio bus(源码在drivers/virtio/virtio.c), 导致/sys/bus/virtio/devices/目录下出现一个新的设备:

int register_virtio_device(struct virtio_device *dev)

{

dev->dev.bus = &virtio_bus;

}

 

所以围绕virtio bus导致该设备的驱动被加载。(以上的过程很类似一个PCI接口的FC HBA卡的驱动模式:先PCI总线,后SCSI总线).这个virtio_device的设备驱动定义在net/9p/trans_virtio.c中。

因为OS启动过程中会扫描pci总线,所以必要使得一个设备先能被系统识别(基于pci),然后再做下一步的处理(基于scsi或者virtio...)。更形象地说,一个披着羊皮的狼才能被羊群所接纳,然后再脱掉羊皮变成狼,显现狼的行为




最近在看QEMU2.0源代码,决定把看的东西记录下来。一方面方便自己查阅,另一方面可以给看QEMU的同学参考。

QEMU中使用了很多构造函数,这些构造函数会在执行main()函数之前就执行,初始化一些数据结构。module_init()就是典型代表。


1.module_init调用关系图如下:

2. 函数分析  

(1) module_init函数定义(module.h)

#define module_init(function, type)                                         \static void __attribute__((constructor)) do_qemu_init_ ## function(void)    \{                                                                           \    register_dso_module_init(function, type);                               \}#else/* This should not be used directly.  Use block_init etc. instead.  */#define module_init(function, type)                                         \static void __attribute__((constructor)) do_qemu_init_ ## function(void)    \{                                                                           \    register_module_init(function, type);                                   \}#endif
__attribute__定义function为构造函数,又把module_init定义为function函数,参数如下:

function:就是要绑定的函数

type:就是这个函数要绑定的类型。type类型如下:

typedef enum {    MODULE_INIT_BLOCK,    MODULE_INIT_MACHINE,    MODULE_INIT_QAPI,    MODULE_INIT_QOM,    MODULE_INIT_MAX} module_init_type;

(2) register_module_init()函数(module.c)

void register_module_init(void (*fn)(void), module_init_type type){    ModuleEntry *e;      //一个节点    ModuleTypeList *l;  //ModuleEntry的链表头    e = g_malloc0(sizeof(*e));    e->init = fn;       //指定type类型的init函数为fn    e->type = type;<span style="white-space:pre"></span>//参数中传进来的type。也就是说type的init函数是fn    l = find_type(type);    QTAILQ_INSERT_TAIL(l, e, node); //把节点e插入到链表l中。QTAIL是一个双向链表,定义在queue.h中。(注意:每个type一个链表,而不是所有type在一个链表里面)}
 ModuleEntry定义如下:

typedef struct ModuleEntry{    void (*init)(void);    //type这个类型对应的init函数    QTAILQ_ENTRY(ModuleEntry) node;     module_init_type type;  //type类型} ModuleEntry;
其实个人理解为:ModuleEntry就是记录了某个type和这个type的init函数的绑定关系。


3.总结

综上所述,module_init(function, type) 的功能就是:

指定function为type类型的init函数,这种挂接关系会存储在这个type的QTAIL链表中。(上面也提到了,共5中type,每种type有各自的一个链表,而不是所有type都在一个链表中)

QTAIL链表的每一个节点node就记录了一种type和其init函数的挂接关系。

后面调用module_call_init(type) 时,就会查这个type的QTAIL链表,找到其对应的所有init函数,就开始执行所有与其挂接的init函数。


qemu中被定义为module_init()的函数有:

#define block_init(function) module_init(function, MODULE_INIT_BLOCK)#define machine_init(function) module_init(function, MODULE_INIT_MACHINE)#define qapi_init(function) module_init(function, MODULE_INIT_QAPI)#define type_init(function) module_init(function, MODULE_INIT_QOM)
上面block_init(),  machine_init(), qapi_init(), type_init()都被定义为module_init(),所以qemu代码中只要出现上面四个函数都是构造函数,都会在执行main函数之前执行。

例如:

在hw/i386/pc_piix.c中定义了:machine_init(pc_machine_init),  即是将MODULE_INIT_MACHIEN类型与pc_machine_init绑定,把这个绑定关系插入MODULE_INIT_MACHIEN的QTAIL链表。

在hw/i386/smbios.c中定义了:machine_init(smbios_register_config), 即是将MODULE_INIT_MACHIEN类型与smbios_register_config绑定,把这个绑定关系插入MODULE_INIT_MACHIEN的QTAIL链表。

所以pc_machine_init 和smbios_register_config都在MODULE_INIT_MACHIEN的QTAIL链表中。

所以当调用module_call_init(MODULE_INIT_MACHIEN)函数时,module_call_init函数内部会去查类型MODULE_INIT_MACHINE的QTAIL链表,执行所有与其挂接的init函数(链表中每个节点就记录了一个与其挂接的init函数),pc_machine_init,smbios_register_config都在链表中,都会被执行。



0 0
原创粉丝点击