天天看點

一 netlink機制

一 概述        Linux提供了多種機制來完成核心空間與使用者空間之間的資料交換,分别有核心啟動參數、子產品參數、sysfs、sysctl、系統調用、procfs、seq_file、debugfs、relayfs。其中,子產品參數、sysfs、sysctl、procfs、seq_file、debugfs、relayfs是基于檔案系統的通信機制,用于核心空間向使用者空間輸出資訊;sysctl、系統調用是由使用者空間發起的通信機制。以上均為單工通信機制,在核心空間與使用者空間的雙向資料交換上略顯不足,由此引入了netlink機制。

       netlink是一種實作核心空間和使用者空間通信的程序間通信機制IPC,可以了解為一種特殊的socket,具有雙向全雙工異步傳輸的特點,能夠很好的滿足核心空間和使用者空間互動。它支援由核心态主動發起通信,核心為Netlink通信提供了一組特殊的API接口,使用者态則基于socket  API,核心發送的資料會儲存在接收程序socket 的接收緩存中,由接收程序處理。

netlink相對于其他的通信機制具有以下優點:

  •  使用netlink通過自定義一種新的協定并加入協定族,即可通過socket API使用netlink協定完成資料交換,而ioctl和proc檔案系統均需要通過程式加入相應的裝置或檔案。
  •  netlink使用socket緩沖隊列,是一種異步通信機制,而ioctl是同步通信,如果傳輸資料量較大會影響系統性能。
  •  netlink支援多點傳播,屬于一個netlink組的子產品和程序都能獲得該多點傳播消息。 即核心态可以将消息發送給多個接收程序,這樣就不用每個程序單獨來查詢了。
  •  netlink允許核心發起會話,而ioctl和系統調用隻能由使用者空間發起。
  • 雙向全雙工異步傳輸,支援由核心主動發起傳輸通信,如此使用者空間在等待核心某種觸發條件滿足時就無需不斷輪詢,而異步接收核心消息即可。

netlink架構框圖:

一 netlink機制

二 使用

1  使用者空間     使用者态使用标準的socket API如socket,bind,sendmsg,recvmsg和close等接口就能很容易地使用netlink socket。     1)、建立netlink socket             sock_fd = 

socket(AF_NETLINK/PF_NETLINK, int type, int protocol)

         其中,type可以取SOCK_RAW或者SOCK_DGRAM。                   protocol指定netlink協定類型。目前支援的協定類型定義如下: 目前 netlink 協定族支援32種協定類型,核心使用了21中,定義在include/uapi/linux/netlink.h: #define NETLINK_ROUTE       0   // Routing/device hook ,用于設定和查詢路由表等網絡核心子產品 #define NETLINK_UNUSED      1    #define NETLINK_USERSOCK    2   

#define NETLINK_FIREWALL    3   

#define NETLINK_SOCK_DIAG   4   

#define NETLINK_NFLOG       5   

#define NETLINK_XFRM        6   

#define NETLINK_SELINUX     7   

#define NETLINK_ISCSI       8   

#define NETLINK_AUDIT       9   

#define NETLINK_FIB_LOOKUP  10

#define NETLINK_CONNECTOR   11

#define NETLINK_NETFILTER   12  

#define NETLINK_IP6_FW      13 #define NETLINK_DNRTMSG     14  

#define NETLINK_KOBJECT_UEVENT  15  // Kernel messages to userspace,用于uevent消息通信 #define NETLINK_GENERIC     16

#define NETLINK_SCSITRANSPORT   18  

#define NETLINK_ECRYPTFS    19

#define NETLINK_RDMA        20

#define NETLINK_CRYPTO      21  

#define NETLINK_INET_DIAG   NETLINK_SOCK_DIAG

#define MAX_LINKS 32</span>                  2)、将netlink socket和程序綁定

      bind(sock_fd, (struct sockaddr*)&nl_addr, sizeof(&nl_addr));

           函數bind用于把一個打開的netlink socket與程序進行綁定,需要進行綁定的netlink socket位址結構如下: //netlink socket位址結構體 struct sockaddr_nl {      __kernel_sa_family_t    nl_family;      unsigned short    nl_pad;               __u32          nl_pid;               __u32          nl_groups; //multicast groups mask ,用于指定多點傳播組,每一個bit對應一個多點傳播組,如果設定為0,表示不加入任何多點傳播組 };

        3)、發送netlink消息        為了能夠把netlink消息發送給核心或者别的使用者程序,需要使用另外一個結構體struct sockaddr_nl作為目的位址。如果消息是發往核心的話,nl_pid和nl_groups都應該設定為0,;如果消息是發往另一個進 程,nl_pid應該設定為接受者程序的PID,nl_groups用于設定需要發往的多點傳播組。 填充好了目的位址後,就可以将netlink位址應用到結構體struct msghdr中,供函數sendmsg來調用: struct msghdr msg; msg.msg_name = (void *)&nladdr; msg.msg_namelen = sizeof(nladdr);

由于linux核心的netlink部分總是認為每個netlink消息體中已經包含了下面的消息頭,是以每個應用程式在發送netlink消息之前需要提供這個頭資訊: //netlink message header struct nlmsghdr {       __u32          nlmsg_len;      __u16          nlmsg_type;           __u16          nlmsg_flags;       __u32          nlmsg_seq;      __u32          nlmsg_pid; };

填充完消息頭後,在消息頭後面就可以填充消息體的内容了,填充完消息體,使用struct iovec結構體,使iov_base指向包含netlink消息的緩沖區,即可調用sendmsg接口發送netlink消息。struct iovec結構定義如下:

struct iovec {     void __user *iov_base;        __kernel_size_t iov_len;  };

發送netlink消息的代碼如下:

struct iovec iov; iov.iov_base = (void *)msg_buffer; iov.iov_len = nlh->nlmsg_len; msg.msg_iov = &iov; msg.msg_iovlen = 1; sendmsg(sock_fd, &msg, 0);

4)、接收消息 接收程式需要申請足夠大的空間來存儲netlink消息頭和消息的payload部分。用如下的方式填充結構體struct msghdr,然後調用recvmsg接口來接收netlink消息:

char msg_buffer[MSX_NL_MSG_LEN]; struct sockaddr_nl nladdr; struct msghdr msg; struct iovec iov; iov.iov_base = (void *)msg_buffer; iov.iov_len = MAX_NL_MSG_LEN; msg.msg_name = (void *)&(nladdr); msg.msg_namelen = sizeof(nladdr); msg.msg_iov = &iov; msg.msg_iovlen = 1; recvmsg(sock_fd, &msg, 0);

當消息正确接收後,msg_buffer裡儲存包含netlink消息頭的整個netlink消息,nladdr包含接收到的消息體的目的位址資訊。

5)、關閉netlink socket

使用完前面建立的netlink socket後,就可以使用close接口關閉netlink socket,釋放socket資源。關閉netlink socket的代碼與關閉其他socket一緻,代碼如下:

close(sock_fd);

     2 核心态使用netlink socket        核心空間的netlink API接口是由核心中的netlink核心代碼支援的,在net/core/af_netlink.c中實作。核心子產品通過這些API通路netlink socket并與使用者空間的程式進行通信。

 1) 添加自定義協定類型 核心已經支援的協定類型在linux/netlink.h中定義,如果要使用自定義的協定類型,需要在此檔案中新增一個協定類型,然後就可以在linux核心子產品中使用這個協定類型了。

2) 在核心建立netlink socket 在核心空間,通過如下接口建立netlink socket: struct sock *netlink_kernel_create(struct net *net,                      int unit,                      unsigned int groups,                      void (*input)(struct sk_buff *skb),                      struct mutex *cb_mutex, 

                    struct module *module);

參數net為網絡裝置名稱,一般傳入&init_net即可;unit為協定類型,傳入自定義的協定類型;groups指定netlink 消息有多少個組,一般情況下傳入0即可;input是用于netlink socket在收到消息時調用的處理消息的函數指針;cb_mutex是用于核心處理netlink socket消息時使用的互斥鎖,一般情況下傳入NULL即可;module指定建立的netlink socket所屬的核心子產品,一般情況下傳入THIS_MODULE。 在核心建立了netlink socket後,當使用者程式發送一個netlink消息到核心時,函數input都會被調用。下面是一個實作了消息處理函數input的例子: void input (struct sock *sk, int len) {    struct sk_buff *skb;    struct nlmsghdr *nlh = NULL;   u8 *payload = NULL;

while ((skb = skb_dequeue(&sk->receive_queue))!= NULL)   {            nlh = (struct nlmsghdr *)skb->data;      payload = NLMSG_DATA(nlh);          }

}

回調函數input是在使用者程序調用sendmsg系統調用時被調用的。是以該函數中的處理時間不能太長,否則會導緻其他系統調用被阻塞。比較好的做法是在該函數中建立一個新的核心線程來處理netlink socket消息。

3) 在核心中發送netlink消息           在核心空間發送netlink消息時有兩個接口可以使用,netlink_unicast接口用來發送一個單點傳播消息,其定義如下: int netlink_unicast(struct sock *ssk, struct sk_buff *skb,

            u32 pid, int nonblock)

ssk是調用netlink_kernel_create接口所建立的socket控制塊,skb中的data指向需要發送的netlink消息體,pid為要發往的使用者程序的PID,nonblock訓示當發送緩沖區不可用時嘗試發送的逾時時間。 netlink消息的源位址和目的位址可以通過如下代碼進行設定: NETLINK_CB(skb).groups = local_groups; NETLINK_CB(skb).pid = 0;    NETLINK_CB(skb).dst_groups = dst_groups;

NETLINK_CB(skb).dst_pid = dst_pid;

使用netlink_broadcast接口可以發送一個多點傳播消息,其定義如下: int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid,

              u32 group, gfp_t allocation)

其中group是接收消息的各個組的比特位進行與運算的結果。allocation是核心申請記憶體的類型,通常情況下在中斷上下文中使用GFP_ATOMIC,否則使用GFP_KERNEL。

4)在核心中關閉netlink socket 假設netlink_kernel_create函數傳回的netlink socket為struct sock *nl_sk,我們可以通過如下API來關閉這個netlink socket:

sock_release(nl_sk->socket);

二 netlink子系統初始化 核心Netlink的初始化在系統啟動階段完成,初始化代碼在af_netlink.c的netlink_proto_init()函數中,整個初始化流程如下:

一 netlink機制

netlink_table結構體: struct netlink_table {     struct rhashtable   hash;  //用來索引同種協定類型的不同netlink套接字執行個體     struct hlist_head   mc_list;//多點傳播使用的sock散清單     struct listeners __rcu  *listeners; //監聽者掩碼     unsigned int        flags;     unsigned int        groups; //協定支援最大多點傳播組數目     struct mutex        *cb_mutex;     struct module       *module;     int         (*bind)(struct net *net, int group);     void            (*unbind)(struct net *net, int group);     bool            (*compare)(struct net *net, struct sock *sock);     int         registered; };   netlink子系統初始化: static int __init netlink_proto_init(void) {     int i;     int err = proto_register(&netlink_proto, 0); //向核心注冊netlink協定

    if (err != 0)         goto out;

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

    nl_table = kcalloc(MAX_LINKS, sizeof(*nl_table), GFP_KERNEL);//建立并初始化netlink_table表數組nl_table,每種協定占nl_table表數組的一項     if (!nl_table)         goto panic;

    for (i = 0; i < MAX_LINKS; i++) {         if (rhashtable_init(&nl_table[i].hash,                     &netlink_rhashtable_params) < 0) {             while (--i > 0)                 rhashtable_destroy(&nl_table[i].hash);             kfree(nl_table);             goto panic;         }     }

    INIT_LIST_HEAD(&netlink_tap_all);

    netlink_add_usersock_entry();//初始化應用層使用的NETLINK_USERSOCK協定類型的netlink(用于應用層程序間通信)     sock_register(&netlink_family_ops);//向核心注冊協定處理函數,即将netlink的socket建立處理函數注冊到核心中,如此以後應用層建立netlink類型的socket時将會調用該協定處理函數,     register_pernet_subsys(&netlink_net_ops);//向核心所有的網絡命名空間注冊”子系統“的初始化和去初始化函數,這裡的"子系統”并非指的是netlink子系統,而是一種通用的處理方式,在網絡命名空間建立和登出時會調用這裡注冊的初始化和去初始化函數(當然對于已經存在的網絡命名空間,在注冊的過程中也會調用其初始化函數),後文中建立各種協定類型的netlink也是通過這種方式實作的。          rtnetlink_init();//建立NETLINK_ROUTE協定類型的netlink out:     return err; panic:     panic("netlink_init: Cannot allocate nl_table\n"); }

core_initcall(netlink_proto_init);

上一篇: netlink使用
下一篇: NETLINK

繼續閱讀