天天看點

使用 netlink 進行使用者空間和核心空間資料互動--核心空間使用netlink 的核心空間 API 關鍵點

之前總結了 netlink 進行使用者空間和核心空間資料互動--使用者空間使用 的文章

netlink 進行使用者空間和核心空間資料互動--使用者空間使用_xuaiwai的部落格

本篇文章主要就在核心中使用netlink 做一個總結:

netlink 的核心空間 API 

核心空間的 netlink API 與應用程式之間的 API 之間有很多的不同, netlink 核心 API 在檔案 net/core/af_netlink.c 中實作。核心 netlink API 可以用于通路核心子產品的 netlink 套接字,并和使用者空間的應用程式進行通信。如果想添加使用者自己定義的協定類型,需要自己增加協定類型,如定義如下的協定類型:

#define NETLINK_TEST 27 //這個是自定義的類型,在使用者态建立NL socket時使用的協定一緻
           

1.netlink的核心套接字建立

在使用者層, socket ()函數用于建立 netlink 套接字,其中的協定類型應該也為 NETLINK_TEST 。核心空間建立此套接字的函數為:

/* net: net指向所在的網絡命名空間, 一般預設傳入的是&init_net(不需要定義);  定義在net_namespace.c(extern struct net init_net);
    unit:netlink協定類型
    cfg: cfg存放的是netlink核心配置參數
 */
static inline struct sock *netlink_kernel_create(
    struct net *net, int unit, struct netlink_kernel_cfg *cfg);
           

參數 unit ,是 netlink 協定類型,例如為 NETLINK_TEST

參數 cfg,存放的是netlink核心配置參數,包括主要的回調處理函數指針等,下面是netlink_kernel_cfg這個結構的定義。

/* optional Netlink kernel configurationparameters */
 struct netlink_kernel_cfg {
     unsigned int    groups; 
     unsigned int    flags; 
     void       (*input)(struct sk_buff *skb); /* input 回調函數,收到的消息在此函數中處理 */
     struct mutex    *cb_mutex;
     void       (*bind)(int group);
     bool       (*compare)(struct net *net, struct sock *sk);
 };
           

建立 netlink 套接字函數在成功的時候,傳回一個 structure sock 指針類型的值,之後可以用這個值對 netlink 套接字進行處理;當傳回值為 NULL 的時候,套接字建立失敗了,需要進行容錯的處理,通常進行資源的釋放等。

static struct netlink_kernel_cfg cfg = {
    .input = netlink_recv_msg,
};

struct sock *netlink_sk = netlink_kernel_create(
    &init_net, NETLINK_TEST, &cfg);
           

2. netlink 的應用層資料接收

當核心使用 netlink kernel_create (函數建立一個 NETLINK_TEST 類型的協定之後,當使用者空間向核心空間通過之前的 netlink 套接字發送消息的時候, cfg 中函數注冊的回調函數 input 會被調用,下面是一個 netlink_recv_msg 函數的實作代碼:

static void netlink_recv_msg(struct sk_buff *__skb)
{
    struct nlmsghdr *nlhdr = NULL;
    struct sk_buff *skb = NULL;
    u8 *payload = NULL;
    u32 user_pid = 0;
    if (NULL == __skb)
    {
        return;
    }

    skb = skb_get(__skb);
    if (NULL == skb)
    {
        return;
    }
    
    //資料長度大于頭長度
    if (skb->len >= NLMSG_SPACE(0))
    {
        //這個 user_pid 可以用來在核心回複消息時使用
        user_pid = nlhdr->nlmsg_pid;
        nlhdr = nlmsg_hdr(skb);
        switch (nlhdr->nlmsg_type)
        {
        case 1:
        {
            //取出負載資料
            payload = (u8 *)NLMSG_DATA(nlhdr);
            //處理 type 為 1的資料
            break;
        }
        defaule:
        break;
    }
    kfree_skb(skb);
}
           

當應用層的程序通過sendmsg()/sendto()函數發送資料的時候,如果 netlink_recv_msg 函數的處理速度足夠快,就不會對系統造成影響。當 netlink_recv_msg 函數的處理過程占用很長時間,需要将處理的代碼從netlink_recv_msg函數中移除,放到别的地方進行處理,防止系統調用在此處阻塞,别的系統不能進行調用。

可以使用核心線程來處理上述的功能,在此核心線程中使用 skb = skb_recv_datagrm (netlink_sk)函數來接收用戶端發送的資料,接收到的資料儲存在 skb->data 中。當使用 netlink kernel create ()函數建立的套接字 netlink_sk 沒有資料時,核心處理線程進入睡眠狀态,當有資料到來的時候需要将核心處理線程喚醒,進行接收和處理線程的工作。是以這種情況在 input()函數中,需要将核心線程喚醒。可以使用如下的代碼實作:

void input(struct sock * sk)
{
    wake_up_interruptible(sk->sleep);
}
           

3. netlink 的核心資料發送

netlink 在核心中發送資料與應用程式發送資料一樣,需要設定 netlink 的源位址和目的 netlink 位址。例如,需要發送的 netlink 消息資料在結構 struct sk_buf *skb 中

發送一個單點傳播消息,使用 netlink_unicase()函數,其原型如下:

int netlink_unicast ( struct sock * ssk , struct sk_buff * skb ,u32 pid , int nonblock );

其中的參數 ssk 由 netlink_kernel_create()函數傳回, skb->data 指向需要發送的 netlink 消息, pid 是應用層接收資料的 pid , nonblock 用于設定當接收緩沖區不可用的時候是阻塞等待直到緩沖區可用,還是直接傳回失敗。

發送多點傳播消息使用 netlink_broadcast()函數,它可以向 pid 指定的應用程式或者 goups 指定的群發送消息。原型定義如下:

void netlink_broadcast(struct sock * ssk , struct sk_buff * skb ,u32 pid ,u32 group , int allocation );

其中的參數 group 是 OR 運算組成的接收資料的多點傳播群 ID 号, alocation 是核心記憶體申請類型,例如 GFP_ATOMIC 在終端上下文中使用, GFP_KERNEL 在其他狀态下使用。要申請記憶體的原因是這個函數在發送消息資料的時候可能需要申請多個套接字緩沖區,用于複制多點傳播消息。

//給 user_pid 發送長度為data_len 的msg_data 類型為nlmsg_type
int netlink_send_msg(int user_pid, int nlmsg_type, u8 *msg_data, int data_len)
{
    struct sk_buff *skb = NULL;
    struct nlmsghdr *nlhdr = NULL;

    //配置設定一個sk_buff, 頭 + data_len
    skb = nlmsg_new(data_len, GFP_ATOMIC);
    if (NULL == skb)
    {
        printk(KERN_ERR "netlink_send_msg: alloc_skb Error.\n");
        return ret;
    }

    //頭指派
    nlhdr = nlmsg_put(skb, 0, 0, nlmsg_type, data_len, 0);
    if (NULL == nlhdr)
    {
        printk(KERN_ERR "netlink_send_msg: nlmsg_put Error.\n");
        nlmsg_free(skb);
        return ret;
    }

    //payload 指派
    memcpy(nlmsg_data(nlhdr), msg_data, data_len);

    //單點傳播 向 user_pid 發送資料 user_pid在接收的回調函數中可獲得
    ret = netlink_unicast(netlink_sk, skb, user_pid, MSG_DONTWAIT);
    if(0 != ret)
    {    
        nlmsg_free(skb);
    }
    return ret;
}
           

4. netlink 的套接字關閉

關閉 netlink 套接字,使用 sock_release()函數來進行。主要進行記憶體等資源的釋放,将一些指針進行重置的操作。函數的原型如下

void sock_release ( struct socket * sock );

netlink_kernel_create ()函數建立成功套接字的傳回值為 struct *sock , sock_release()函數釋放套接字時傳入的參數是 struct *socket 類型,類型 struct *socket 是 struct *sock 的一個成員,是以可以使用如下方式來釋放套接字:

 sock_release(nl_sk->socket);
           

關鍵點

1、在建立sock時需要和使用者态使用相同的協定類型,比如這裡使用的是 NETLINK_TEST;

2、接收到資料有需要擷取在頭中取去發送端設定的pid,在回複的時候才能正确發送資料給對端;

3、發送時也需要構造一個skb_buff用以發送。

凡是過往,即為序章  

繼續閱讀