设为首页 收藏本站
查看: 914|回复: 0

[经验分享] qemu网络虚拟化之数据流向分析二

[复制链接]

尚未签到

发表于 2017-6-25 09:52:42 | 显示全部楼层 |阅读模式
  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, info, peers, model, name,
23                               NULL);
24         nic->ncs.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关联的数组里面其实只有一个表项?
  总结:
  本篇文章大致结合源代码分析了各个数据结构之间建立关系的过程,总体上展现了一个框架,下篇文章就在上一个层次,从数据包的流向看数据包是如何在这些结构中流动的!!
  前面笔者的疑惑,还请晓得的老师多多指点,谢谢!

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-387860-1-1.html 上篇帖子: Makefile 使用总结【转】 下篇帖子: 微博混合云DCP:极端流量下的峰值应对与架构挑战
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表