說點題外話(我不僅把這些當作技術文章,還當作工作筆記,甚至當作生活日記),最近工作在制作各種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的連接結構為:

上面圖中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調用(一)