內(nèi)核態(tài)與用戶態(tài)通信方式——Nelink
Linux下內(nèi)核空間與用戶空間進(jìn)行通信的方式主要有system call、sysctl、procfs、模塊參數(shù)、debugfs、relayfs、sysfs和netlink等。
Why NetlinK full-duplex模塊參數(shù)、sysfs、procfs、debugfs和relayfs都是基于文件系統(tǒng),用于內(nèi)核向用戶發(fā)送消息;sysctl和system call是用戶向內(nèi)核發(fā)送消息。它們都是單工通信方式。netlink是一種特殊的通信方式,用于在內(nèi)核空間和用戶空間傳遞消息,是一種雙工通信方式。使用地址協(xié)議簇AF_NETLINK,使用頭文件include/linux/netlink.h。
simple to add為新特性添加system call、sysctl或者procfs是一件復(fù)雜的工作,它們會(huì)污染kernel,破壞系統(tǒng)的穩(wěn)定性,這是非常危險(xiǎn)的。Netlink的添加,對內(nèi)核的影響僅在于向netlink.h中添加一個(gè)固定的協(xié)議類型,然后內(nèi)核模塊和應(yīng)用層的通信使用一套標(biāo)準(zhǔn)的API。 Netlink Socket APIs 用戶空間 創(chuàng)建
int socket(int doamin, int type, int protocal);
domain填A(yù)F_NETLINK,type為SOCK_RAW或者SOCK_DGRAM。protocal可選NETLINK_ROUTE、NETLINK_FIREWALL、NETLINK_ARPD、NETLINK_ROUTE6和NETLINK_IP6_FW,或者傳入自定義協(xié)議類型。
綁定int bind(int fd, (struct sockaddr *)&nladdr, sizeof(nladdr));
netlink地址結(jié)構(gòu)如下
struct sockaddr_nl{
sa_family_t nl_family; //AF_NETLINK
unsigned short nl_pad; //0
__u32 nl_pid; //進(jìn)程pid
__u32 nl_groups; //多播組掩碼
}
如果進(jìn)程僅僅使用一個(gè)netlink,nl_pid可以傳入該進(jìn)程的pid,
my_pid = getpid();
如果一個(gè)進(jìn)程的多個(gè)線程需要不同的netlink,使用如下方法獲得nl_pid:
pthread_self << 16 | gitpid();
發(fā)送
int sendmsg(fd, &msg, 0);
struct nlmsghdr msg為netlink發(fā)送消息結(jié)構(gòu),
struct msghdr{
void *msg_name; //socket name
int msg_namelen; //len of name
struct iovec *msg_iov; //data block
__kernel_size_t msg_iovlen; //num of block
void *msg_control; //per protocol magic
__kernel_size_t msg_controllen; //len of cmsg list
unsigned msg_flags;
}
其中需要填充的是msg_iov和msg_oivlen。
msg與其他結(jié)構(gòu)體的關(guān)系如圖所示
nlmsghdr結(jié)構(gòu)體為消息頭信息
struct nlmsghdr{
__u32 nlmsg_len; //len of msg
__u16 nlmsg_type; //msg type
__u16 nlmsg_flags; //additional flags
__u32 nlmsg_seq; //sequence num
__u32 nlmsg_pid; //send process pid
}
nlmsg_len是整個(gè)消息的長度,包含頭;
nlmsg_type對內(nèi)核不透明;
nlmsg_flag被用于對消息的附加控制;
nlmsg_seq和nlmsg_pid被用于跟蹤消息,對內(nèi)核不透明。
要將地址結(jié)構(gòu)體傳入消息頭部:
struct msghdr msg;
msg.msg_name = (void *)&nladdr;
msg.msg_namelen = sizeof(nladdr);
發(fā)送地址同樣使用sockaddr_nl結(jié)構(gòu)體,如果目的是內(nèi)核,nl_pid和nl_groups都為0;如果消息為單播,目的為另一個(gè)進(jìn)程,nl_pid是目的進(jìn)程pid,nl_groups為0;如果消息為多播,目的是一個(gè)或多個(gè)多播組,nl_groups域中需填寫所有目的多播組的掩碼和。
結(jié)構(gòu)體iovce結(jié)構(gòu)如下
struct iovec iov;
iov.iov_base = (void *)nlh;
iov.iov_len = nlh->nlmsg_len;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
接收
接收時(shí)需要?jiǎng)?chuàng)建足夠的buf,
struct sockadddr_nl nladdr;
struct msghdr msg;
struct iovec iov;
iov.iov_base = (void *)nlh;
iov.iov_len = MAX_NL_MSG_LEN;
msg.msg_name = (void *)&nladdr;
msg.msg_namelen = sizeof(nladdr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
recvmsg(fd, &msg, 0);
內(nèi)核空間
內(nèi)核空間的API由net/core/af_netlink.c支持。
可以在netlink.h中添加自定義協(xié)議類型:
#define NETLINK_TEST 17
創(chuàng)建
struct sock *netlink_kernel_create(int unit,
void (*input)(struct sock *sk, int len));
unit是netlink.h中定義的協(xié)議類型,*input是一個(gè)回調(diào)函數(shù)指針,接收到消息會(huì)觸發(fā)這個(gè)函數(shù)。
發(fā)送發(fā)送消息使用struct sk_buff *skb。
用以下方式設(shè)置本地地址:
NETLINK_CB(skb).groups = local_groups;
NETLINK_CB(skb).pid = 0;
因?yàn)槭窃趦?nèi)核,pid = 0
用以下方式設(shè)置目的地址:
NETLINK_CB(skb).dst_groups = dst_groups;
NETLINK_CB(skb).dst_pid = dst_pid;
發(fā)送單播函數(shù)為
int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock);
*sk為netlink_kernel_create()返回的結(jié)構(gòu)體指針;
skb->data指向消息;
pid為應(yīng)用程序的pid;
block表明如果當(dāng)接收緩沖非法時(shí)數(shù)據(jù)分塊還是返回錯(cuò)誤值。
發(fā)送多播函數(shù)為
void netlink_broadcast(struct sock *sk, struct sk_buff *skb, u32 pid, u32 group, int allocation);
group是所有需要接收多播消息的組掩碼和;
allocation是內(nèi)核內(nèi)存分配標(biāo)志,中斷上下文中使用GFP_ATOMIC,其他情況使用GFP_KERNEL。
sock_release(nl_sk->socket);
example
user-space
初始化
#define MAX_NLMSG_PAYLOAD_LEN 128
#define NETLINK_GROUP_MASK(group) (1 << group)
int usr_netlink_init(int netlink_type, int *fd, int group)
{
struct nlmsghdr *nlh = NULL;
struct sockaddr_nl addr;
memset((void *)&addr, 0x0, sizeof(addr));
if ((*fd = socket(PF_NETLINK, SOCK_RAW, netlink_type)) < 0)
{
/* error process*/
return -1;
}
addr.nl_family = AF_NETLINK;
addr.nl_pid = getpid();
addr.nl_groups = NETLINK_GROUP_MASK(group);
if (bind(*fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
/* error process */
close(*fd);
*fd = -1;
return -2;
}
/* send pid to kernel */
if (us_netlink_send(*fd, 0, NULL, 0) < 0)
{
/* error process */
close(*fd);
*fd = -1;
return -3;
}
return 0;
}
發(fā)送
int usr_netlink_send(int fd, uint16 msg_type, char *data, int data_len)
{
struct nlmsghdr *nlh;
struct msghdr msg;
struct iovec iov;
int msg_len;
if (data && (msg_len > MAX_NLMSG_PAYLOAD_LEN))
{
return -1;
}
memset((void*)&iov, 0x0, sizeof(iov));
memset((void*)&msg, 0x0, sizeof(msg));
msg_len = NLMSG_SPACE(data_len);
nlh = (struct nlmsghdr *)malloc(msg_len);
if (NULL = nlh)
{
return -2;
}
/* fill nlmsghdr */
memset(nlh, 0x0, msg_len);
nlh->nlmsg_len = msg_len;
nlh->nlmsg_pid = getpid();
nlh->nlmsg_type = msg_type;
nlh->nlmsg_flags = 0;
if (data)
{
memcpy(NLMSG_DATA(nlh), data, msg_len);
}
/* fill iovec */
iov.iov_base = (void *)nlh;
iov.iov_len = nlh->nlmsg_len;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
if (sendmsg(fd, &msg, 0) < 0)
{
/* error process */
free(nlh);
return -3;
}
free(nlh);
return 0;
}
接收
int usr_netlink_recv(int fd, char *buf, int len)
{
struct iovec iov = {buf, len};
struct sockaddr_nl nl_src_addr;
struct msghdr msg;
int recv_len;
memset(&msg, 0x0, sizeof(struct msghdr));
memset(&nl_src_addr, 0x0, sizeof(struct sockaddr_nl));
msg.msg_name = (void *)&nl_src_addr;
msg.msg_namelen = sizeof(nl_src_addr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
recv_len = recvmsg(fd, &msg, 0);
if (recv_len < 0)
{
/* recv error */
}
else if (recv_len == 0)
{
/* recv EOF */
}
return recv_len;
}
kernel
初始化
static struct sock *sg_knl_xxx_sk = NULL;
static int sg_knl_xxx_pid = -1;
void knl_netlink_init()
{
struct net init_net;
sg_knl_xxx_sk = netlink_kernel_create(&init_net, NETLINK_TEST, 0, knl_neklink_recv, NULL, THIS_MODULE);
}
接收
void knl_netlink_recv(struct sock *skb)
{
struct nlmsghdr *nlh = nlmsg_hdr(skb);
sg_knl_xxx_pid = nlh->nlmsg_pid;
}
發(fā)送
int group_mask(int group)
{
return (1 << group);
}
void knl_netlink_send(int msg_type, char *data, int data_len, int group)
{
struct sk_buff *skb = NULL;
struct nlmsghdr *nlh = NULL;
unsigned int msg_len = NLMSG_SPACE(data_len);
if(data && (data_len > MAX_PAYLOAD_LEN))
{
/* error process */
return;
}
skb = alloc_skb(msg_len, GFP_KERNEL);
if (!skb)
{
/* erro process */
return;
}
memset((void *)skb, 0x0, msg_len);
nlh = nlmsg_put(skb, 0, 0, msg_type, msg_len, 0);
if(data)
{
memcpy(NLMSG_DATA(nlh), data, data_len);
}
NETLINK_CB(skb).pid = 0; /*from kernel */
NETLINK_CB(skb).dst_group = group_mask(group);
/* if unicast */
netlink_unicast(sg_knl_xxx_sk, skb, sg_knl_xxx_pid, MSG_DONTWAIT);
/* if mulicast */
//nlmsg_multicast(sg_knl_xxx_sk, skb, 0, group_mask(group), 0);
}