2016-09-27

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

这里还是分为三个部分:

1、Tap设备区

2、Hub区

3、NIC区


1、Tap设备区

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

 static int (* const net_client_init_fun[NET_CLIENT_OPTIONS_KIND_MAX])(
const NetClientOptions *opts,
const char *name,
NetClientState *peer) = {
[NET_CLIENT_OPTIONS_KIND_NIC] = net_init_nic,
#ifdef CONFIG_SLIRP
[NET_CLIENT_OPTIONS_KIND_USER] = net_init_slirp,
#endif
[NET_CLIENT_OPTIONS_KIND_TAP] = net_init_tap,
[NET_CLIENT_OPTIONS_KIND_SOCKET] = net_init_socket,
#ifdef CONFIG_VDE
[NET_CLIENT_OPTIONS_KIND_VDE] = net_init_vde,
#endif
[NET_CLIENT_OPTIONS_KIND_DUMP] = net_init_dump,
#ifdef CONFIG_NET_BRIDGE
[NET_CLIENT_OPTIONS_KIND_BRIDGE] = net_init_bridge,
#endif
[NET_CLIENT_OPTIONS_KIND_HUBPORT] = net_init_hubport,
};

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

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

 static int net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer,
const char *model, const char *name,
const char *ifname, const char *script,
const char *downscript, const char *vhostfdname,
int vnet_hdr, int fd)
{
TAPState *s; s = net_tap_fd_init(peer, model, name, fd, vnet_hdr);
if (!s) {
close(fd);
return -;
} if (tap_set_sndbuf(s->fd, tap) < ) {
return -;
} if (tap->has_fd || tap->has_fds) {
snprintf(s->nc.info_str, sizeof(s->nc.info_str), "fd=%d", fd);
} else if (tap->has_helper) {
snprintf(s->nc.info_str, sizeof(s->nc.info_str), "helper=%s",
tap->helper);
} else {
snprintf(s->nc.info_str, sizeof(s->nc.info_str),
"ifname=%s,script=%s,downscript=%s", ifname, script,
downscript); if (strcmp(downscript, "no") != ) {
snprintf(s->down_script, sizeof(s->down_script), "%s", downscript);
snprintf(s->down_script_arg, sizeof(s->down_script_arg),
"%s", ifname);
}
} if (tap->has_vhost ? tap->vhost :
vhostfdname || (tap->has_vhostforce && tap->vhostforce)) {
int vhostfd; if (tap->has_vhostfd || tap->has_vhostfds) {
vhostfd = monitor_handle_fd_param(cur_mon, vhostfdname);
if (vhostfd == -) {
return -;
}
} else {
vhostfd = -;
} s->vhost_net = vhost_net_init(&s->nc, vhostfd,
tap->has_vhostforce && tap->vhostforce);
if (!s->vhost_net) {
error_report("vhost-net requested but could not be initialized");
return -;
}
} else if (tap->has_vhostfd || tap->has_vhostfds) {
error_report("vhostfd= is not valid without vhost");
return -;
} return ;
}

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

 NetClientState *qemu_new_net_client(NetClientInfo *info,
NetClientState *peer,
const char *model,
const char *name)
{
NetClientState *nc; assert(info->size >= sizeof(NetClientState)); nc = g_malloc0(info->size);//这里申请的空间是info->size,回想在网卡端申请的是info->size+num*sizeof(NetClientState)
//在增加端口的时候peer还是null
qemu_net_client_setup(nc, info, peer, model, name,
qemu_net_client_destructor); return nc;
}

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

 static NetClientInfo net_tap_info = {
.type = NET_CLIENT_OPTIONS_KIND_TAP,
.size = sizeof(TAPState),
.receive = tap_receive,
.receive_raw = tap_receive_raw,
.receive_iov = tap_receive_iov,
.poll = tap_poll,
.cleanup = tap_cleanup,
};

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

 static void qemu_net_client_setup(NetClientState *nc,
NetClientInfo *info,
NetClientState *peer,
const char *model,
const char *name,
NetClientDestructor *destructor)
{
nc->info = info;//建立NetClientState到port的连接
nc->model = g_strdup(model);
if (name) {
nc->name = g_strdup(name);
} else {
nc->name = assign_name(nc, model);
} if (peer) {//相互指向
assert(!peer->peer);
nc->peer = peer;
peer->peer = nc;
}
QTAILQ_INSERT_TAIL(&net_clients, nc, next);//加入全局的net_clients链表中 nc->incoming_queue = qemu_new_net_queue(nc);//设置接收队列
nc->destructor = destructor;
}

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


2、HUb端

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

 int net_init_hubport(const NetClientOptions *opts, const char *name,
NetClientState *peer)
{
const NetdevHubPortOptions *hubport; assert(opts->kind == NET_CLIENT_OPTIONS_KIND_HUBPORT);
hubport = opts->hubport; /* Treat hub port like a backend, NIC must be the one to peer */
if (peer) {
return -EINVAL;
} net_hub_add_port(hubport->hubid, name);
return ;
}

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

看net_hub_add_port函数

 NetClientState *net_hub_add_port(int hub_id, const char *name)
{
NetHub *hub;
NetHubPort *port; QLIST_FOREACH(hub, &hubs, next) {
if (hub->id == hub_id) {
break;
}
}
if (!hub) {
hub = net_hub_new(hub_id);
}
port = net_hub_port_new(hub, name);
return &port->nc;
}

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

 static NetHubPort *net_hub_port_new(NetHub *hub, const char *name)
{
NetClientState *nc;
NetHubPort *port;
int id = hub->num_ports++;
char default_name[]; if (!name) {
snprintf(default_name, sizeof(default_name),
"hub%dport%d", hub->id, id);
name = default_name;
} nc = qemu_new_net_client(&net_hub_port_info, NULL, "hub", name);
port = DO_UPCAST(NetHubPort, nc, nc);
port->id = id;
port->hub = hub; QLIST_INSERT_HEAD(&hub->ports, port, next); return port;
}

这里会通过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文件中

 static void e1000_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); k->init = pci_e1000_init;
k->exit = pci_e1000_uninit;
k->romfile = "efi-e1000.rom";
k->vendor_id = PCI_VENDOR_ID_INTEL;
k->device_id = E1000_DEVID;
k->revision = 0x03;
k->class_id = PCI_CLASS_NETWORK_ETHERNET;
set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
dc->desc = "Intel Gigabit Ethernet";
dc->reset = qdev_e1000_reset;
dc->vmsd = &vmstate_e1000;
dc->props = e1000_properties;
}

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

 static Property e1000_properties[] = {
DEFINE_NIC_PROPERTIES(E1000State, conf),
DEFINE_PROP_BIT("autonegotiation", E1000State,
compat_flags, E1000_FLAG_AUTONEG_BIT, true),
DEFINE_PROP_BIT("mitigation", E1000State,
compat_flags, E1000_FLAG_MIT_BIT, true),
DEFINE_PROP_END_OF_LIST(),
};

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

 #define DEFINE_NIC_PROPERTIES(_state, _conf)                            \
DEFINE_PROP_MACADDR("mac", _state, _conf.macaddr), \
DEFINE_PROP_VLAN("vlan", _state, _conf.peers), \
DEFINE_PROP_NETDEV("netdev", _state, _conf.peers), \
DEFINE_PROP_INT32("bootindex", _state, _conf.bootindex, -)

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

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

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

 NICState *qemu_new_nic(NetClientInfo *info,
NICConf *conf,
const char *model,
const char *name,
void *opaque)
{
//conf->peers.ncs指向一个NetClientState指针数组,即数组的每一项都指向一个NetClientState结构
NetClientState **peers = conf->peers.ncs;
NICState *nic;
int i, queues = MAX(, conf->queues);//这里的queues貌似应该是0 assert(info->type == NET_CLIENT_OPTIONS_KIND_NIC);
assert(info->size >= sizeof(NICState)); nic = g_malloc0(info->size + sizeof(NetClientState) * queues);
nic->ncs = (void *)nic + info->size;
//nic->ncs也指向一个NetClientState数组,数组项的个数是MAX(1, conf->queues);
nic->conf = conf;
nic->opaque = opaque;
//设置两个数组的NetClientState建立关系
for (i = ; i < queues; i++) {
qemu_net_client_setup(&nic->ncs[i], info, peers[i], model, name,
NULL);
nic->ncs[i].queue_index = i;
} return nic;
}

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

函数体中首先获取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关联的数组里面其实只有一个表项?

总结:

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

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

qemu网络虚拟化之数据流向分析二的更多相关文章

  1. qemu网络虚拟化之数据流向分析一

    插曲:   今天下午欣喜的想写点关于qemu网络部分的功能,但是中途出现了点小插曲,电脑被某人搞得死机了,并且文章也没有保存.结果,,,就只能重新写了!!所以这里强烈建议开发团队提供自动保存的功能! ...

  2. qemu网络虚拟化之数据流向分析三

    2016-09-27 前篇文章通过分析源代码,大致描述了各个数据结构之间的关系是如何建立的,那么今天就从数据包的角度,分析下数据包是如何在这些数据结构中间流转的! 这部分内容需要结合前面两篇文章来看, ...

  3. KDD Cup 99网络入侵检测数据的分析

    看论文 该数据集是从一个模拟的美国空军局域网上采集来的 9 个星期的网络连接数据, 分成具有标识的训练数据和未加标识的测试数据.测试数据和训练数据有着不同的概率分布, 测试数据包含了一些未出现在训练数 ...

  4. MapReduce数据流向分析

    MR数据流向示意图 步骤 1 输入文件从HDFS流向Mapper节点.在一般情况下,map所需要的数据就存在本节点,这就是数据本地化计算的优势,但是往往集群中数据分布不均衡(1000台节点,数据冗余度 ...

  5. 深入浅出Hyper-V网络虚拟化(序)

    服务器虚拟化已经越来越普及,很多企业也都在尝试着将现有业务转换成虚拟化的方式来运行,即在一个物理服务器上虚拟出多个实例,而每个实例彼此隔离,就好像在使用一台真实主机一样:网络虚拟化也同样如此,在同一条 ...

  6. 网络虚拟化技术(二): TUN/TAP MACVLAN MACVTAP (转)

    网络虚拟化技术(二): TUN/TAP MACVLAN MACVTAP 27 March 2013 TUN 设备 TUN 设备是一种虚拟网络设备,通过此设备,程序可以方便得模拟网络行为.先来看看物理设 ...

  7. 网络数据包分析 网卡Offload

    http://blog.nsfocus.net/network-packets-analysis-nic-offload/     对于网络安全来说,网络传输数据包的捕获和分析是个基础工作,绿盟科技研 ...

  8. 数据中心网络技术新贵:VXLAN与园区网络虚拟化

    摘要:为了应对传统数据中心网络对服务器虚拟化技术的限制,VXLAN技术应运而生. 1 概述 传统数据中心网络面临的问题 虚拟机规模受设备表项规格限制 在传统二层网络中,交换机通过查询MAC地址表来转发 ...

  9. Android4.0图库Gallery2代码分析(二) 数据管理和数据加载

    Android4.0图库Gallery2代码分析(二) 数据管理和数据加载 2012-09-07 11:19 8152人阅读 评论(12) 收藏 举报 代码分析android相册优化工作 Androi ...

随机推荐

  1. Excel关闭事件

    记录一下,弄VBA曾经遇到一个需求,遇到用到这个事件,找了很久,最后还是问别人才知道的. Sub Auto_Close() ThisWorkbook.Saved = True End Sub

  2. hdu1285 确定比赛名次(拓扑排序多种方法)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1285 Problem Description 有N个比赛队(1<=N<=500),编号依次 ...

  3. docker Failed to get D-Bus connection 报错 docker run -e MYVAR1 --env MYVAR2=foo --env-file ./env.list ubuntu bash

    docker Failed to get D-Bus connection 报错 原创憬薇2016-01-15 11:25:26评论(10)40278人阅读   在centos7的容器里面出现了一个B ...

  4. thinkphp 命名规范

    目录和文件命名 目录和文件名采用 小写+下划线,并且以小写字母开头: 类库.函数文件统一以.php为后缀: 类的文件名均以命名空间定义,并且命名空间的路径和类库文件所在路径一致(包括大小写): 类名和 ...

  5. 第二百七十五节,MySQL数据库安装和介绍

    MySQL数据库安装 一.概述 1.什么是数据库 ? 答:数据的仓库,称其为数据库 2.什么是 MySQL.Oracle.SQLite.Access.MS SQL Server等 ? 答:他们均是一种 ...

  6. 小结:A* & IDA* & 迭代深搜

    概要: 在dfs中,如果答案的深度很小但是却很宽,而且bfs还不一定好做的情况下,我们就综合bfs的优点,结合dfs的思想,进行有限制的dfs.在这里A*.IDA*和迭代深搜都是对dfs的优化,因此放 ...

  7. phalcon下拉列表

    <?php echo Phalcon\Tag::selectStatic("gender", array(0 => "Male", 1 => ...

  8. Linux性能分析top iostat vmstat free

    最近看到一大牛的分析报告,才知道笔者认识这4个命令是多么肤浅,其实要读懂内存的信息,是要一些功力的.1.top   VIRT           虚拟内存总量,VIRT=SWAP+RESSWAP    ...

  9. iOS开发之--NSData与UIImage之间的转换

    //NSData转换为UIImage NSData *imageData = [NSData dataWithContentsOfFile: imagePath]; UIImage *image = ...

  10. 1-2、superset国际化

    最近由于工作需要研究开源可视化项目superset,由于其国际化做不怎么好,故而记录下国际化的过程,本篇本着『授人以鱼不如授人以渔』的原则,只叙述国际化的过程及方法,不提供直接的国际化文件. 为了方便 ...