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关联的数组里面其实只有一个表项?

 

总结:

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

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

 

posted @ 2016-09-27 20:51  jack.chen  Views(1804)  Comments(0Edit  收藏  举报

以马内利