说点题外话(我不仅把这些当作技术文章,还当作工作笔记,甚至当作生活日记),最近工作在制作各种docker镜像,有点小忙。而工作剩余时间又在看汇编,和操作系统知识,所以对ovs就没什么时间去了解了。不过还好,这周空闲下来了,就看了下ovs中的upcall()函数调用。
话说现在ovs已经出了2.xxx版本了,我稍微浏览了下,发现有些函数名改变了,但其主要功能还是保留的。为了衔接前几篇blog,所以我还是选择下载1.xx版本的源代码来分析。我以前那套ovs源代码做了很多笔记,不过可惜搬公司的时候服务器坏掉了,所有数据都找不到了(因为分析这个源代码是个人行为、私事,所以也就没有去恢复硬盘了)。
还有个事要麻烦下,我分析这些源代码是以个人的观点和判断,我没有什么资料,就是一步一步的去分析,然后组成整个框架,其中当然免不了有些错误(人家是世界级团队完成的,你一个小程序员花这么点时间就想弄明白,那估计是不太可能的),所以我非常鼓励支持查资料的朋友仅仅是把我的分析当作一种参考,然后如果发现和我猜想的框架有问题时能及时告知我,谢谢!!
好了,下面正式谈谈和源代码有关的事了。我看了下upcall()函数的大体实现,其中主线是用Linux内核中的NetLink通信机制。而其中涉及到一些其他知识点,大部分在前面已经分析过了;但vlan知识点,在前面好像基本上没有提到,个人觉得这是个非常有价值的知识点,后续我会好好了解下。而有关ovs的前一篇[openVswitch(OVS)源代码分析 upcall调用(之linux中的NetLink通信机制)](http://blog.csdn.net/yuzhihui_no1/article/details/40790131)我现在到回去看了看,感觉没有写好,有点懊恼。有些东西写的不够仔细,太注重代码的实现了,而没写好一些理论的东西。如果要了解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()的详解)](http://blog.csdn.net/yuzhihui_no1/article/details/38356393)。
分析到这里看出什么问题来了没?就是网桥和端口连接关系问题。
第一、在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的连接结构为:
![](https://box.kancloud.cn/2016-02-17_56c42ec4ef46f.jpg)
上面图中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](http://blog.csdn.net/yuzhihui_no1/article/details/41546481)
若有不正确之处,望大家指正,共同学习!谢谢!!!
- 前言
- OVS datapath模块分析:packet处理流程
- openVswitch(OVS)源代码分析之简介
- openVswitch(OVS)源代码分析之数据结构
- openVswitch(OVS)源代码分析之工作流程(收发数据包)
- openVswitch(OVS)源代码分析之工作流程(数据包处理)
- openVswitch(OVS)源代码分析之工作流程(key值得提取)
- openVswitch(OVS)源代码分析之工作流程(flow流表查询)
- openVswitch(OVS)源代码的分析技巧(哈希桶结构体为例)
- openVswitch(OVS)源代码分析之工作流程(哈希桶结构体的解释)
- openVswitch(OVS)源代码之linux RCU锁机制分析
- openVswitch(OVS)源代码分析 upcall调用(之linux中的NetLink通信机制)
- openVswitch(OVS)源代码分析 upcall调用(一)