天天看點

linux的netlink機

netlink作為一種使用者空間和核心空間通信的機制已經有一定年頭了,它不光為了核心和使用者通信,還可以作為IPC機制進行程序間通信。其實 netlink定義了一個架構,人們可以基于這個架構用它來做可以做的任何事情,linux中不乏這些類似的好的架構。它們的共同點就是核心并不管它們能 做什麼,然而它們真的很強大,往往可以做到的事情很多,這就是核心不問政策隻管實作機制,所有政策讓使用者實作,netlink架構就是用來傳遞資料的,内 核隻知道它可以傳遞資料而不知道為何要傳遞這些資料也不管這些資料是什麼。你甚至可以将它用于真正的網絡而不僅僅限于本機,這些都是可以的,它也用到了 sk_buff結構體,和網絡套接字一樣,更好的事情是它并沒有觸及sk_buff裡面的标準字段,而僅僅用了一個擴充的cb字段,cb在sk_buff 裡面的定義是char cb[40];在netlink子產品裡面NETLINK_CB宏就是取cb字段的,也就是netlink所用的私有字段,這樣的話你就可以用 netlink向任何執行實體傳輸任何資料了,不限于本機。

關于使用者空間的netlink套接字很簡單,就和傳統的網絡套接字一樣一樣的,隻不過修改了一些參數罷了。如下:

sd = socket(AF_NETLINK, SOCK_RAW,NETLINK_GENERIC);

就是建立一個使用者netlink套接字。之後的bind也是很簡單,注意資料結構的意義就是了。這裡就不說了,下面詳細說一下核心的netlink實作。核心裡面建立一個netlink套接字需要如下調用:

struct sock * netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len))

{

         struct socket *sock;

         struct sock *sk;

         if (unit<0 || unit>=MAX_LINKS)

                 return NULL;

         if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock))

         if (netlink_create(sock, unit) < 0) {

                 sock_release(sock);

                 return NULL;

         }

         sk = sock->sk;

         sk->sk_data_ready = netlink_data_ready;  //之是以将sk_data_ready設為新的函數而不用預設的是因為為了實作一些使用者政策,比如可以傳入自己的input函數,待到有資料的時候自行 處理。

         if (input)

                 nlk_sk(sk)->data_ready = input;

         netlink_insert(sk, 0);

         return sk;

}

注 意該函數的參數input是個回調函數,在有資料的時候核心會調用它。另外sk_data_ready回調函數是套接字标準中定義的,不管什麼套接字都有 sk_data_ready回調機制。在input中,你可以直接處理收到的資料,也可以不處理,在大量資料傳輸的情況下,在input中處理是不明智 的,正确的方式應該是建立一個核心線程專門接收資料,沒有資料的時候該核心線程睡眠,一旦有了資料,input回調函數喚醒這個核心線程就是了。

static void netlink_data_ready(struct sock *sk, int len)

         struct netlink_opt *nlk = nlk_sk(sk);

         if (nlk->data_ready)

                 nlk->data_ready(sk, len);  //這裡調用的回調函數就是核心netlink套接字建立的時候傳入的那個函數。

         netlink_rcv_wake(sk);    //告知别的程序該sock上剛完成了一次接收,可能會騰出地方以便接收新的skb

}

static inline void netlink_rcv_wake(struct sock *sk)

         if (!skb_queue_len(&sk->sk_receive_queue))

                 clear_bit(0, &nlk->state);

         if (!test_bit(0, &nlk->state))

                 wake_up_interruptible(&nlk->wait);  //喚醒可能等待發送的程序

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

         struct sock *sk;

         int err;

         long timeo;

         netlink_trim(skb, gfp_any());

         timeo = sock_sndtimeo(ssk, nonblock);

retry:

         sk = netlink_getsockbypid(ssk, pid);

...

         err = netlink_attachskb(sk, skb, nonblock, timeo);  //将sock和sk_buff綁定在一起,在netlink中套接字和skb的綁定與解綁定是很頻繁的。

         if (err == 1)

                 goto retry;

         if (err)

                 return err;

         return netlink_sendskb(sk, skb, ssk->sk_protocol);  //在套接字sk上傳輸這個skb,其實就是将這個skb排入了該sk的接收隊列的後頭。

int netlink_attachskb(struct sock *sk, struct sk_buff *skb, int nonblock, long timeo)

{//這個函數将一個sk_buff給了一個套接字sock,也就是skb與sock的綁定,在綁定之前有很多工作要做。

         struct netlink_opt *nlk;

         nlk = nlk_sk(sk);

         if (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf || test_bit(0, &nlk->state)) {

                 DECLARE_WAITQUEUE(wait, current);

                 __set_current_state(TASK_INTERRUPTIBLE);

                add_wait_queue(&nlk->wait, &wait);

                 if ((atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf || test_bit(0, &nlk->state)) &&

                     !sock_flag(sk, SOCK_DEAD)) //如果此時這個sock不能接受這個sk,那麼就要等待了,正好等在nlk->wait上,待到和該sock相關的程序在 netlink_rcv_wake中喚醒之,說明可以過繼skb了。

                         timeo = schedule_timeout(timeo);

                 __set_current_state(TASK_RUNNING);

                 remove_wait_queue(&nlk->wait, &wait);

                 sock_put(sk);

                 if (signal_pending(current)) {

                         kfree_skb(skb);

                         return sock_intr_errno(timeo);

                 }

                 return 1;

         skb_orphan(skb);

         skb_set_owner_r(skb, sk);   //該sock正式接受這個sk_buff

         return 0;

那 麼誰會調用netlink_attachskb呢?這是顯而易見的,在發送的時候,要把一個要發送的消息初始化成一個sk_buff結構體,但是這個 skb歸誰所有呢?确定綁定的主客雙方的過程就是綁定,也就是上面的函數做的事。在netlink的消息發送過程中的第一步就是sock和sk_buff 的綁定,于是調用上述綁定函數的就是netlink_sendmsg中調用的netlink_unicast,也就是單點傳播,單點傳播的意思就是隻發給一個 sock而不是多個,于是要做的就是找到接收此skb的sock,netlink_unicast的參數dst_pid為确定目标sock提供了方向,在 netlink_unicast中(見上)正是通過這個dst_pid找到了目标sock。

static int netlink_sendmsg(struct kiocb *kiocb, struct socket *sock, struct msghdr *msg, size_t len)

    err = netlink_unicast(sk, skb, dst_pid, msg->msg_flags&MSG_DONTWAIT);

out:

         return err;

發 送是一個主動的過程,是以需要主動尋找目标sock,然後把根據要發送的消息初始化好的sk_buff找機會排入目标sock的接收隊列就完事了,如此看 來的話,接收更是一個簡單的過程了,它隻要等着接收就可以了,純粹就是一個被動的過程了,在對應的netlink_recvmsg中循環接收,沒有資料時 就睡在一個sock->sk_sleep隊列上就是了,一旦有資料過來,該接收程序就被喚醒,具體過程就是,當發送方調用 netlink_sendmsg時,後者調用netlink_unicast,然後它進一步調用netlink_sendskb,這個 netlink_sendskb最後将sk_buff排入目标接收sock的接收隊列後調用目标sock的sk_data_ready,而這個 sk_data_ready對于使用者空間的netlink就是sock_def_readable,它會喚醒睡眠了sk->sk_sleep上的接 收sock程序,而對于核心netlink套接字,其sk_data_ready将會是netlink_data_ready,就是上面所說的那個函數。 這個netlink_data_ready裡面調用了一個程式設計者傳入核心的data_ready回調函數進而可以實作使用者政策,再次引入了機制和政策 的分離。在netlink_rcv_wake中會判斷目前接收隊列sk->sk_receive_queue是否已經空了,空了的話證明接收已經完 成,這種情況下就要喚醒等待排入隊列新skb的發送程序,也就是調用:

繼續閱讀