(1)netlink使用cb字段傳輸數據。(sk_buff是Linux在其協議棧裏傳送的結構體,也就是所謂的“包”)
(2)用戶空間的netlink套接字很簡單,和傳統的網絡套接字類似,只是修改了一些參數:
sd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
第三個參數指定netlink協議類型,可以是用戶自定義協議類型NETLINK_MYTEST。NETLINK_GENERIC是一個通用的協議類型,它是專門爲用戶使用的,因此,用戶可以直接使用它,而不必再添加新的協議類型。
(3)內核裏面建立一個netlink套接字需要如下調用:
struct sock * netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len))
在input中,你可以直接處理收到的數據,也可以不處理,在大量數據傳輸的情況下,在input中處理是不明智的,正確的方式應該是建立一個內核線程專門接收數據,沒有數據的時候該內核線程睡眠,一旦有了數據,input回調函數喚醒這個內核線程就是了。
用戶態使用netlink:
用戶態應用使用標準的socket APIs, socket(), bind(), sendmsg(), recvmsg() 和 close() 就能很容易地使用 netlink socket。
函數 bind() 用於把一個打開的 netlink socket 與 netlink 源 socket 地址綁定在一起。netlink socket 的地址結構如下:
struct sockaddr_nl { sa_family_t nl_family; //必須設置爲 AF_NETLINK 或着 PF_NETLINK unsigned short nl_pad; //當前沒有使用,因此要總是設置爲 0 __u32 nl_pid; //接收或發送消息的進程的 ID,如果希望內核處理消息或多播消息,就把該字段設置爲 0,否則設置爲處理消息的進程 ID。 __u32 nl_groups; //用於指定多播組,bind 函數用於把調用進程加入到該字段指定的多播組,如果設置爲 0,表示調用者不加入任何多播組。 };
bind(fd, (struct sockaddr*)&nladdr, sizeof(struct sockaddr_nl));fd爲前面的 socket 調用返回的文件描述符,參數 nladdr 爲 struct sockaddr_nl 類型的地址。
爲了發送一個 netlink 消息給內核或其他用戶態應用,需要填充目標 netlink socket 地址,此時,字段 nl_pid 和 nl_groups 分別表示接收消息者的進程 ID 與多播組。如果字段 nl_pid 設置爲 0,表示消息接收者爲內核或多播組,如果 nl_groups爲 0,表示該消息爲單播消息,否則表示多播消息。
使用函數 sendmsg 發送 netlink 消息時還需要引用結構 struct msghdr、struct nlmsghdr 和 struct iovec,結構 struct msghdr 需如下設置:
struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_name = (void *)&(nladdr);
msg.msg_namelen = sizeof(nladdr);
|
其中 nladdr 爲消息接收者的 netlink 地址。
struct nlmsghdr 爲 netlink socket 自己的消息頭,這用於多路複用和多路分解 netlink 定義的所有協議類型以及其它一些控制。因此,應用在發送 netlink 消息時必須提供該消息頭。
struct nlmsghdr { __u32 nlmsg_len; /* Length of message */ __u16 nlmsg_type; /* Message type*/ __u16 nlmsg_flags; /* Additional flags */ __u32 nlmsg_seq; /* Sequence number */ __u32 nlmsg_pid; /* Sending process PID */ }; |
字段 nlmsg_len 指定消息的總長度,包括緊跟該結構的數據部分長度以及該結構的大小,字段 nlmsg_type 用於應用內部定義消息的類型,它對 netlink 內核實現是透明的,因此大部分情況下設置爲 0,字段 nlmsg_flags 用於設置消息標誌,字段 nlmsg_seq 和 nlmsg_pid 用於應用追蹤消息,前者表示順序號,後者爲消息來源進程 ID。
結構 struct iovec 用於把多個消息通過一次系統調用來發送。
在完成以上步驟後,消息就可以通過下面語句直接發送:
sendmsg(fd, &msg, 0); |
應用接收消息時需要首先分配一個足夠大的緩存來保存消息頭以及消息的數據部分,然後填充消息頭,添完後就可以直接調用函數 recvmsg() 來接收。
在消息接收後,nlhdr指向接收到的消息的消息頭,nladdr保存了接收到的消息的目標地址,宏NLMSG_DATA(nlhdr)返回指向消息的數據部分的指針。宏NLMSG_ALIGN(len)用於得到不小於len且字節對齊的最小數值。
宏NLMSG_LENGTH(len)用於計算數據部分長度爲len時實際的消息長度。它一般用於分配消息緩存。
宏NLMSG_SPACE(len)返回不小於NLMSG_LENGTH(len)且字節對齊的最小數值,它也用於分配消息緩存。
宏NLMSG_DATA(nlh)用於取得消息的數據部分的首地址,設置和讀取消息數據部分時需要使用該宏。
宏NLMSG_NEXT(nlh,len)用於得到下一個消息的首地址,同時len也減少爲剩餘消息的總長度,該宏一般在一個消息被分成幾個部分發送或接收時使用。
宏NLMSG_OK(nlh,len)用於判斷消息是否有len這麼長。
宏NLMSG_PAYLOAD(nlh,len)用於返回payload的長度.
netlink內核API:
參數unit表示netlink協議類型,如NETLINK_MYTEST,參數input則爲內核模塊定義的netlink消息處理函數,當有消 息到達這個netlink socket時,該input函數指針就會被引用。函數指針input的參數sk實際上就是函數netlink_kernel_create返回的 struct sock指針,sock實際是socket的一個內核表示數據結構,用戶態應用創建的socket在內核中也會有一個struct sock結構來表示。
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)
void input (struct sock *sk, int len) { struct sk_buff *skb; struct nlmsghdr *nlh = NULL; u8 *data = NULL; //skb_dequeue用於取得socket sk的接收隊列上的消息,返回爲一個struct sk_buff的結構,skb->data指向實際的netlink消息。 while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) { /* process netlink message pointed by skb->data */ nlh = (struct nlmsghdr *)skb->data; data = NLMSG_DATA(nlh); /* process netlink message with header pointed by nlh and data pointed by data */ } }函數input()會在發送進程執行sendmsg()時被調用,這樣處理消息比較及時,但是,如果消息特別長時,這樣處理將增加系統調用 sendmsg()的執行時間,對於這種情況,可以定義一個內核線程專門負責消息接收,而函數input的工作只是喚醒該內核線程,這樣sendmsg將 很快返回。
當內核中發送netlink消息時,也需要設置目標地址與源地址,而且內核中消息是通過struct sk_buff來管理的:
#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb))
來方便消息的地址設置。下面是一個消息地址設置的例子:
NETLINK_CB(skb).pid = 0; NETLINK_CB(skb).dst_pid = 0; NETLINK_CB(skb).dst_group = 1; |
字段pid表示消息發送者進程ID,也即源地址,對於內核,它爲 0, dst_pid 表示消息接收者進程 ID,也即目標地址,如果目標爲組或內核,它設置爲 0,否則 dst_group 表示目標組地址,如果它目標爲某一進程或內核,dst_group 應當設置爲 0。
在內核中,模塊調用函數 netlink_unicast 來發送單播消息:
int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock); |
參數sk爲函數netlink_kernel_create()返回的socket,參數skb存放消息,它的data字段指向要發送的 netlink消息結構,而skb的控制塊保存了消息的地址信息,前面的宏NETLINK_CB(skb)就用於方便設置該控制塊, 參數pid爲接收消息進程的pid,參數nonblock表示該函數是否爲非阻塞,如果爲1,該函數將在沒有接收緩存可利用時立即返回,而如果爲0,該函 數在沒有接收緩存可利用時睡眠。
內核模塊或子系統也可以使用函數netlink_broadcast來發送廣播消息:
void netlink_broadcast(struct sock *sk, struct sk_buff *skb, u32 pid, u32 group, int allocation); |
前面的三個參數與netlink_unicast相同,參數group爲接收消息的多播組,該參數的每一個代表一個多播組,因此如果發送給多個多播 組,就把該參數設置爲多個多播組組ID的位或。參數allocation爲內核內存分配類型,一般地爲GFP_ATOMIC或 GFP_KERNEL,GFP_ATOMIC用於原子的上下文(即不可以睡眠),而GFP_KERNEL用於非原子上下文。
在內核中使用函數sock_release來釋放函數netlink_kernel_create()創建的netlink socket:
void sock_release(struct socket * sock); |
注意函數netlink_kernel_create()返回的類型爲struct sock,因此函數sock_release應該這種調用:
sock_release(sk->sk_socket); |
sk爲函數netlink_kernel_create()的返回值。