QEMU虚拟网卡设备的创建流程

来源:互联网 发布:蚂蚁花呗提现淘宝店铺 编辑:程序博客网 时间:2024/05/22 23:50
基于qemu-kvm-0.12.1.2-2.160.el6_1.8.src.rpm
虚拟网卡类型为virtio-net-pci
virtio网卡设备对应的命令行参数为 
-device virtio-net-pci,netdev=hostnet0,id=net0,mac=00:16:36:01:c4:86,bus=pci.0,addr=0x3

1. 在parse命令行的时候,qemu把所有的-device选项parse后保存到qemu_device_opts中
2. 调用module_call_init(MODULE_INIT_DEVICE); 往系统中添加所有支持的设备类型
   virtio-net-pci的设备类型信息如下(virtio-pci.c):
static PCIDeviceInfo virtio_info[] = {
    {
        .qdev.name  = "virtio-net-pci",
        .qdev.size  = sizeof(VirtIOPCIProxy),
        .init       = virtio_net_init_pci,
        .exit       = virtio_net_exit_pci,
        .romfile    = "pxe-virtio.bin",
        .qdev.props = (Property[]) {
            DEFINE_PROP_BIT("ioeventfd", VirtIOPCIProxy, flags,
                            VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT, false),
            DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, 3),
            DEFINE_VIRTIO_NET_FEATURES(VirtIOPCIProxy, host_features),
            DEFINE_NIC_PROPERTIES(VirtIOPCIProxy, nic),
            DEFINE_PROP_UINT32("x-txtimer", VirtIOPCIProxy,
                               net.txtimer, TX_TIMER_INTERVAL),
            DEFINE_PROP_INT32("x-txburst", VirtIOPCIProxy,
                              net.txburst, TX_BURST),
            DEFINE_PROP_STRING("tx", VirtIOPCIProxy, net.tx),
            DEFINE_PROP_END_OF_LIST(),
        },
        .qdev.reset = virtio_pci_reset,
    }
   };

3. 调用qemu_opts_foreach(&qemu_device_opts, device_init_func, NULL, 1) 创建命令行上指定的设备
4. device_init_func调用qdev_device_add(opts)
5. qdev_device_add函数的流程如下:
   a) 调用qemu_opt_get(opts, "driver")获取driver选项,这里应该是virtio-net-pci
   b) 调用qdev_find_info(NULL, driver)来获取注册的DeviceInfo,这里应该是上面virtio_info里面关于
      virtio-net-pci的结构
   c) 调用qemu_opt_get(opts, "bus")获取bus路径,以/分隔各组件。这里是pci.0
   d) 如果bus路径不为空,则调用qbus_find(path)来获取bus实例(BusState结构)
      qbus_find函数的流程如下:
      d.1) 先找到路径中的根bus,如果路径以/开头,则根bus为main_system_bus,否则,使用
           qbus_find_recursive(main_system_bus, elem, NULL)来查找。这里的elem = "pci.0"
      d.2) 如果整个路径已经完成,则返回当前bus
      d.2) parse出下一个组件,调用qbus_find_dev查找对应的设备
      d.3) parse出下一个组件,调用qbus_find_bus查找属于上层设备的子bus
      d.4) 返回步骤2
      由于这里的值是pci.0,因此其实只进行了一次qbus_find_recursive调用
   e) 如果bus路径为空,则调用qbus_find_recursive(main_system_bus, NULL, info->bus_info)来获取bus
      实例。这里的info是driver("virtio-net-pci")所对应的DeviceInfo,即最上面的结构
      virtio-pci的初始化步骤是virtio_pci_register_devices -> pci_qdev_register_many -> 
      pci_qdev_register,在该函数中,会设置info->bus_info = &pci_bus_info,这样就把PCIDeviceInfo
      和pci的BusInfo联系起来了
      qbus_find_recursive是一个递归函数,其流程如下:
      e.1) 如果当前bus的名称和指定的名称相同(指定名称不为空的情况下),并且当前bus指向的bus info和
           指定的bus info相同(指定bus info不为空的情况下),则返回当前bus
      e.2) 这里是一个两重循环:
           对于当前bus所有附属的设备(bus->children为链表头)
               对于当前设备所有的附属bus(dev->child_bus为链表头)
                   调用qbus_find_recursive函数
   f) 调用qdev_create_from_info(bus, info)来创建设备,返回的是DeviceState结构。这里其实返回的是
      一个VirtIOPCIProxy实例,因为create的时候是根据qdev.size来分配内存大小的。
   g) 如果qemu_opts_id(opts)不为空,则设置qdev->id
   h) 调用qemu_opt_foreach(opts, set_property, qdev, 1)来设置设备的各种属性
   i) 调用qdev_init来初始化设备。
   j) qdev_init会调用dev->info->init函数。这里实际调用的函数是virtio_net_init_pci


在这里也大致描述一下bus pci.0是如何生成的
1. 在main函数里面很前面的地方会调用module_call_init(MODULE_INIT_MACHINE);
2. module_call_init会调用所有已注册QEMUMachine的init函数。该版本的qemu是注册了
   pc_machine_rhel610, pc_machine_rhel600, pc_machine_rhel550, pc_machine_rhel544,
   pc_machine_rhel540这几个 (pc.c)
3. 这些Machine的init函数(pc_init_rhel600, ...)都会调用到pc_init_pci函数
4. pc_init_pci回调用pc_init1,pc_init1在pci_enabled情况下会调用i440fx_init (piix_pci.c)
5. i440fx_init首先会调用qdev_create(NULL, "i440FX-pcihost")创建一个host device
6. 然后调用pci_bus_new在该设备下面创建一个附属的pci bus。在调用该函数时,传递的name为NULL。
   下面再看看这个bus的名称怎么会变成pci.0的
7. pci_bus_new调用pci_bus_new_inplace(bus, parent, name, devfn_min),其中bus指向刚分配的
   内存,parent是前面创建的host device,name为NULL,devfn_min为0
8. pci_bus_new_inplace会调用qbus_create_inplace(&bus->qbus, &pci_bus_info, parent, name),
   注意这里的第二个参数是&pci_bus_info
9. qbus_create_inplace在开始的地方会为该bus生成一个名称。因为传递进来的name为NULL,并且
   parent(那个host device)的id也为NULL,因此分支会跳到下面的代码
        
        len = strlen(info->name) + 16;
        buf = qemu_malloc(len);
        len = snprintf(buf, len, "%s.%d", info->name,
                       parent ? parent->num_child_bus : 0);
        for (i = 0; i < len; i++)
            buf[i] = qemu_tolower(buf[i]);
        bus->name = buf;
10. 在该段代码中,info就是之前pci_bus_new_inplace调用时传进来的&pci_bus_info,info->name是
    字符串"PCI"。并且,因为这是在host device上创建的第一个bus,因此parent->num_child_bus = 0,
    最后经过小写处理之后,该bus的名称就成为了"pci.0"
 
    这一段分析所对应的bus/device layout如下
    main-system-bus ---->  i440FX-pcihost ----> pci.0
0 0
原创粉丝点击