openVswitch(OVS)源代码分析 upcall调用(一)

        说点题外话(我不仅把这些当作技术文章,还当作工作笔记,甚至当作生活日记),最近工作在制作各种docker镜像,有点小忙。而工作剩余时间又在看汇编,和操作系统知识,所以对ovs就没什么时间去了解了。不过还好,这周空闲下来了,就看了下ovs中的upcall()函数调用。

        话说现在ovs已经出了2.xxx版本了,我稍微浏览了下,发现有些函数名改变了,但其主要功能还是保留的。为了衔接前几篇blog,所以我还是选择下载1.xx版本的源代码来分析。我以前那套ovs源代码做了很多笔记,不过可惜搬公司的时候服务器坏掉了,所有数据都找不到了(因为分析这个源代码是个人行为、私事,所以也就没有去恢复硬盘了)。

        还有个事要麻烦下,我分析这些源代码是以个人的观点和判断,我没有什么资料,就是一步一步的去分析,然后组成整个框架,其中当然免不了有些错误(人家是世界级团队完成的,你一个小程序员花这么点时间就想弄明白,那估计是不太可能的),所以我非常鼓励支持查资料的朋友仅仅是把我的分析当作一种参考,然后如果发现和我猜想的框架有问题时能及时告知我,谢谢!!

        好了,下面正式谈谈和源代码有关的事了。我看了下upcall()函数的大体实现,其中主线是用Linux内核中的NetLink通信机制。而其中涉及到一些其他知识点,大部分在前面已经分析过了;但vlan知识点,在前面好像基本上没有提到,个人觉得这是个非常有价值的知识点,后续我会好好了解下。而有关ovs的前一篇openVswitch(OVS)源代码分析 upcall调用(之linux中的NetLink通信机制)我现在到回去看了看,感觉没有写好,有点懊恼。有些东西写的不够仔细,太注重代码的实现了,而没写好一些理论的东西。如果要了解upcall()函数,那些基础的结构体还是要重点了解下,所以我会修改前面的NetLink分析或者到后面再分析下理论知识。

        现在来想下为什么有upcall()函数?因为比如当第一个数据包过来时(前期没有和这个数据包的ip主机通信过),ovs中没有任何有关于该主机的信息,更没有设置一些规则来处理接受到该主机的数据包。所以当第一次接受到这个数据包时,就要提取出数据包中一些信息,下发到用户空间去,让用户空间做些规则用来处理下次接收到的该类数据包。

        下面开始看代码,还是从void ovs_dp_process_received_packet(struct vport *p, struct sk_buff *skb)函数开始切入吧。

	if (unlikely(!flow)) {//查不到流表的情况
		struct dp_upcall_info upcall;

		upcall.cmd = OVS_PACKET_CMD_MISS; //包miss,表示这个包是没匹配到的
		upcall.key = &key; //key值,对一个sk_buff网络包的特征数据进行提取组成的结构
		upcall.userdata = NULL; // 传送给用户空间的数据
		upcall.portid = p->upcall_portid; //传送给用户空间时使用的id号,netlink中已经说明
		ovs_dp_upcall(dp, skb, &upcall); // 调用函数处理,本blog的主角
		consume_skb(skb); // 释放掉包结构==》kfree_skb(skb) 
		stats_counter = &stats->n_missed; //对包的计算
		goto out;
	}

        下面就是轮到今天的主角出场了:

int ovs_dp_upcall(struct datapath *dp, struct sk_buff *skb,
		  const struct dp_upcall_info *upcall_info)
{
	struct dp_stats_percpu *stats;
	int dp_ifindex;
	int err;
        // 判断下pid是否为0,这个是用来NetLink通讯使用的,为0表示传给内核空间的
	if (upcall_info->portid == 0) {
		err = -ENOTCONN;
		goto err;
	}
        // 这个字段呢,是个设备结构索引号,等下详细分析,也是这篇blog的重点
	dp_ifindex = get_dpifindex(dp);
	if (!dp_ifindex) {
		err = -ENODEV;
		goto err;
	}

/*
 *     forward_ip_summed - map internal checksum state back onto native
 *      kernel fields.
 * @skb: Packet to manipulate.
 * @xmit: Whether we are about send on the transmit path the network stack.
 * This follows the same logic as the @xmit field in compute_ip_summed().
 * Generally, a given vport will have opposite values for @xmit passed to
 * these two functions.
 * When a packet is about to egress from OVS take our internal fields (including
 * any modifications we have made) and recreate the correct representation for
 * this kernel.  This may do things like change the transport header offset.
 */
	forward_ip_summed(skb, true);
//下面这两个函数就是要排队发送信息到用户空间的,不过这要到下一篇blog分析
	if (!skb_is_gso(skb))
		err = queue_userspace_packet(ovs_dp_get_net(dp), dp_ifindex, skb, upcall_info);
	else
		err = queue_gso_packets(ovs_dp_get_net(dp), dp_ifindex, skb, upcall_info);
	if (err)
		goto err;

	return 0;
// 下面是出错时,跳转到这里做退出处理的,就是一些数据包的统计等操作
err:
	stats = this_cpu_ptr(dp->stats_percpu);

	u64_stats_update_begin(&stats->sync);
	stats->n_lost++;
	u64_stats_update_end(&stats->sync);

	return err;
}

        上面是大概的分析了下upcall()函数,不过这不是本blog的重点,本blog重点是由dp_ifindex = get_dpifindex(dp);引出的一个框架问题,感觉有必要分析清楚(有这个价值),关系到网桥和端口之间的关系。

===================================================================================================================

        

        切入点还是从dp_ifindex = get_dpifindex(dp);开始吧,这是一个设备接口索引,就是获取网卡设备索引号的。

static int get_dpifindex(struct datapath *dp)
{
	struct vport *local;
	int ifindex;
       // rcu读锁
	rcu_read_lock();
       // 根据网桥和指定port_no查找vport结构体
	local = ovs_vport_rcu(dp, OVSP_LOCAL);
	//get_ifindex:获取与所述设备相关联的系统的接口索引。这个可以参考net_device网络设备结构体
	//可以为null,如果设备不具备的接口索引。
	if (local)
		ifindex = local->ops->get_ifindex(local);
	else
		ifindex = 0;

	rcu_read_unlock();

	return ifindex;
}

        继续追查下去,发现最后会调用struct vport *ovs_lookup_vport(const struct datapath *dp, u16 port_no)函数来查询端口结构体。其实到这里你就会发现一些情况了。其中注意下各个函数的调用传的参数。

struct vport *ovs_lookup_vport(const struct datapath *dp, u16 port_no)
{
	struct vport *vport;
	struct hlist_head *head;
        // 这个调用了vport_hash_bucker()函数,具体实现在下面,这是一个查找hash表头部的函数
	head = vport_hash_bucket(dp, port_no);
        // 上面是查找hash表头部,说明有多个hash表头,每个hash表头下面应该挂载了很多node节点
        // 而下面就是Linux内核中定义的宏,用来遍历查找hash表中每个node节点的,通过匹配port_no来查找到vport
	// struct vport    ; struct hlist_head head ; struct hlist_node
	hlist_for_each_entry_rcu(vport, head, dp_hash_node) {
		if (vport->port_no == port_no)
			return vport;
	}
	return NULL;
}
/*----------------------------------------------------------------------------------------------*/
// 这是查找哈希头函数,有多个相连的hash头链表
static struct hlist_head *vport_hash_bucket(const struct datapath *dp,</span>
					    u16 port_no)
{
	// port_no & (DP_VPORT_HASH_BUCKETS - 1)就是查找hash位置
	// 比如表长为8的,需要查找id为10,那么用10/8 == 2。10 & (8-1) == 2
	return &dp->ports[port_no & (DP_VPORT_HASH_BUCKETS - 1)];
}
/*----------------------------------------------------------------------------------------------*/
// 下面是Linux中定义的宏,专门用来遍历链表中的节点的
// struct vport    ; struct hlist_head head ; struct hlist_node
//      hlist_for_each_entry_rcu(vport, head, dp_hash_node)
#define hlist_for_each_entry_rcu(pos, head, member)			\
	for (pos = hlist_entry_safe (rcu_dereference_raw(hlist_first_rcu(head)),\
			typeof(*(pos)), member);			\
		pos;							\
		pos = hlist_entry_safe(rcu_dereference_raw(hlist_next_rcu(\
			&(pos)->member)), typeof(*(pos)), member))

/*----------------------------------------------------------------------------------------------*/
// xxx(first, vport , datapath) 求vport的结构体
#define hlist_entry_safe(ptr, type, member) \
	({ typeof(ptr) ____ptr = (ptr); \
	 ____ptr ? hlist_entry(____ptr, type, member) : NULL; \
	 })

        上面的遍历链表节点宏,是Linux专门定义的,个人感觉还是比较巧妙。在内核代码中有很多地方用到这个宏,hlist_entry_safe(xxxx)就是 linux内核之container_of()详解(即:list_entry()的详解)。

        分析到这里看出什么问题来了没?就是网桥和端口连接关系问题。

        第一、在struct vport *ovs_lookup_vport(const struct datapath *dp, u16 port_no)中调用了vport_hash_bucket(dp, port_no);来获取head结构体,这个函数非常简单,可以看到它里面的实现其实就一句话:&dp->ports[port_no & (DP_VPORT_HASH_BUCKETS - 1)];但这说明了一个问题,就是vport的head在个哈希表中。

        第二、调用hlist_entry_safe()传的参数:hlist_entry_safe(head->first,vport,dp_hash_node),这可以看出dp_hash_node是连接head下面的,组成vport链表的。

        所以综合上面情况,可以看出网桥和vport的连接结构为:

        openVswitch(OVS)源代码分析 upcall调用(一)_第1张图片

        上面图中vport结构体链表其实是用dp_hash_node链接起来的,所以说dp_hash_node是哈希链表链接元素,而其他则是数据结构体。为了形象点,所以没怎么区分,理解就行。

        看到这个结构可能会有点诱惑:那么vport中的struct hlist_node hash_node;字段是干什么的。开始我也以为这个字段是连接vport形成vport链表的,而dp_hash_node是有关网桥的链表。但错了,虽然现在我也不能够非常清楚hash_node字段是干什么用的。可以查看下vport结构体各个字段解释:

/**
 * struct vport - one port within a datapath
 * @rcu: RCU callback head for deferred destruction.
 * @dp: Datapath to which this port belongs.
 * @upcall_portid: The Netlink port to use for packets received on this port that
 * miss the flow table.
 * @port_no: Index into @dp's @ports array.
 * @hash_node: Element in @dev_table hash table in vport.c.
 * @dp_hash_node: Element in @datapath->ports hash table in datapath.c.
 * @ops: Class structure.
 * @percpu_stats: Points to per-CPU statistics used and maintained by vport
 * @stats_lock: Protects @err_stats and @offset_stats.
 * @err_stats: Points to error statistics used and maintained by vport
 * @offset_stats: Added to actual statistics as a sop to compatibility with
 * XAPI for Citrix XenServer.  Deprecated.
 */
        我追查了下hash_node,确实发现和dev_table有关,但具体的还没有分析出来。dev_tables是什么?他的定义是:

/* Protected by RCU read lock for reading, ovs_mutex for writing. */
static struct hlist_head *dev_table;

        可以看下他们相关联的函数:struct vport *ovs_vport_locate(struct net *net, const char *name),现在只找相关的东西,不会具体分析函数语句:

struct vport *ovs_vport_locate(struct net *net, const char *name)
{
	struct hlist_head *bucket = hash_bucket(net, name);
	struct vport *vport;

	hlist_for_each_entry_rcu(vport, bucket, hash_node)// 这里可以看出vport结构体中的hash_node是和bucket一样的,那么bucket是什么呢?
		if (!strcmp(name, vport->ops->get_name(vport)) &&
		    net_eq(ovs_dp_get_net(vport->dp), net))
			return vport;

	return NULL;
}
        在追查bucket时,调用了hash_bucket()函数,可以看下实现:

static struct hlist_head *hash_bucket(struct net *net, const char *name)
{
	unsigned int hash = jhash(name, strlen(name), (unsigned long) net);// 求随机数
	return &dev_table[hash & (VPORT_HASH_BUCKETS - 1)];// 返回的是dev_table表中的某个元素的地址
}

        到这里就可以看出vport结构中的hash_node确实和dev_table有关,具体有什么关系就不再深究了,因为这不是科研。如果看了前面那副框架图的,在网桥连接vport的框架部分应该是上面的图了,当然这是个人观点。

        转载请注明作者和原文出处,原文地址:http://blog.csdn.net/yuzhihui_no1/article/details/41546481

        若有不正确之处,望大家指正,共同学习!谢谢!!!


你可能感兴趣的:(云计算,sdn,openvswitch,网络虚拟化,linux内核网络协议栈)