OpenvSwitch 解讀

OVS 核心代碼

  • datapath 目錄
  • ovs-switchd
  • OVS數據庫管理
  • ofproto

OVS 架構


OVS 主要的數據結構


數據結構關係圖

主要的數據結構和數據結構的參數

數據結構代碼

vvport

/**
 - struct vport - one port within a datapath
 - @rcu: RCU callback head for deferred destruction.
 - @dp: Datapath to which this port belongs.
 - @upcall_portids: RCU protected 'struct vport_portids'.
 - @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
 - @err_stats: Points to error statistics used and maintained by 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_parms

/**
 - struct vport_parms - parameters for creating a new vport
 *
 - @name: New vport's name.
 - @type: New vport's type.
 - @options: %OVS_VPORT_ATTR_OPTIONS attribute from Netlink message, %NULL if
 - none was supplied.
 - @dp: New vport's datapath.
 - @port_no: New vport's port number.
 */
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_ops

/**
 - struct vport_ops - definition of a type of virtual port
 *
 - @type: %OVS_VPORT_TYPE_* value for this type of virtual port.
 - @create: Create a new vport configured as specified.  On success returns
 - a new vport allocated with ovs_vport_alloc(), otherwise an ERR_PTR() value.
 - @destroy: Destroys a vport.  Must call vport_free() on the vport but not
 - before an RCU grace period has elapsed.
 - @set_options: Modify the configuration of an existing vport.  May be %NULL
 - if modification is not supported.
 - @get_options: Appends vport-specific attributes for the configuration of an
 - existing vport to a &struct sk_buff.  May be %NULL for a vport that does not
 - have any configuration.
 - @get_name: Get the device's name.
 - @send: Send a packet on the device.  Returns the length of the packet sent,
 - zero for dropped packets or negative for error.
 - @get_egress_tun_info: Get the egress tunnel 5-tuple and other info for
 - a packet.
 */

 struct vport_ops {  
    enum ovs_vport_type type; // 端口的類型  

    // 新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 *); //
    int (*send)(struct vport *, struct sk_buff *); // 發送數據包到設備上  
};  

vport_ops_list

/* List of statically compiled vport implementations.  Don't forget to also
 - add yours to the list at the bottom of vport.h.
 */
static const struct vport_ops *vport_ops_list[] = {  
    &ovs_netdev_vport_ops,
    &ovs_internal_vport_ops,
    &ovs_geneve_vport_ops,
#if IS_ENABLED(CONFIG_NET_IPGRE_DEMUX)
    &ovs_gre_vport_ops,
    &ovs_gre64_vport_ops,
#endif
    &ovs_vxlan_vport_ops,
    &ovs_lisp_vport_ops,
};

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  
};  

sw_flow_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;  
    };  
};  

flow_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; //是否保留流表項  
};  

};html

sw_flow

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標識  
};    

sw_flow_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值  
};  

datapath 模塊


datapath 簡介

datapath爲 ovs內核模塊,負責執行數據交換,也就是把從接收端口收到的數據包在流表中進行匹配,並執行匹配到的動做。node

一個datapath能夠對應多個vport,一個vport相似物理交換機的端口概念。一個datapth關聯一個flow table,一個flow table包含多個條目,每一個條目包括兩個內容:一個match/key和一個action
linux

datapath 代碼

  • dp_init()
  • ovs_dp_process_received_packet()

dp_init 代碼

static int __init dp_init(void)
{
    int err;

    BUILD_BUG_ON(sizeof(struct ovs_skb_cb) > FIELD_SIZEOF(struct sk_buff, cb));

    pr_info("Open vSwitch switching datapath %s, built "__DATE__" "__TIME__"\n",
        VERSION);
    err = ovs_flow_init();//申請 flow_cache和 flow_stats_cache
    if (err)
        goto error;

    err = ovs_vport_init();//vport 數據結構初始化,申請 dev_table
    if (err)
        goto error_flow_exit;

    err = register_pernet_device(&ovs_net_ops);//註冊網絡名字空間設備
    if (err)
        goto error_vport_exit;

    err = register_netdevice_notifier(&ovs_dp_device_notifier);//註冊設備通知事件
    if (err)
        goto error_netns_exit;

    err = dp_register_genl();//dp_register_genl 初始化 dp 相關的 netlink 的 family和ops
    if (err < 0)
        goto error_unreg_notifier;

    return 0;

error_unreg_notifier:  
    unregister_netdevice_notifier(&ovs_dp_device_notifier);
error_netns_exit:  
    unregister_pernet_device(&ovs_net_ops);
error_vport_exit:  
    ovs_vport_exit();
error_flow_exit:  
    ovs_flow_exit();
error:  
    return err;
}

vswitchd 模塊


vswitchd 代碼

set_program_name(argv)

設置程序名稱、版本、編譯日期等信息算法

proctitle_int(argh,argv)

複製出輸入的參數列表到新的存儲中,讓argv指向這塊內存【主要是爲了後面的proctitle_set()函數準備】數據庫

service_start(&argc,&argv)

註冊回調和服務管理器出現故障錯誤時操做的配置api

remote=parse_options(argh,argv,&unixctl_path)

解析參數,其中unixctl_path存儲unixctrl域的sock名,做爲接受外部控制命令的渠道;而remote存儲鏈接到ovsdb的信息,即鏈接到配置數據庫的sock名數組

ovsrec_init()

數據表結構初始化,包括13張數據表安全

daemonize_start()

若是系統守護進程被配置了,啓動系統守護進程,經過派生和在返回的子進程。父進程徘徊,直到
 讓子進程知道它完成啓動成功(經過調用daemon_complete()),或者它沒有啓動(用非零退出
 退出代碼。服務器

unixctl_server_create(unixctl_path,&unixctl)

建立一個unixctl_server(存放在unixctl),並監聽在unixctl_path指定的punix路徑,該路徑做爲ovs-appctl發送命令給ovsd的通道網絡

unixctl_command_register

註冊unixctl命令

bridge_init

從remote數據庫獲取配置信息,並初始化bridge

主循環
memory_run()

運行內存監視器,客戶端調用memory_should_report()。此函數以及該模塊的接口的剩餘部分,僅被一個線程調用。

bridge_run

主要對網包進行完整處理過程。包括完成必要的配置更新【在配置更新中會從數據庫中讀取配置信息,生成必要的bridge和dp等數據結構】

ovsdb_idl_run(idl);

處理了一批從'IDL'數據庫服務器的消息。這可能會致使IDL的內容發生變化。客戶端能夠檢查與ovsdb_idl_get_seqno()。

system_stats_enable(false);

由於咱們不運行system_stats的run()在這個進程中有多個OVS-vswitchd守護進程的現狀,關閉系統自動統計信息收集。

bridge_init_ofproto(cfg)

初始化ofproto庫。這僅須要執行一次,但配置設置以後它必需要作的。若是已經出現了初始化,bridge_init_ofproto()當即返回。

bridge_run__(void)
ofproto_run中的p->ofproto_class->run(p)上的run函數依次調用函數
  • 必選調用dpif_run()處理全部註冊的netlink notifier的彙報事件
  • 必選調用run_fast()處理常見的週期事件,包括對upcalls的處理等
  • 可選調用netflow_run()和sflow_run(),進行對netflow和sflow的支持

可選調用較多,自行查看

connmr_run函數處理與控制器的週期性交互
  • 首先檢查是否存在in_band的控制器
  • 調用ofconn_run()處理對ofproto的協議解析和行動
  • rconn_run(ofconn->rconn)負責鏈接到controller
  • rconn_recv(ofconn->rconn)負責從controller收取消息
  • handle_openflow()最終調用handle_openflow__()(ofproto/ofproto.c)來完成對各個Of消息的處理
以 PACKET_OUT消息爲例,調用的是handle_packout 函數
首先調用ofputil_decode_packet_out()對of消息進行解析
調用ofconn_pktbuf_retrieve()獲取payload信息
利用ofproto_class->packet_out()將網包發出
packet_out()
{    
    ofproto_dpif_execute_actions()
        {
        dpif_flow_stats_extract() 流狀態提取
        xlate_actions()將ofpacts轉化爲dp的行動格式odp_actions
        調用dpif_execute()函數讓dpif執行給定的action構建OVS_PACKET_CMD_EXECUTE netlink消息併發給datapath
        datapath中將對應調用ovs_packet_cmd_execute函數處理收到的nlmsg
        ovs_packet_cmd_execute的調用過程
        ovs_packet_cmd_execute()->ovs_execute_actions()->do_execute_actions()
        }
}
從新配置SSL

經過主循環每一次遍歷,而不是隻當數據庫的變化,由於密鑰和證書文件的內容能夠更改在數據庫不更改中。咱們完成這些在bridge_reconfigure()以前,由於該功能可能會啓動SSL鏈接以前作到這一點,所以須要SSL進行配置。

對all_bidge上的每一個bridge的ofproto執行ofproto_run()
netdev_run()

若是打開了一些netted,則執行對應在netdev_classes上定義的每一個netdev_class實體,調用它們的run()包括處理網卡註冊的各個通知事件,獲取網卡的最新的信息等

unixctl_server_run(unixctl)

從unixctl指定的server中獲取來自ovs-appctl發出的命令數據,並執行對應的命令

循環等待事件處理

包括memory、bridge、unixctl_server、netted等事件,被poll_fd_wait()註冊的最短期

poll_block(void)

阻塞知道以前被poll_fd_wait()註冊過的事件發生,或者等待時間超過poll_timer_wait()註冊的最短期

清理工做

退出bridge,關閉unixctl鏈接

動態過程分析


數據流流向


通常的數據包在 Linux網絡協議中的流向爲黑色箭頭流向:網卡收到數據包後層層網上分析,最後離開內核態,把數據傳送到用戶態。
有 OVS時:數據流流向不一樣
(1)創網橋(ovs-vsctl add-br br0)
(2)綁網卡(ovs-vsctl add-port bro eth0 默認爲 eth0)
數據流:

從網卡 eth0到 ovs 的 vport 進入OVS,根據 key值流表匹配

成功——>執行流表 action
失敗——>upcall處理

添加網橋


1. 鍵入命令ovs-vsctl add-br testBR
2. 內核中的 openvswitch.ko 收到一個添加網橋的命令時候——即收到 OVS_DATAPATH_FAMILY通道的 OVS_DP_CMD_NEW命令。該命令綁定的回調函數爲 ovs_dp_cmd_new
3. ovs_dp_cmd_new 函數除了初始化 dp 結構外,調用 new_vport 函數來生成新的 vport
4. new_vport 函數調用 ovs_vport_add()來嘗試生成一個新的 vport
5. ovs_vport_add()函數會檢查 vport 類型(經過 vport_ops_list[]數組),並調用相關的 create()函數來生成 vport 結構
6. 當dp是網絡設備時(vport_netdev.c),最終由 ovs_vport_add()函數調用的是 netdev_create()【在 vport_ops_list的ovs_netdev_ops 中】
7. netdev_create()函數最關鍵的一步是註冊了收到網包時的回調函數
8. err=netdev_rx_handler_register(netdev_vport->dev,netdev_frame_hook,vport);
9. 操做是將 netdev_vport->dev 收到網包時的相關數據由 netdev_frame_hook()函數來處理,都是些輔助處理,依次調用各處理函數,在 netdev_port_receive()【這裏會進行數據包的拷貝,避免損壞】進入 ovs_vport_receive()回到 vport.c,從 ovs_dp_process_receive_packet()回到 datapath.c,進行統一處理
10. 流程:netdev_frame_hook()->netdev_port_receive->ovs_vport_receive->ovs_dp_process_received_packet()
11. net_port_receive()首先檢測是否 skb 被共享,如果則獲得 packet 的拷貝。
12. net_port_receive()其調用ovs_vport_receive(),檢查包的校驗和,而後交付給咱們的vport通用層來處理。
netdev_rx_handler_register()
linux 內核實現的一個函數,爲網絡設備 dev 註冊一個handler_frame_hook,rx_handle_data 指向的是handler_frame_hook 內存的區域,這個 handler 之後會被__netif_receive_skb()呼叫,就是說netdev_rx_handler_register(netdev_vport->dev,netdev_frame_hook,vport);在收到packet 後會調用 netdev_frame_hook 函數處理

收包處理


1.ovs_vport_receive_packets()調用ovs_flow_extract基於skb生成key值,並檢查是否有錯,而後調用ovs_dp_process_packet。交付給datapath處理
2.ovs_flow_tbl_lookup_stats。基於前面生成的key值進行流表查找,返回匹配的流表項,結構爲sw_flow。 
3.若不存在匹配,則調用ovs_dp_upcall上傳至userspace進行匹配。 (包括包和key都要上傳) 
若存在匹配,則直接調用ovs_execute_actions執行對應的action,好比添加vlan頭,轉發到某個port等。

流表匹配


1. flow_lookup()查找對應的流表項
2. for 循環調用 rcu_dereference_ovs 對流表結構體中的 mask_list 成員遍歷,找到對應的的 成員
3. flow=masked_flow_lookup()遍歷進行下一級 hmap查找,找到爲止
4. 進入 包含函數 ovs_flow_mask_key(&masked_key,unmasked,mask),將最開始提取的 Key 值和 mask 的 key 值進行「與」操做,結果存放在 masked_key 中,用來獲得後面的 Hash 值
5. hash=flow_hash(&masked_key,key_start,key_end)key 值的匹配字段只有部分
6. ovs_vport_add()函數會檢查 vport 類型(經過 vport_ops_list[]數組),並調用相關的 create()函數來生成 vport 結構
7. 可見,當 dp 時網絡設備時(vport_netdev.c),最終由 ovs_vport_add()函數調用的是 netdev_create()【在 vport_ops_list的ovs_netdev_ops 中】
8. netdev_vport->dev 收到網包時的相關數據由 netdev_frame_hook()函數來處理,都是些輔助處理,依次調用各處理函數,在 netdev_port_receive()【這裏會進行數據包的拷貝,避免損壞】進入 ovs_vport_receive()回到 vport.c,從 ovs_dp_process_receive_packet()回到 datapath.c,進行統一處理

upcall 消息處理


1. ovs_dp_upcall()首先調用 err=queue_userspace_packet()將信息排隊發到用戶空間去
2. dp_ifindex=get_dpifindex(dp)獲取網卡設備索引號
3. 調整 VLAN的 MAC 地址頭指針
4. 網絡鏈路屬性,若是不須要填充則調用此函數
5. len=upcall_msg_size(),得到 upcall 發送消息的大小
6. user_skb=genlmsg_new_unicast,建立一個新的 netlink 消息
7. upcall=genlmsg_put()增長一個新的 netlink 消息到 skb
8. err=genlmsg_unicast(),發送消息到用戶空間去處理

相關內容


Linux RCU鎖機制分析


RCU是linux的新型鎖機制(RCU是在linux 2.6內核版本中開始正式使用)

傳統讀寫鎖rwlock運行機制

讀鎖(共享鎖):若請求是讀數據時,上讀鎖,多個讀鎖不排斥(即訪問數據的讀者上限未達到時,能夠對數據區再上讀鎖),若請求是寫數據時,不能立刻上寫鎖,得等數據區的全部鎖(包括讀鎖和寫鎖)都釋放才能上寫鎖

寫鎖(獨佔鎖):要操做的數據區上了寫鎖,無論什麼請求都要等到數據區的寫鎖釋放掉後才能上鎖訪問

RCU 鎖機制——RCU(readcopyupdate)對數據的讀、複製、修改的保護鎖機制

寫數據:(1)不需讀寫鎖那樣等待全部鎖釋放【拷貝一份數據區的副本,在副本中修改,修改完後,用副本替代原來的數據區】(2)替換的時候須要讀寫鎖上寫鎖那樣,等到數據區上全部訪問者退出後,才進行數據的替換
(3)RCU鎖能夠有多個寫者,拷貝多份數據區數據,修改後,各個數據區陸續替換掉原數據區內容
讀數據:不用上任何鎖,幾乎不須要等待(讀寫鎖須要等寫鎖釋放)就能夠直接訪問數據
,「幾乎」,由於寫數據中替換原數據,只需修改個指針,消耗的時間幾乎不算

RCU 鎖機制特性

• 容許多個讀者和多個寫者同時訪問共享數據區內容
• 對多讀少寫的數據來講很是高效,能夠減小 CPU 開銷
• 寫數據操做多了,就不如讀寫鎖那麼好了,由於RCU 對寫數據開銷大,須要拷貝數據,修改,等待替換

RCU 機制 API

rcu_read_lock();
• 這不是和上讀寫鎖的那種上鎖,這僅僅只是標識了臨界區的開始位置。代表在臨界區內不能阻塞和休眠,也不能讓寫者進行數據的替換(其實這功能遠不止這些)。rcu _read_unlock()則是和上面rcu_read_lock()對應的,用來界定一個臨界區(就是要用鎖保護起來的數據區)。
synchronize_rcu();
• 當該函數被一個CPU調用時(通常是有寫者替換數據時調用),而其餘的CPU都在RCU保護的臨界區讀數據,那麼synchronize_rcu()將會保證阻塞寫者,直到全部其它讀數據的CPU都退出臨界區時,才停止阻塞,讓寫着開始替換數據。該函數做用就是保證在替換數據前,全部讀數據的CPU可以安全的退出臨界區。一樣,還有個call_rcu()函數功能也是相似的。若是call_rcu()被一個CPU調用,而其餘的CPU都在RCU保護的臨界區內讀數據,相應的RCU回調的調用將被推遲到其餘讀臨界區數據的CPU所有安全退出後才執行(能夠看linux內核源文件的註釋,在Rcupdate.h文件中rcu_read_look()函數前面的註釋)。
rcu_dereference(); 
• 獲取在一個RCU保護的指針,指向RCU讀端臨界區。他的指針之後可能會被安全地解除引用。說到底就是一個RCU保護指針。
list_add_rcu();
• 往RCU保護的數據結構中添加一個數據節點進去。這個和通常的往鏈表中增長一個節點操做是相似的,惟一不一樣的是多了這條代碼:rcu_assign_pointer(prev->next, new); 代碼大概含義:分配指向一個新初始化的結構指針,將由RCU讀端臨界區被解除引用,返回指定的值。
list_for_each_entry_rcu();
• 這是個遍歷RCU鏈表的操做,和通常的鏈表遍歷差很少。不一樣點就是必需要進入RCU保護的CPU(即:調用了rcu_read_lock()函數的CPU)才能調用這個操做,能夠和其餘CPU共同遍歷這個RCU鏈表。

Generic Netlink 通訊機制


+---------------------+      +---------------------+
      | (3) application "A" |      | (3) application "B" |
      +------+--------------+      +--------------+------+
             |                                    |
             \                                    /
              \                                  /
               |                                |
       +-------+--------------------------------+-------+
       |        :                               :       |   user-space
  =====+        :   (5)  Kernel socket API      :       +================
       |        :                               :       |   kernel-space
       +--------+-------------------------------+-------+
                |                               |
          +-----+-------------------------------+----+
          |        (1)  Netlink subsystem            |
          +---------------------+--------------------+
                                |
          +---------------------+--------------------+
          |       (2) Generic Netlink bus            |
          +--+--------------------------+-------+----+
             |                          |       |
     +-------+---------+                |       |
     |  (4) Controller |               /         \
     +-----------------+              /           \
                                      |           |
                   +------------------+--+     +--+------------------+
                   | (3) kernel user "X" |     | (3) kernel user "Y" |
                   +---------------------+     +---------------------+

(5)API向用戶空間和內核空間分別提供接口。
Netlink子系統(1)是全部genl通訊的基礎。Netlink子系統中收到的全部Generic類型的netlink數據都被送到genl總線(2)上;從內核發出的數據也經由genl總線送至netlink子系統,再打包送至用戶空間。
Generic Netlink控制器(4)做爲內核的一部分,負責動態地分配genl通道(即genl family id),並管理genl任務。genl控制器是一個特殊的genl內核用戶,它負責監聽genl bus上的通訊通道。genl通訊創建在一系列的通訊通道的基礎上,每一個genl family對應多個通道,這些通道由genl控制器動態分配。

Generic Netlink相關結構體

genl family

Generic Netlink是基於客戶端-服務端模型的通訊機制。服務端註冊family(family是對genl服務的各項定義的集合)。控制器和客戶端都經過已註冊的信息與服務端通訊。
genl family的結構體以下:

struct genl_family
{
      unsigned int            id;
      unsigned int            hdrsize;
      char                    name[GENL_NAMSIZ];
      unsigned int            version;
      unsigned int            maxattr;
      struct nlattr **        attrbuf;
      struct list_head        ops_list;
      struct list_head        family_list;
};
  • id: family id。當新註冊一個family的時候,應該用GENL_ID_GENERATE宏(0x0),表示請控制器自動爲family分配的一個id。0x10保留供genl控制器使用。
  • hdrsize: 用戶自定議頭部長度。即圖2中User Msg的長度。若是沒有用戶自定義頭部,這個值被賦爲0。
  • version: 版本號,通常填1便可。
  • name: family名,要求不一樣的family使用不一樣的名字。以便控制器進行正確的查找。
  • maxattr:genl使用netlink標準的attr來傳輸數據。此字段定義了最大attr類型數。(注意:不是一次傳輸多少個attr,而是一共有多少種attr,所以,這個值能夠被設爲0,爲0表明不區分所收到的數據的attr type)。在接收數據時,能夠根據attr type,得到指定的attr type的數據在總體數據中的位置。
  • struct nlattr **attrbuf
  • struct list_head ops_list
  • struct list_head family_list

以上的三個字段爲私有字段,由系統自動配置,開發者不須要作配置。

genl 報文格式

genl_ops 結構體
struct genl_ops
{
      u8                      cmd;
      unsigned int            flags;
      struct nla_policy       *policy;
      int                     (*doit)(struct sk_buff *skb,
                                      struct genl_info *info);
      int                     (*dumpit)(struct sk_buff *skb,
                                          struct netlink_callback *cb);
      struct list_head        ops_list;
};

cmd: 命令名。用於識別各genl_ops
flag: 各類設置屬性,以「或」鏈接。在須要admin特權級別時,使用GENL_ADMIN_PERM
policy:定義了attr規則。若是此指針非空,genl在觸發事件處理程序以前,會使用這個字段來對幀中的attr作校驗(見nlmsg_parse函數)。該字段能夠爲空,表示在觸發事件處理程序以前,不作校驗。

doit:這是一個回調函數。在generic netlink收到數據時觸發,運行在進程上下文。
doit傳入兩個參數,skb爲觸發此回調函數的socket buffer。第二個參數是一個genl_info結構體

struct genl_info
        {
             u32                     snd_seq;
             u32                     snd_pid;
             struct nlmsghdr *       nlhdr;
             struct genlmsghdr *     genlhdr;
             void *                  userhdr;
             struct nlattr **        attrs;
        };
  • snd_seq:發送序號
  • snd_pid:發送客戶端的PID
  • nlhdr:netlink header的指針
  • genlmsghdr:genl頭部的指針(即family頭部)
  • userhdr:用戶自定義頭部指針
  • attrs:attrs,若是定義了genl_ops->policy,這裏的attrs是被policy過濾之後的結果。在完成了操做之後,若是執行正確,返回0;不然,返回一個負數。負數的返回值會觸發NLMSG_ERROR消息。當genl_ops的flag標誌被添加了NLMSG_ERROR時,即便doit返回0,也會觸發NLMSG_ERROR消息。

dumpit
這是一個回調函數,當genl_ops的flag標誌被添加了NLM_F_DUMP之後,每次收到genl消息即會回觸發這個函數。dumpit與doit的區別是:dumpit的第一個參數skb不會攜帶從客戶端發來的數據。相反地,開發者應該在skb中填入須要傳給客戶端的數據,而後,並skb的數據長度(能夠用skb->len)return。skb中攜帶的數據會被自動送到客戶端。只要dumpit的返回值大於0,dumpit函數就會再次被調用,並被要求在skb中填入數據。當服務端沒有數據要傳給客戶端時,dumpit要返回0。若是函數中出錯,要求返回一個負值。關於doit和dumpit的觸發過程,能夠查看源碼中的genl_rcv_msg函數。

ops_list
爲私有字段,由系統自動配置,開發者不須要作配置。

Generic Netlink 服務端(內核)初始化

初始化Generic Netlink的過程分爲如下四步:定義family,定義operation,註冊family,註冊operation。
Datapath使用 generic netlink

在 dp_init()函數(datapath.c)中,調用 dp_register_genl()完成對四種類型的 family 以 及相應操做的註冊,包括 datapath、vport、flow 和 packet。前三種 family,都對應四種操 做都包括 NEW、DEL、GET、SET,而 packet 的操做僅爲 EXECUTE。
這些 family 和操做的定義均在 datapath.c 中。 以 flow family 爲例。代碼爲

static const struct nla_policy flow_policy[OVS_FLOW_ATTR_MAX + 1] = { 
    [OVS_FLOW_ATTR_KEY] = { .type = NLA_NESTED }, 
    [OVS_FLOW_ATTR_ACTIONS] = { .type = NLA_NESTED }, 
    [OVS_FLOW_ATTR_CLEAR] = { .type = NLA_FLAG },
};
static struct genl_family dp_flow_genl_family = { 
    .id = GENL_ID_GENERATE,
    .hdrsize = sizeof(struct ovs_header),
    .name = OVS_FLOW_FAMILY,
    .version = OVS_FLOW_VERSION,
    .maxattr = OVS_FLOW_ATTR_MAX, SET_NETNSOK
};

綁定的 ops 的定義

static struct genl_ops dp_flow_genl_ops[] = { 
    { .cmd = OVS_FLOW_CMD_NEW,
      .flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */  
      .policy = flow_policy,
      .doit = ovs_flow_cmd_new_or_set
    },
    { .cmd = OVS_FLOW_CMD_DEL,
      .flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */ 
      .policy = flow_policy,
      .doit = ovs_flow_cmd_del 
    },
    { .cmd = OVS_FLOW_CMD_GET,
      .flags = 0, /* OK for unprivileged users. */ 
      .policy = flow_policy,
      .doit = ovs_flow_cmd_get,
      .dumpit = ovs_flow_cmd_dump
    },
    { .cmd = OVS_FLOW_CMD_SET,
      .flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */ 
      .policy = flow_policy,
      .doit = ovs_flow_cmd_new_or_set,
    }, 
};

ovsd 使用 netlink
ovsd 對於 netlink 的實現,主要在 lib/netlink-socket.c 文件中。而對這些 netlink 操做的 調用,主要在 lib/dpif-linux.c 文件(以 dpif_linux_class 爲例)中對於各個行爲的處理,各 種可能的消息類型在 datapath 模塊中事先進行了內核註冊。
datapath 中對 netlink family 類型進行了註冊,ovsd 在使用這些 netlink family 以前須要 獲取它們的信息,這一過程主要在 lib/dpif-linux.c 文件(以 dpif_linux_class 爲例), dpif_linux_init()函數。代碼爲

static int dpif_linux_init(void) 
{
    static int error = -1;
    if (error < 0) {
        unsigned int ovs_vport_mcgroup;
        error = nl_lookup_genl_family(OVS_DATAPATH_FAMILY,&ovs_datapath_family);
        if (error) {
            VLOG_ERR("Generic Netlink family '%s' does not exist. ""The Open vSwitch kernel module is probably not loaded.",OVS_DATAPATH_FAMILY); }
        if (!error) {
            error = nl_lookup_genl_family(OVS_VPORT_FAMILY, &ovs_vport_family);}
        if (!error) {
            error = nl_lookup_genl_family(OVS_FLOW_FAMILY, &ovs_flow_family); }
        if (!error) {
            error = nl_lookup_genl_family(OVS_PACKET_FAMILY,&ovs_packet_family);}
        if (!error) {
            error = nl_sock_create(NETLINK_GENERIC, &genl_sock); }
        if (!error) {
            error = nl_lookup_genl_mcgroup(OVS_VPORT_FAMILY, OVS_VPORT_MCGROUP,&ovs_vport_mcgroup, OVS_VPORT_MCGROUP_FALLBACK_ID);}
        if (!error) {
            static struct dpif_linux_vport vport;
            nln = nln_create(NETLINK_GENERIC, ovs_vport_mcgroup,
            dpif_linux_nln_parse, &vport);}
     }
    return error; 
}

完成這些查找後,ovsd 便可利用 dpif 中的 api,經過發出這些 netlink 消息給 datapath 實現對 datapath 的操做。

相關的中間層 API 定義主要在 dpif_class(位於 lib/dpif-provider.h)的抽象類型中
dpif_class結構體的註釋:

/* Datapath interface class structure, to be defined by each implementation of
 -a datapath interface.
 *
 - These functions return 0 if successful or a positive errno value on failure,
 - except where otherwise noted.
 *
 - These functions are expected to execute synchronously, that is, to block as
 - necessary to obtain a result.  Thus, they may not return EAGAIN or
 - EWOULDBLOCK or EINPROGRESS.  We may relax this requirement in the future if
 - and when we encounter performance problems. */

一共有兩種dpif_class實例化類型,分別爲dpif_netlink_class和dpif_netdev_class。dpif_netlink_class表示的是經過netlink和本地的datapath通訊,而dpif_netdev_class經過網絡協議和遠程的datapath通訊
ovsd使用netlink進行消息發送的過程:

參考內容


庾志輝OVS 專欄
http://blog.csdn.net/column/d...
datapath 模塊分析
http://vinllen.com/ovs-datapa...
Baohua Yang的OpenvSwitch 代碼分析
OpenvSwitch2.4.0源碼解讀
http://www.cnblogs.com/cotyb/...
GenerRic Netlink 詳解
http://www.tuicool.com/articl...

相關文章
相關標籤/搜索