記得Pascal之父、結構化程序設計的先驅Niklaus Wirth最著名的一本書,書名叫作《算法 + 數據結構 = 程序》。還有位傳奇的軟件工程師Frederick P. Brooks曾經說過:“給我看你的數據”。因此可見數據結構對於一個程序來說是多麼的重要,如果你不瞭解程序中的數據結構,你根本就無法去理解整個程序的工作流程。所以在分析openVswitch(OVS)源代碼之前先來了解下openVswitch中一些重要的數據結構,這將對你分析後面的源代碼起着至關重要的作用。
按照數據包的流向來分析下涉及到一些重要的數據結構。
第一、vport端口模塊中涉及到的一些數據結構:
- // 這是表示網橋中各個端口結構體
- struct vport {
- struct rcu_head rcu; // 一種鎖機制
- struct datapath *dp; // 網橋結構體指針,表示該端口是屬於哪個網橋的
- u32 upcall_portid; // Netlink端口收到的數據包時使用的端口id
- u16 port_no; // 端口號,唯一標識該端口
-
- // 因爲一個網橋上有多個端口,而這些端口都是用哈希鏈表來存儲的,
- // 所以這是鏈表元素(裏面沒有數據,只有next和prev前驅後繼指針,數據部分就是vport結構體中的其他成員)
- struct hlist_node hash_node;
- struct hlist_node dp_hash_node; // 這是網橋的哈希鏈表元素
- const struct vport_ops *ops; // 這是端口結構體的操作函數指針結構體,結構體裏面存放了很多操作函數的函數指針
-
- struct pcpu_tstats __percpu *percpu_stats;// vport指向每個cpu的統計數據使用和維護
-
- spinlock_t stats_lock; // 自旋鎖,防止異步操作,保護下面的兩個成員
- struct vport_err_stats err_stats; // 錯誤狀態(錯誤標識)指出錯誤vport使用和維護的統計數字
- struct ovs_vport_stats offset_stats; // 添加到實際統計數據,部分原因是爲了兼容
- };
-
- // 端口參數,當創建一個新的vport端口是要傳入的參數
- struct vport_parms {
- const char *name; // 新端口的名字
- enum ovs_vport_type type; // 新端口的類型(端口不僅僅只有一種類型,後面會分析到)
- struct nlattr *options; // 這個沒怎麼用到過,好像是從Netlink消息中得到的OVS_VPORT_ATTR_OPTIONS屬性
-
- /* For ovs_vport_alloc(). */
- struct datapath *dp; // 新的端口屬於哪個網橋的
- u16 port_no; // 新端口的端口號
- u32 upcall_portid; // 和Netlink通信時使用的端口id
- };
-
- // 這是端口vport操作函數的函數指針結構體,是操作函數的集合,裏面存放了所有有關vport操作函數的函數指針
- struct vport_ops {
- enum ovs_vport_type type; // 端口的類型
- u32 flags; // 標識符
-
- // vport端口模塊的初始化加載和卸載函數
- int (*init)(void); // 加載模塊函數,不成功則over
- void (*exit)(void); // 卸載端口模塊函數
-
- // 新vport端口的創建函數和銷燬端口的函數
- struct vport *(*create)(const struct vport_parms *); // 根據指定的參數配置創建個新的vport,成功返回新端口指針
- void (*destroy)(struct vport *); // 銷燬端口函數
-
- // 得到和設置option成員函數
- int (*set_options)(struct vport *, struct nlattr *);
- int (*get_options)(const struct vport *, struct sk_buff *);
-
- // 得到端口名稱和配置以及發送數據包函數
- const char *(*get_name)(const struct vport *); // 獲取指定端口的名稱
- void (*get_config)(const struct vport *, void *);// 獲取指定端口的配置信息
- int (*get_ifindex)(const struct vport *);// 獲取系統接口和設備間的指數
- int (*send)(struct vport *, struct sk_buff *); // 發送數據包到設備上
- };
-
- // 端口vport的類型,枚舉類型存儲
- enum ovs_vport_type{
- OVS_VPORT_TYPE_UNSPEC,
- OVS_VPORT_TYPE_NETDEV,
- OVS_VPORT_TYPE_INTERNAL,
- OVS_VPORT_TYPE_GRE,
- OVS_VPORT_TYPE_VXLAN,
- OVS_VPORT_TYPE_GRE64 = 104,
- OVS_VPORT_TYPE_LISP = 105,
- _OVS_VPORT_TYPE_MAX
- };
第二、網橋模塊datapath中涉及到的一些數據結構:
- // 網橋結構體
- struct datapath {
- struct rcu_head rcu; // RCU調延遲破壞。
- struct list_head list_node; // 網橋哈希鏈表元素,裏面只有next和prev前驅後繼指針,數據時該結構體其他成員
-
- /* Flow table. */
- struct flow_table __rcu *table;// 這是哈希流表,裏面包含了哈希桶的地址指針。該哈希表受_rcu機制保護
-
- /* Switch ports. */
- struct hlist_head *ports;// 一個網橋有多個端口,這些端口都是用哈希鏈表來鏈接的
-
- /* Stats. */
- struct dp_stats_percpu __percpu *stats_percpu;
-
- #ifdef CONFIG_NET_NS
- /* Network namespace ref. */
- struct net *net;
- #endif
- };
其實上面的網橋結構也表示了整個openVswitch(OVS)的結構,如果能捋順這些結構的關係,那就對分析openVswitch源代碼有很多幫助,下面來看下這些結構的關係圖:
第三、流表模塊flow中涉及到的一些數據結構:
- // 可以說這是openVswitch中最重要的結構體了(個人認爲)
- // 這是key值,主要是提取數據包中協議相關信息,這是後期要進行流表匹配的關鍵結構
- struct sw_flow_key {
- // 這是隧道相關的變量
- struct ovs_key_ipv4_tunnel tun_key; /* Encapsulating tunnel key. */
- struct {
- // 包的優先級
- u32 priority; // 包的優先級
- u32 skb_mark; // 包的mark值
- u16 in_port; // 包進入的端口號
- } phy; // 這是包的物理層信息結構體提取到的
- struct {
- u8 src[ETH_ALEN]; // 源mac地址
- u8 dst[ETH_ALEN]; // 目的mac地址
- __be16 tci; // 這好像是局域網組號
- __be16 type; // 包的類型,即:是IP包還是ARP包
- } eth; // 這是包的二層幀頭信息結構體提取到的
- struct {
- u8 proto; // 協議類型 TCP:6;UDP:17;ARP類型用低8位表示
- u8 tos; // 服務類型
- u8 ttl; // 生存時間,經過多少跳路由
- u8 frag; // 一種OVS中特有的OVS_FRAG_TYPE_*.
- } ip; // 這是包的三層IP頭信息結構體提取到的
- // 下面是共用體,有IPV4和IPV6兩個結構,爲了後期使用IPV6適應
- union {
- struct {
- struct {
- __be32 src; // 源IP地址
- __be32 dst; // 目標IP地址
- } addr; // IP中地址信息
- // 這又是個共用體,有ARP包和TCP包(包含UDP)兩種
- union {
- struct {
- __be16 src; // 源端口,應用層發送數據的端口
- __be16 dst; // 目的端口,也是指應用層傳輸數據端口
- } tp; // TCP(包含UDP)地址提取
- struct {
- u8 sha[ETH_ALEN]; // ARP頭中源Mac地址
- u8 tha[ETH_ALEN]; // ARP頭中目的Mac地址
- } arp;ARP頭結構地址提取
- };
- } ipv4;
- // 下面是IPV6的相關信息,基本和IPV4類似,這裏不講
- struct {
- struct {
- struct in6_addr src; /* IPv6 source address. */
- struct in6_addr dst; /* IPv6 destination address. */
- } addr;
- __be32 label; /* IPv6 flow label. */
- struct {
- __be16 src; /* TCP/UDP source port. */
- __be16 dst; /* TCP/UDP destination port. */
- } tp;
- struct {
- struct in6_addr target; /* ND target address. */
- u8 sll[ETH_ALEN]; /* ND source link layer address. */
- u8 tll[ETH_ALEN]; /* ND target link layer address. */
- } nd;
- } ipv6;
- };
- };
接下來要分析的數據結構是在網橋結構中涉及的的:struct flow_table __rcu *table;
- //流表
- struct flow_table {
- struct flex_array *buckets; //哈希桶地址指針
- unsigned int count, n_buckets; // 哈希桶個數
- struct rcu_head rcu; // rcu包含機制
- struct list_head *mask_list; // struct sw_flow_mask鏈表頭指針
- int node_ver;
- u32 hash_seed; //哈希算法需要的種子,後期匹配時要用到
- bool keep_flows; //是否保留流表項
- };
順序分析下去,應該是分析哈希桶結構體了,因爲這個結構體設計的實在是太巧妙了。所以應該仔細的分析下。
這是一個共用體,是個設計非常巧妙的共用體。因爲共用體的特點是:整個共用體的大小是其中最大成員變量的大小。也就是說 共用體成員中某個最大的成員的大小就是共用體的大小。正是利用這一點特性,最後一個char padding[FLEX_ARRAY_BASE_SIZE]其實是沒有用的,僅僅是起到一個佔位符的作用了。讓整個共用體的大小爲FLEX_ARRAY_BASE_SIZE(即是一個頁的大小:4096),那爲什麼要這麼費勁心機去設計呢?是因爲struct flex_array_part *parts[]; 這個結構,這個結構並不多見,因爲在標準的c/c++代碼中是無效的,只有在GNU下才是合法的。這個稱爲彈性數組,或者可變數組,和常規的數組不一樣。這裏這個彈性數組的大小是一個頁大小減去前面幾個整型成員變量後所剩的大小。
- // 哈希桶結構
- struct flex_array {
- // 共用體,第二個成員爲佔位符,爲共用體大小
- union {
- // 對於這個結構體的成員數據含義,真是花了我不少時間來研究,發現有歧義,(到後期流表匹配時會詳細分析)。現在就我認爲最正確的理解來分析
- struct {
- int element_size; // 無疑這是數組元素的大小
- int total_nr_elements; // 這是數組元素的總個數
- int elems_per_part; // 這是每個part指針指向的空間能存儲多少元素
- u32 reciprocal_elems;
- struct flex_array_part *parts[]; // 結構體指針數組,裏面存放的是struct flex_array_part結構的指針
- };
- /*
- * This little trick makes sure that
- * sizeof(flex_array) == PAGE_SIZE
- */
- char padding[FLEX_ARRAY_BASE_SIZE];
- };
- };
-
- // 其實struct flex_array_part *parts[];中的結構體只是一個數組而已
- struct flex_array_part {
- char elements[FLEX_ARRAY_PART_SIZE]; // 裏面是一個頁大小的字符數組
- };
-
- // 上面的字符數組中存放的就是流表項頭指針,流表項也是用雙鏈錶鏈接而成的
- //流表項結構體
- struct sw_flow {
- struct rcu_head rcu; // rcu保護機制
- struct hlist_node hash_node[2]; // 兩個節點指針,用來鏈接作用,前驅後繼指針
- u32 hash; // hash值
-
- struct sw_flow_key key; // 流表中的key值
- struct sw_flow_key unmasked_key; // 也是流表中的key
- struct sw_flow_mask *mask; // 要匹配的mask結構體
- struct sw_flow_actions __rcu *sf_acts; // 相應的action動作
-
- spinlock_t lock; // 保護機制自旋鎖
- unsigned long used; // 最後使用的時間
- u64 packet_count; // 匹配過的數據包數量
- u64 byte_count; // 匹配字節長度
- u8 tcp_flags; // TCP標識
- };
順序下來,應該輪到分析mask結構體鏈表了:
- // 這個mask比較簡單,就幾個關鍵成員
- struct sw_flow_mask {
- int ref_count;
- struct rcu_head rcu;
- struct list_head list;// mask鏈表元素,因爲mask結構是個雙鏈表結構體
- struct sw_flow_key_range range;// 操作範圍結構體,因爲key值中有些數據時不要用來匹配的
- struct sw_flow_key key;// 要和數據包操作的key,將要被用來匹配的key值
- };
-
- // key的匹配範圍,因爲key值中有一部分的數據時不用匹配的
- struct sw_flow_key_range {
- size_t start; // key值匹配數據開始部分
- size_t end; // key值匹配數據結束部分
- };
下面是整個openVswitch中數據結構所構成的圖示,也是整個openVswitch中主要結構:
轉載請註明原文出處,原文地址是:http://blog.csdn.net/yuzhihui_no1/article/details/39188373
如有不正確之處,望大家指正!謝謝!!!