有了ioctl,和proc,我們是不是還有其它可以與kernel交換資料的界面呢?沒錯,就是netlink,那什麼時候會用到netlink呢?當你設routing table或防火牆時就會用到了,kernel就是用netlink界面與route指令溝通,scsi驅動也可以看到netlink的影子,netlink有什麼好處呢?

優點:
1.它沒有ioctl等ret回來的kernel round trip time
2.可發多個訊息給不同的user process(如果user processes都在同一group)
缺點:
1.因為只有UDP封包,所以你送資料後也不曉得該資料到底是不是去到對的地方
2.用了一堆socket API,程式碼寫起來並不簡潔

簡單描述我現在做的測試,以下面的kernel udp echo server為例,它會接收使用者的訊息並echo回來,所以整個程式分為兩部份,udp echo server在kernel space,使用者程式在user space

udp echo server重點列如下
1. netlink_kernel_create: create kernel socket並且註冊你的callback函式處理netlink封包,請特別注意第二個參數,我用了NETLINK_USERSOCK這個linux保留給使用者測試的netlink type.
2. sock_release:移除module時,一定要release kernel socket
3. udp_receive:這個函式就是我們處理netlink傳入封包的地方,在這裡你可以取得user process id和data
4. udp_reply:做一個新的封包並呼叫函式netlink_unicast echo回去

[kudp echo server]

#include <linux/module.h> 
#include <linux/kernel.h> 
#include <linux/init.h> 
#include <net/sock.h> 
#include <net/netlink.h> 
#include <linux/skbuff.h> 
 
 
static struct sock *netlink_sock;
 
static void udp_reply(int pid,int seq,void *payload) 
{ 
struct sk_buff *skb;
struct nlmsghdr *nlh;
int size=strlen(payload)+1;
int len = NLMSG_SPACE(size);
void *data;
int ret;
 
skb = alloc_skb(len, GFP_ATOMIC);
if (!skb) 
return;
nlh= NLMSG_PUT(skb, pid, seq, 0, size);
nlh->nlmsg_flags = 0;
data=NLMSG_DATA(nlh);
memcpy(data, payload, size);
NETLINK_CB(skb).pid = 0; /* from kernel */ 
NETLINK_CB(skb).dst_group = 0; /* unicast */ 
ret=netlink_unicast(netlink_sock, skb, pid, MSG_DONTWAIT);
if (ret <0) 
{ 
printk("send failed\n");
return;
} 
return;
 
nlmsg_failure: /* Used by NLMSG_PUT */ 
if (skb) 
kfree_skb(skb);
} 
 
 
/* Receive messages from netlink socket. */ 
static void udp_receive(struct sk_buff *skb) 
{ 
u_int uid, pid, seq, sid;
void *data;
struct nlmsghdr *nlh;
 
nlh = (struct nlmsghdr *)skb->data;
pid = NETLINK_CREDS(skb)->pid;
uid = NETLINK_CREDS(skb)->uid;
sid = NETLINK_CB(skb).sid;
seq = nlh->nlmsg_seq;
data = NLMSG_DATA(nlh);
printk("recv skb from user space uid:%d pid:%d seq:%d,sid:%d\n",uid,pid,seq,sid);
printk("data is :%s\n",(char *)data);
udp_reply(pid,seq,data);
return ;
} 
 
static int __init kudp_init(void) 
{ 
netlink_sock = netlink_kernel_create(&init_net, NETLINK_USERSOCK, 0,udp_receive, NULL, THIS_MODULE);
return 0;
} 
 
static void __exit kudp_exit(void) 
{ 
sock_release(netlink_sock->sk_socket);
printk("netlink driver remove successfully\n");
} 
module_init(kudp_init);
module_exit(kudp_exit);
 
MODULE_DESCRIPTION("kudp echo server module");
MODULE_AUTHOR("Joey Cheng<jemicheng@gmail.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("QT2410:kudp echo server module");

user space client重點列如下
1.宣告socket fd請用sock_fd = socket(PF_NETLINK, SOCK_RAW,NETLINK_USERSOCK),第三個傳入參數為netlink type
2.注意pid與group的使用方式,如果只是unicast,要把group設為0
3.小心struct msghdr msg,要先把裡面的iov,nlmsghdr等欄位宣告並初始化
4.簡單的用sendmsg和recvmsg就大功告成啦
[user space client]

#include <stdio.h> 
#include <string.h> 
#include <malloc.h> 
#include <sys/socket.h> 
#include <linux/netlink.h> 
 
#define MAX_PAYLOAD 1024
 
int main(int argc,char *argv[]) 
{ 
struct sockaddr_nl src_addr, dest_addr;
struct nlmsghdr *nlh = NULL;
struct iovec iov;
int sock_fd;
struct msghdr msg;
 
sock_fd = socket(PF_NETLINK, SOCK_RAW,NETLINK_USERSOCK);
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid(); /* self pid */ 
src_addr.nl_groups = 0; /* not in mcast groups */ 
bind(sock_fd, (struct sockaddr*)&src_addr,sizeof(src_addr));
 
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0; /* For Linux Kernel */ 
dest_addr.nl_groups = 0; /* unicast */ 
 
nlh=(struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
/* Fill the netlink message header */ 
nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
nlh->nlmsg_pid = getpid(); /* self pid */ 
nlh->nlmsg_flags = 0;
/* Fill in the netlink message payload */ 
strcpy(NLMSG_DATA(nlh), "Hello World!");
 
iov.iov_base = (void *)nlh;
iov.iov_len = nlh->nlmsg_len;
msg.msg_name = (void *)&dest_addr;
msg.msg_namelen = sizeof(dest_addr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
 
sendmsg(sock_fd, &msg, 0);
 
printf("Waiting for message from kernel\n");
 
/* Read message from kernel */ 
recvmsg(sock_fd, &msg, 0);
 
printf("Received message payload: %s\n",NLMSG_DATA(msg.msg_iov->iov_base));
close(sock_fd);
return 0;
}

全部的程式請在這邊下載,如果要嘗試multicast server,那麼請參考kdupb.c這支程式,和它的user space client mc1.c和mc2.c,只要group設定正確mc1和mc2都可以收到kudpb的multicast訊息,kudpb利用netlink_broadcast廣播給mc1和mc2,執行畫面如下

最後修改日期: 3 6 月, 2022

作者

留言

撰寫回覆或留言

發佈留言必須填寫的電子郵件地址不會公開。