zoukankan      html  css  js  c++  java
  • qemu网络虚拟化之数据流向分析二

    2016-09-27 

    上篇文章大致介绍了qemu网络虚拟化相关的数据结构,本篇就结合qemu-kvm源代码分析下各个数据结构是如何初始化以及建立联系的。

    这里还是分为三个部分:

    1、Tap设备区

    2、Hub区

    3、NIC区


    1、Tap设备区

    在net.c中有数组记录下net client 初始化的相关函数

     1 static int (* const net_client_init_fun[NET_CLIENT_OPTIONS_KIND_MAX])(
     2     const NetClientOptions *opts,
     3     const char *name,
     4     NetClientState *peer) = {
     5         [NET_CLIENT_OPTIONS_KIND_NIC]       = net_init_nic,
     6 #ifdef CONFIG_SLIRP
     7         [NET_CLIENT_OPTIONS_KIND_USER]      = net_init_slirp,
     8 #endif
     9         [NET_CLIENT_OPTIONS_KIND_TAP]       = net_init_tap,
    10         [NET_CLIENT_OPTIONS_KIND_SOCKET]    = net_init_socket,
    11 #ifdef CONFIG_VDE
    12         [NET_CLIENT_OPTIONS_KIND_VDE]       = net_init_vde,
    13 #endif
    14         [NET_CLIENT_OPTIONS_KIND_DUMP]      = net_init_dump,
    15 #ifdef CONFIG_NET_BRIDGE
    16         [NET_CLIENT_OPTIONS_KIND_BRIDGE]    = net_init_bridge,
    17 #endif
    18         [NET_CLIENT_OPTIONS_KIND_HUBPORT]   = net_init_hubport,
    19 };

    这里我们就从net_init_tap开始,位于tap.c中这里暂且忽略下其他无关的代码

    在该函数中关键是调用了net_init_tap_one,因为qemu中的vlan不支持多TAP。

     1 static int net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer,
     2                             const char *model, const char *name,
     3                             const char *ifname, const char *script,
     4                             const char *downscript, const char *vhostfdname,
     5                             int vnet_hdr, int fd)
     6 {
     7     TAPState *s;
     8 
     9     s = net_tap_fd_init(peer, model, name, fd, vnet_hdr);
    10     if (!s) {
    11         close(fd);
    12         return -1;
    13     }
    14 
    15     if (tap_set_sndbuf(s->fd, tap) < 0) {
    16         return -1;
    17     }
    18 
    19     if (tap->has_fd || tap->has_fds) {
    20         snprintf(s->nc.info_str, sizeof(s->nc.info_str), "fd=%d", fd);
    21     } else if (tap->has_helper) {
    22         snprintf(s->nc.info_str, sizeof(s->nc.info_str), "helper=%s",
    23                  tap->helper);
    24     } else {
    25         snprintf(s->nc.info_str, sizeof(s->nc.info_str),
    26                  "ifname=%s,script=%s,downscript=%s", ifname, script,
    27                  downscript);
    28 
    29         if (strcmp(downscript, "no") != 0) {
    30             snprintf(s->down_script, sizeof(s->down_script), "%s", downscript);
    31             snprintf(s->down_script_arg, sizeof(s->down_script_arg),
    32                      "%s", ifname);
    33         }
    34     }
    35 
    36     if (tap->has_vhost ? tap->vhost :
    37         vhostfdname || (tap->has_vhostforce && tap->vhostforce)) {
    38         int vhostfd;
    39 
    40         if (tap->has_vhostfd || tap->has_vhostfds) {
    41             vhostfd = monitor_handle_fd_param(cur_mon, vhostfdname);
    42             if (vhostfd == -1) {
    43                 return -1;
    44             }
    45         } else {
    46             vhostfd = -1;
    47         }
    48 
    49         s->vhost_net = vhost_net_init(&s->nc, vhostfd,
    50                                       tap->has_vhostforce && tap->vhostforce);
    51         if (!s->vhost_net) {
    52             error_report("vhost-net requested but could not be initialized");
    53             return -1;
    54         }
    55     } else if (tap->has_vhostfd || tap->has_vhostfds) {
    56         error_report("vhostfd= is not valid without vhost");
    57         return -1;
    58     }
    59 
    60     return 0;
    61 }

    这里首先就创建了一个NetClientState结构,前文分析过其实作为逻辑连接点,这里称之为net client.

     1 NetClientState *qemu_new_net_client(NetClientInfo *info,
     2                                     NetClientState *peer,
     3                                     const char *model,
     4                                     const char *name)
     5 {
     6     NetClientState *nc;
     7 
     8     assert(info->size >= sizeof(NetClientState));
     9 
    10     nc = g_malloc0(info->size);//这里申请的空间是info->size,回想在网卡端申请的是info->size+num*sizeof(NetClientState)
    11     //在增加端口的时候peer还是null
    12     qemu_net_client_setup(nc, info, peer, model, name,
    13                           qemu_net_client_destructor);
    14 
    15     return nc;
    16 }

    该函数中申请空间后就调用了qemu_net_client_setup函数设置net client。还有一点需要注意,这里申请的空间是info->size,可以看下

    1 static NetClientInfo net_tap_info = {
    2     .type = NET_CLIENT_OPTIONS_KIND_TAP,
    3     .size = sizeof(TAPState),
    4     .receive = tap_receive,
    5     .receive_raw = tap_receive_raw,
    6     .receive_iov = tap_receive_iov,
    7     .poll = tap_poll,
    8     .cleanup = tap_cleanup,
    9 };

    可见这里的大小是TAPState的大小。这也就解释了上面的函数中DO_UPCAST(TAPState, nc, nc);下面看qemu_net_client_setup函数

     1 static void qemu_net_client_setup(NetClientState *nc,
     2                                   NetClientInfo *info,
     3                                   NetClientState *peer,
     4                                   const char *model,
     5                                   const char *name,
     6                                   NetClientDestructor *destructor)
     7 {
     8     nc->info = info;//建立NetClientState到port的连接
     9     nc->model = g_strdup(model);
    10     if (name) {
    11         nc->name = g_strdup(name);
    12     } else {
    13         nc->name = assign_name(nc, model);
    14     }
    15 
    16     if (peer) {//相互指向
    17         assert(!peer->peer);
    18         nc->peer = peer;
    19         peer->peer = nc;
    20     }
    21     QTAILQ_INSERT_TAIL(&net_clients, nc, next);//加入全局的net_clients链表中
    22 
    23     nc->incoming_queue = qemu_new_net_queue(nc);//设置接收队列
    24     nc->destructor = destructor;
    25 }

    该函数设置net client,完成最主要的功能就是TAPState和Hub进行关联。函数体并不难理解,设置了下info,model,name,peer等字段,peer用于指向传递进来的peer指针,通知也设置对端的peer指向。然后把NetClientState结构加入到全局的net_clients链表中(从尾部加入),之后再设置接收队列incoming_queue和析构函数。


    2、HUb端

     和前面类似,这里也从net_init_hubport开始,位于hub.c中

     1 int net_init_hubport(const NetClientOptions *opts, const char *name,
     2                      NetClientState *peer)
     3 {
     4     const NetdevHubPortOptions *hubport;
     5 
     6     assert(opts->kind == NET_CLIENT_OPTIONS_KIND_HUBPORT);
     7     hubport = opts->hubport;
     8 
     9     /* Treat hub port like a backend, NIC must be the one to peer */
    10     if (peer) {
    11         return -EINVAL;
    12     }
    13 
    14     net_hub_add_port(hubport->hubid, name);
    15     return 0;
    16 }

     函数体很简单,做了简单的验证后就调用net_hub_add_port函数给指定的Hub增加一个port,有两个参数,分别为hubid和name,前面提到过qemu中用Hub来实现vlan,这里实际上也可以理解为vlan id.

    看net_hub_add_port函数

     1 NetClientState *net_hub_add_port(int hub_id, const char *name)
     2 {
     3     NetHub *hub;
     4     NetHubPort *port;
     5 
     6     QLIST_FOREACH(hub, &hubs, next) {
     7         if (hub->id == hub_id) {
     8             break;
     9         }
    10     }
    11     if (!hub) {
    12         hub = net_hub_new(hub_id);
    13     }
    14     port = net_hub_port_new(hub, name);
    15     return &port->nc;
    16 }

    这里首先要从全局的Hub链表hubs遍历到指定的Hub,如果没有则创建一个新的,然后调用net_hub_port_new函数创建port,返回port->nc

     1 static NetHubPort *net_hub_port_new(NetHub *hub, const char *name)
     2 {
     3     NetClientState *nc;
     4     NetHubPort *port;
     5     int id = hub->num_ports++;
     6     char default_name[128];
     7 
     8     if (!name) {
     9         snprintf(default_name, sizeof(default_name),
    10                  "hub%dport%d", hub->id, id);
    11         name = default_name;
    12     }
    13 
    14     nc = qemu_new_net_client(&net_hub_port_info, NULL, "hub", name);
    15     port = DO_UPCAST(NetHubPort, nc, nc);
    16     port->id = id;
    17     port->hub = hub;
    18 
    19     QLIST_INSERT_HEAD(&hub->ports, port, next);
    20 
    21     return port;
    22 }

     这里会通过qemu_new_net_client函数创建一个net client即NetClientState,和上面一样,这里申请的空间是sizeof(NetHubPort),然后转换指针到NetHubPort,做一些其他的设置,如指定port id和所属的hub,然后加入port到Hub下属的port链表(从头插入)。


     3、NIC端

    这里我们还是先从net_init_nic函数说起。但是该函数  具体的作用我还真没有分析到。结合具体的网卡没有发现调用此函数的,相比之下该函数好像有点独立了!后面有机会在看吧,先结合e1000网卡的初始化过程进行分析。

    下面从e1000网卡初始化开始,主要的文件在e1000.c文件中

     1 static void e1000_class_init(ObjectClass *klass, void *data)
     2 {
     3     DeviceClass *dc = DEVICE_CLASS(klass);
     4     PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
     5 
     6     k->init = pci_e1000_init;
     7     k->exit = pci_e1000_uninit;
     8     k->romfile = "efi-e1000.rom";
     9     k->vendor_id = PCI_VENDOR_ID_INTEL;
    10     k->device_id = E1000_DEVID;
    11     k->revision = 0x03;
    12     k->class_id = PCI_CLASS_NETWORK_ETHERNET;
    13     set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
    14     dc->desc = "Intel Gigabit Ethernet";
    15     dc->reset = qdev_e1000_reset;
    16     dc->vmsd = &vmstate_e1000;
    17     dc->props = e1000_properties;
    18 }

    其中最主要的函数pci_e1000_init函数和e1000_properties,其他的暂且忽略。前者是e1000网卡的初始化函数,后者是从命令行接收到的参数赋值到相关的网卡属性。先看后者吧

    1 static Property e1000_properties[] = {
    2     DEFINE_NIC_PROPERTIES(E1000State, conf),
    3     DEFINE_PROP_BIT("autonegotiation", E1000State,
    4                     compat_flags, E1000_FLAG_AUTONEG_BIT, true),
    5     DEFINE_PROP_BIT("mitigation", E1000State,
    6                     compat_flags, E1000_FLAG_MIT_BIT, true),
    7     DEFINE_PROP_END_OF_LIST(),
    8 };

    宏定义DEFINE_NIC_PROPERTIES把接收到的相关信息赋值到E1000State结构中的conf字段

    1 #define DEFINE_NIC_PROPERTIES(_state, _conf)                            
    2     DEFINE_PROP_MACADDR("mac",   _state, _conf.macaddr),                
    3     DEFINE_PROP_VLAN("vlan",     _state, _conf.peers),                   
    4     DEFINE_PROP_NETDEV("netdev", _state, _conf.peers),                   
    5     DEFINE_PROP_INT32("bootindex", _state, _conf.bootindex, -1)

    然后查看pci_e1000_init函数,首先根据pci_dev获取了E1000State结构

    然后调用e1000_mmio_setup函数设置网卡的MMIO地址空间,然后调用pci_register_bar函数注册bar空间。

    最重要的是设置d->nic,即E1000State结构中的NICState字段,这里是调用了qemu_new_nic函数创建一个net client

     1 NICState *qemu_new_nic(NetClientInfo *info,
     2                        NICConf *conf,
     3                        const char *model,
     4                        const char *name,
     5                        void *opaque)
     6 {
     7 //conf->peers.ncs指向一个NetClientState指针数组,即数组的每一项都指向一个NetClientState结构
     8     NetClientState **peers = conf->peers.ncs;
     9     NICState *nic;
    10     int i, queues = MAX(1, conf->queues);//这里的queues貌似应该是0
    11 
    12     assert(info->type == NET_CLIENT_OPTIONS_KIND_NIC);
    13     assert(info->size >= sizeof(NICState));
    14 
    15     nic = g_malloc0(info->size + sizeof(NetClientState) * queues);
    16     nic->ncs = (void *)nic + info->size;
    17     //nic->ncs也指向一个NetClientState数组,数组项的个数是MAX(1, conf->queues);
    18     nic->conf = conf;
    19     nic->opaque = opaque;
    20     //设置两个数组的NetClientState建立关系
    21     for (i = 0; i < queues; i++) {
    22         qemu_net_client_setup(&nic->ncs[i], info, peers[i], model, name,
    23                               NULL);
    24         nic->ncs[i].queue_index = i;
    25     }
    26 
    27     return nic;
    28 }

    该函数就需要仔细分析一下了,其中有几点我也不是很明白,后面会表明出来。

    函数体中首先获取conf->peers.ncs,结合前篇文章不难看到这里是获取了一个NetClientState地址数组的地址,peers便指向这个数组,这里应该是对端的NetClientState地址数组。

    然后给nic分配地址,这里分配的空间是info->size + sizeof(NetClientState) * queues,可以看到这里虽然是给NICState申请空间,但是紧跟着NICState还有一个queues数量的NetClientState空间,这些net client是代表网卡端的net client.

    然后就是一个循环,依次调用qemu_net_client_setup函数对net client 做设置,并和前面提到的对端数组中的对应NetClientState做相互的关联。

    疑惑:

    1、在NICConf的初始化中并为什么没有发现初始化queues字段的?难道是这里queues字段默认是0?

    2、这里NICState为什么会关联一个NetClientState地址数组,从架构来看,好像和多队列相关,每个队列对应一个NetClientState结构,但是在Hub初始化端口的时候发现每个端口只有一个NetClientState,这里有点疑惑,难道NICState关联的数组里面其实只有一个表项?

    总结:

    本篇文章大致结合源代码分析了各个数据结构之间建立关系的过程,总体上展现了一个框架,下篇文章就在上一个层次,从数据包的流向看数据包是如何在这些结构中流动的!!

    前面笔者的疑惑,还请晓得的老师多多指点,谢谢!

  • 相关阅读:
    IDEA工具-快捷键整理
    Intellij热部署插件JRebel_转载
    [刘阳Java]_为什么要前后端分离
    [刘阳Java]_程序员Java编程进阶的5个注意点,别编程两三年还是增删改查
    [刘阳Java]_Web前端入门级练习_迅雷首页第一屏设计
    [刘阳Java]_Web前端入门级练习_迅雷官宣网设计
    [刘阳Java]_MySQL数据优化总结_查询备忘录
    Mybatis总结(一)
    java.io.IOException: Could not find resource com/xxx/xxxMapper.xml
    push to origin/master was rejected错误解决方案
  • 原文地址:https://www.cnblogs.com/ck1020/p/5913906.html
Copyright © 2011-2022 走看看