2017-07-05
本節從一個小案例入手,結合源碼分析下通過netlink進行內核和用戶通信的流程。
內核端
按照傳統CS模式,其實內核端可以作為是服務器端,用以接收用戶的請求並作出處理,但是從netlink本身的特性,其更像是一個對等實體。雙方都可以進行主動數據的傳遞。
內核中首先調用netlink_kernel_create函數創建一個sock結構,其實這里僅僅是返回一個sock結構,而其中創建了相關的socket,netlink_sock,inode等。

老版本的函數參數都是寫在netlink_kernel_create函數里的,現在把部分參數抽離到一個netlink_kernel_cfg結構中,其中我們只關心接收數據的處理函數.input。首個參數表示網絡命名空間,一般取init_net,NETLINK_TEST是我們自定義的協議類型,netlink支持32個協議0-31,其實就是協商好的一個數字,用來用戶層和內核層的通信。第三個就是我們的配置結構。函數內部首先創建了socket,然后創建了sock,並在socket和sock間建立關聯,默認創建sock是在全局的命名空間init_net下,所以還需要修改到參數中的net,不過一般我們也正是選擇全局的。然后需要設置接收函數到netlink_sock中的netlink_rcv字段。最后需要把sock加入到全局管理結構nl_table中。函數大致功能如此,后面我們再詳細討論。
目前sock已經注冊上,如何處理接收到的數據呢?看下我們實現的簡單的接收函數rece_msg,

Netlink基於socket,所以其數據是通過套接字緩沖區sk_buff管理的。從skb中獲取信息長度,該長度是包含了nlmsghdr的長度加上數據,后面為了方便直接讀取的100字節,實際上應該讓獲取到的長度將去nlmsghdr的長度。操作完畢需要調用skb_pull減去已經讀取到的長度。后面沒獲取一次請求我們就調用了發送函數向用戶空間發送一個消息,pid是nlmsghdr頭部中的nlmsg_pid,表示發送者的端口。看下發送函數sendmsg

由於是在內核,有些事情需要親力親為,比如skb的分配。這里我們首先分配了一個skb,然后獲取nlmsghdr,設置skb的源屬性,即來自於哪里,這里設置源端口為0,組掩碼為0表示不支持組播。接着就賦值數據到skb的數據區,這里依然是通過nlmsghdr。最后才調用netlink_unicast發送出去。
用戶端
一下貼圖均位於一個main函數中。

用戶端首先要創建一個套接字,不過這里就不會返回套接字結構,而是返回一個文件描述符fd;參數都是標准套接字的參數,這里就不多說,針對netlink首個協議族選擇AF_NETLINK,第二個參數是socket類型,我們選擇的是原生socketSOCK_RAW,最后是指定的協議,這個和前面內核定義的是一致的,否則無法通信。

設置源地址信息,我們設置的源端口為100,也可以是線程ID,保證唯一性即可。組播掩碼同樣設置0。最后調用bind函數把源地址和socket進行綁定。

分配一個nlmsghdr頭部,把源信息記錄進去,並設置目標地址信息,這里nl.pid=0表示目標在內核,這點同樣是和內核中對應的。nlh->nlmsg_len是對應的包含頭部在內的總長度,回想下內核中接收部分就明白了。

設置好之后就要准備發送了,不過用戶層發送到內核是通過另一個結構msghdr,這點在首篇文章框架介紹中有詳細說明,為此我們還要對msghdr進行填充,中間是通過iov向量管理。填充完畢就調用sendmsg庫函數發送到內核。接收就相當簡單了,我們利用了前面設置的msg,直接調用recvmsg函數即可
基本的通信流程如上文所述,下文針對每一部分做詳細分析,最后效果如圖所示……

以馬內利
參考資料:
1、《深入linux內核架構》
2、linux3.10.1源碼
