|
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关联的数组里面其实只有一个表项?
总结:
本篇文章大致结合源代码分析了各个数据结构之间建立关系的过程,总体上展现了一个框架,下篇文章就在上一个层次,从数据包的流向看数据包是如何在这些结构中流动的!!
前面笔者的疑惑,还请晓得的老师多多指点,谢谢! |
|