牙科医院网站建设,天津建设网站安全员考试查询,wordpress机器人,纯手工活150一天在家做一#xff0c;什么是Netlink通信机制
Netlink是linux提供的用于内核和用户态进程之间的通信方式。但是注意虽然Netlink主要用于用户空间和内核空间的通信#xff0c;但是也能用于用户空间的两个进程通信。只是进程间通信有其他很多方式#xff0c;一般不用Netlink。除非需要…一什么是Netlink通信机制
Netlink是linux提供的用于内核和用户态进程之间的通信方式。但是注意虽然Netlink主要用于用户空间和内核空间的通信但是也能用于用户空间的两个进程通信。只是进程间通信有其他很多方式一般不用Netlink。除非需要用到Netlink的广播特性时。
那么Netlink有什么优势呢
一般来说用户空间和内核空间的通信方式有三种/proc、ioctl、Netlink。而前两种都是单向的但是Netlink可以实现双工通信。Netlink协议基于BSD socket和AF_NETLINK地址簇(address family)使用32位的端口号寻址(以前称作PID)每个Netlink协议(或称作总线man手册中则称之为netlink family)通常与一个或一组内核服务/组件相关联如NETLINK_ROUTE用于获取和设置路由与链路信息、NETLINK_KOBJECT_UEVENT用于内核向用户空间的udev进程发送通知等。
netlink具有以下特点
① 支持全双工、异步通信(当然同步也支持)② 用户空间可使用标准的BSD socket接口(但netlink并没有屏蔽掉协议包的构造与解析过程推荐使用libnl等第三方库)③ 在内核空间使用专用的内核API接口④ 支持多播(因此支持“总线”式通信可实现消息订阅)⑤ 在内核端可用于进程上下文与中断上下
二用户态数据结构
首先看一下几个重要的数据结构的关系 1.struct msghdr
msghdr这个结构在socket变成中就会用到并不算Netlink专有的这里不在过多说明。只说明一下如何更好理解这个结构的功能。我们知道socket消息的发送和接收函数一般有这几对recvsend、readvwritev、recvfromsendto。当然还有recvmsgsendmsg前面三对函数各有各的特点功能而recvmsgsendmsg就是要囊括前面三对的所有功能当然还有自己特殊的用途。msghdr的前两个成员就是为了满足recvfromsendto的功能中间两个成员msg_iov和msg_iovlen则是为了满足readvwritev的功能而最后的msg_flags则是为了满足recvsend中flag的功能剩下的msg_control和msg_controllen则是满足recvmsgsendmsg特有的功能。
2.struct sockaddr_ln
struct sockaddr_ln为Netlink的地址和我们通常socket编程中的sockaddr_in作用一样他们的结构对比如下 struct sockaddr_nl的详细定义和描述如下
struct sockaddr_nl
{sa_family_t nl_family; /*该字段总是为AF_NETLINK */unsigned short nl_pad; /* 目前未用到填充为0*/__u32 nl_pid; /* process pid */__u32 nl_groups; /* multicast groups mask */
};(1) nl_pid在Netlink规范里PID全称是Port-ID(32bits)其主要作用是用于唯一的标识一个基于netlink的socket通道。通常情况下nl_pid都设置为当前进程的进程号。前面我们也说过Netlink不仅可以实现用户-内核空间的通信还可使现实用户空间两个进程之间或内核空间两个进程之间的通信。该属性为0时一般指内核。
(2) nl_groups如果用户空间的进程希望加入某个多播组则必须执行bind()系统调用。该字段指明了调用者希望加入的多播组号的掩码(注意不是组号后面我们会详细讲解这个字段)。如果该字段为0则表示调用者不希望加入任何多播组。对于每个隶属于Netlink协议域的协议最多可支持32个多播组(因为nl_groups的长度为32比特)每个多播组用一个比特来表示。
3.struct nlmsghdr
Netlink的报文由消息头和消息体构成struct nlmsghdr即为消息头。消息头定义在文件里由结构体nlmsghdr表示
struct nlmsghdr
{__u32 nlmsg_len; /* Length of message including header */__u16 nlmsg_type; /* Message content */__u16 nlmsg_flags; /* Additional flags */__u32 nlmsg_seq; /* Sequence number */__u32 nlmsg_pid; /* Sending process PID */
};消息头中各成员属性的解释及说明
(1) nlmsg_len整个消息的长度按字节计算。包括了Netlink消息头本身。
(2) nlmsg_type消息的类型即是数据还是控制消息。目前(内核版本2.6.21)Netlink仅支持四种类型的控制消息如下
a) NLMSG_NOOP-空消息什么也不做b) NLMSG_ERROR-指明该消息中包含一个错误c) NLMSG_DONE-如果内核通过Netlink队列返回了多个消息那么队列的最后一条消息的类型为NLMSG_DONE其余所有消息的nlmsg_flags属性都被设置NLM_F_MULTI位有效。d) NLMSG_OVERRUN-暂时没用到。
(3) nlmsg_flags附加在消息上的额外说明信息如上面提到的NLM_F_MULTI。
三用户空间Netlink socket API
1.创建socket
int socket(int domain, int type, int protocol)
domain指代地址族,即AF_NETLINK;
套接字类型为SOCK_RAW或SOCK_DGRAM,因为netlink是一个面向数据报的服务;
protocol选择该套接字使用哪种netlink特征。
以下是几种预定义的协议类型:
NETLINK_ROUTE,NETLINK_FIREWALL,NETLINK_APRD,NETLINK_ROUTE6_FW。
可以非常容易的添加自己的netlink协议。为每一个协议类型最多可以定义32个多播组。每一个多播组用一个bitmask来表示,1i(0i 31),这在一组进程和内核进程协同完成一项任务时非常有用。发送多播netlink消息可以减少系统调用的数量,同时减少用来维护多播组成员信息的负担。
2.地址绑定bind()
bind(fd, (struct sockaddr*), nladdr, sizeof(nladdr));
3.发送netlink消息
为了发送一条netlink消息到内核或者其他的用户空间进程,另外一个struct sockaddr_nl nladdr需要作为目的地址,这和使用sendmsg()发送一个UDP包是一样的。
如果该消息是发送至内核的,那么nl_pid和nl_groups都置为0.如果消息是发送给另一个进程的单播消息,nl_pid是另外一个进程的pid值而nl_groups为零。如果消息是发送给一个或多个多播组的多播消息,所有的目的多播组必须bitmask必须or起来从而形成nl_groups域。sendmsg(fd, , msg, 0);
4.接收netlink消息
一个接收程序必须分配一个足够大的内存用于保存netlink消息头和消息负载。然后其填充struct msghdr msg,再使用标准的recvmsg()函数来接收netlink消息。
当消息被正确的接收之后,nlh应该指向刚刚接收到的netlink消息的头。nladdr应该包含接收消息的目的地址,其中包括了消息发送者的pid和多播组。同时,宏NLMSG_DATA(nlh),定义在netlink.h中,返回一个指向netlink消息负载的指针。调用close(fd)关闭fd描述符所标识的socketrecvmsg(fd, , msg, 0);
四内核空间Netlink socket API
1.创建 netlink socket
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);参数说明
(1) net是一个网络名字空间namespace在不同的名字空间里面可以有自己的转发信息库有自己的一套net_device等等。默认情况下都是使用 init_net这个全局变量。(2) unit表示netlink协议类型如NETLINK_TEST、NETLINK_SELINUX。(3) groups多播地址。(4) input为内核模块定义的netlink消息处理函数当有消 息到达这个netlink socket时该input函数指针就会被引用且只有此函数返回时调用者的sendmsg才能返回。(5) cb_mutex为访问数据时的互斥信号量。(6) module 一般为THIS_MODULE。
2.发送单播消息 netlink_unicast
int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock)参数说明
(1) ssk为函数 netlink_kernel_create()返回的socket。(2) skb存放消息它的data字段指向要发送的netlink消息结构而 skb的控制块保存了消息的地址信息宏NETLINK_CB(skb)就用于方便设置该控制块。(3) pid为接收此消息进程的pid即目标地址如果目标为组或内核它设置为 0。(4) nonblock表示该函数是否为非阻塞如果为1该函数将在没有接收缓存可利用时立即返回而如果为0该函数在没有接收缓存可利用定时睡眠。
3.发送广播消息 netlink_broadcast
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid, u32 group, gfp_t allocation)前面的三个参数与 netlink_unicast相同参数group为接收消息的多播组该参数的每一个位代表一个多播组因此如果发送给多个多播组就把该参数设置为多个多播组组ID的位或。参数allocation为内核内存分配类型一般地为GFP_ATOMIC或GFP_KERNELGFP_ATOMIC用于原子的上下文即不可以睡眠而GFP_KERNEL用于非原子上下文。
4.释放 netlink socket
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid, u32 group, gfp_t allocation)五用户态
范例一
#include sys/stat.h
#include unistd.h
#include stdio.h
#include stdlib.h
#include sys/socket.h
#include sys/types.h
#include string.h
#include asm/types.h
#include linux/netlink.h
#include linux/socket.h
#include errno.h
#define MAX_PAYLOAD 1024 // maximum payload size
#define NETLINK_TEST 25 //自定义的协议
int main(int argc, char* argv[])
{int state;struct sockaddr_nl src_addr, dest_addr;struct nlmsghdr *nlh NULL; //Netlink数据包头struct iovec iov;struct msghdr msg;int sock_fd, retval;int state_smg 0;// Create a socketsock_fd socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);if(sock_fd -1){printf(error getting socket: %s, strerror(errno));return -1;}// To prepare bindingmemset(src_addr, 0, sizeof(src_addr));src_addr.nl_family AF_NETLINK;src_addr.nl_pid 100; //A设置源端端口号src_addr.nl_groups 0;//Bindretval bind(sock_fd, (struct sockaddr*)src_addr, sizeof(src_addr));if(retval 0){printf(bind failed: %s, strerror(errno));close(sock_fd);return -1;}// To orepare create mssagenlh (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));if(!nlh){printf(malloc nlmsghdr error!\n);close(sock_fd);return -1;
}memset(dest_addr,0,sizeof(dest_addr));dest_addr.nl_family AF_NETLINK;dest_addr.nl_pid 0; //B设置目的端口号dest_addr.nl_groups 0;nlh-nlmsg_len NLMSG_SPACE(MAX_PAYLOAD);nlh-nlmsg_pid 100; //C设置源端口nlh-nlmsg_flags 0;strcpy(NLMSG_DATA(nlh),Hello you!); //设置消息体iov.iov_base (void *)nlh;iov.iov_len NLMSG_SPACE(MAX_PAYLOAD);//Create mssagememset(msg, 0, sizeof(msg));msg.msg_name (void *)dest_addr;msg.msg_namelen sizeof(dest_addr);msg.msg_iov iov;msg.msg_iovlen 1;//send messageprintf(state_smg\n);state_smg sendmsg(sock_fd,msg,0);if(state_smg -1){printf(get error sendmsg %s\n,strerror(errno));}memset(nlh,0,NLMSG_SPACE(MAX_PAYLOAD));//receive messageprintf(waiting received!\n);while(1){printf(In while recvmsg\n);state recvmsg(sock_fd, msg, 0);if(state0){printf(state1);}printf(Received message: %s\n,(char *) NLMSG_DATA(nlh));}close(sock_fd);return 0;
}上面程序首先向内核发送一条消息“Hello you”然后进入循环一直等待读取内核的回复并将收到的回复打印出来。如果看上面程序感觉很吃力那么应该首先复习一下UDP中使用sendmsg的用法特别时struct msghdr的结构要清楚这里再赘述。
下面主要分析与UDP发送数据包的不同点
1. socket地址结构不同UDP为sockaddr_inNetlink为struct sockaddr_nl2. 与UDP发送数据相比Netlink多了一个消息头结构struct nlmsghdr需要我们构造。
注意代码注释中的A、B、C三处分别设置了pid。首先解释一下什么是pid网上很多文章把这个字段说成是进程的pid其实这完全是望文生义。这里的pid和进程pid没有什么关系仅仅相当于UDP的port。对于UDP来说port和ip标示一个地址那对我们的NETLINK_TEST协议注意Netlink本身不是一个协议来说pid就唯一标示了一个地址。所以你如果用进程pid做为标示当然也是可以的。当然同样的pid对于NETLINK_TEST协议和内核定义的其他使用Netlink的协议是不冲突的就像TCP的80端口和UDP的80端口。
下面分析这三处设置pid分别有什么作用首先A和B位置的比较好理解这是在地址sockaddr_nl上进行的设置就是相当于设置源地址和目的地址其实是端口只是注意B处设置pid为00就代表是内核可以理解为内核专用的pid那么用户进程就不能用0做为自己的pid吗这个只能说如果你非要用也是可以的只是会产生一些问题后面在分析。
接下来看为什么C处的消息头仍然需要设置pid呢这里首先要知道一个前提内核不会像UDP一样根据我们设置的原、目的地址为我们构造消息头所以我们不在包头写入我们自己的地址pid那内核怎么知道是谁发来的报文呢当然如果内核只是处理消息不需要回复进程的话舍不设置这个消息头pid都可以。
所以每个pid的设置功能不同A处的设置是要设置发送者的源地址有人会说既然源地址又不会自动填充到报文中我们为什么还要设置这个因为你还可能要接收回复啊。就像寄信你连“门牌号”都没有即使你在写信时候写上你的地址是100号对方回信目的地址也是100号但是邮局发现根本没有这个地址怎么可能把信送到你手里呢所以A的主要作用是注册源地址保证可以收到回复如果不需要回复当然可以简单将pid设置为0B处自然就是收信人的地址pid为0代表内核的地址假如有一个进程在101号上注册了地址并调用了recvmsg如果你将B处的pid设置为101那数据包就发给了另一个进程这就实现了使用Netlink进行进程间通信C相当于你在信封上写的源地址通常情况下这个应该和你的真实地址A处注册的源地址相同当然你要是不想收到回信又想恶搞一下或者有特殊需求你可以写成其他进程注册的pid比如101。这和我们现实中寄信是一样的你给你朋友写封情书把写信人写成你的另一个好基友然后后果你懂得……
好了有了这个例子我们就大概知道用户态怎么使用Netlink了。
六内核态程序
范例一
#include linux/init.h
#include linux/module.h
#include linux/timer.h
#include linux/time.h
#include linux/types.h
#include net/sock.h
#include net/netlink.h
#define NETLINK_TEST 25
#define MAX_MSGSIZE 1024
int stringlength(char *s);
int err;
struct sock *nl_sk NULL;
int flag 0;
//向用户态进程回发消息
void sendnlmsg(char *message, int pid)
{struct sk_buff *skb_1;struct nlmsghdr *nlh;int len NLMSG_SPACE(MAX_MSGSIZE);int slen 0;if(!message || !nl_sk){return ;}printk(KERN_ERR pid:%d\n,pid);skb_1 alloc_skb(len,GFP_KERNEL);if(!skb_1){printk(KERN_ERR my_net_link:alloc_skb error\n);}slen stringlength(message);nlh nlmsg_put(skb_1,0,0,0,MAX_MSGSIZE,0);NETLINK_CB(skb_1).pid 0;NETLINK_CB(skb_1).dst_group 0;message[slen] \0;memcpy(NLMSG_DATA(nlh),message,slen1);printk(my_net_link:send message %s.\n,(char *)NLMSG_DATA(nlh));netlink_unicast(nl_sk,skb_1,pid,MSG_DONTWAIT);
}
int stringlength(char *s)
{int slen 0;for(; *s; s){slen;}return slen;
}
//接收用户态发来的消息
void nl_data_ready(struct sk_buff *__skb){struct sk_buff *skb;struct nlmsghdr *nlh;char str[100];struct completion cmpl;printk(begin data_ready\n);int i10;int pid;skb skb_get (__skb);if(skb-len NLMSG_SPACE(0)){nlh nlmsg_hdr(skb);memcpy(str, NLMSG_DATA(nlh), sizeof(str));printk(Message received:%s\n,str) ;pid nlh-nlmsg_pid;while(i--){//我们使用completion做延时每3秒钟向用户态回发一个消息init_completion(cmpl);wait_for_completion_timeout(cmpl,3 * HZ);sendnlmsg(I am from kernel!,pid);}flag 1;kfree_skb(skb);}}
// Initialize netlink
int netlink_init(void)
{nl_sk netlink_kernel_create(init_net, NETLINK_TEST, 1,nl_data_ready, NULL, THIS_MODULE);if(!nl_sk){printk(KERN_ERR my_net_link: create netlink socket error.\n);return 1;}printk(my_net_link_4: create netlink socket ok.\n);return 0;
}
static void netlink_exit(void)
{if(nl_sk ! NULL){sock_release(nl_sk-sk_socket);}printk(my_net_link: self module exited\n);
}
module_init(netlink_init);
module_exit(netlink_exit);
MODULE_AUTHOR(zhao_h);
MODULE_LICENSE(GPL);附上内核代码的Makefile文件
ifneq ($(KERNELRELEASE),)
obj-m :netl.o
else
KERNELDIR ?/lib/modules/$(shell uname -r)/build
PWD :$(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M$(PWD) modules
endif
我们将内核模块insmod后运行用户态程序结果如下 这个结果复合我们的预期但是运行过程中打印出“state_smg”卡了好久才输出了后面的结果。这时候查看客户进程是处于D状态的不了解D状态的同学可以google一下。这是为什么呢因为进程使用Netlink向内核发数据是同步内核向进程发数据是异步。什么意思呢也就是用户进程调用sendmsg发送消息后内核会调用相应的接收函数但是一定到这个接收函数执行完用户态的sendmsg才能够返回。我们在内核态的接收函数中调用了10次回发函数每次都等待3秒钟所以内核接收函数30秒后才返回所以我们用户态程序的sendmsg也要等30秒后才返回。相反内核回发的数据不用等待用户程序接收这是因为内核所发的数据会暂时存放在一个队列中。
资料直通车最新Linux内核源码资料文档视频资料
内核学习地址Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈
再来回到之前的一个问题用户态程序的源地址pid可以用0吗我把上面的用户程序的A和C处pid都改为了0结果一运行就死机了。为什么呢我们看一下内核代码的逻辑收到用户消息后根据消息中的pid发送回去而pid为0内核并不认为这是用户程序认为是自身所有又将回发的10个消息发给了自己内核这样就陷入了一个死循环而用户态这时候进程一直处于D。
另外一个问题如果同时启动两个用户进程会是什么情况答案是再调用bind时出错“Address already in use”这个同UDP一样同一个地址同一个port如果没有设置SO_REUSEADDR两次bind就会出错之后我用同样的方式再Netlink的socket上设置了SO_REUSEADDR但是并没有什么效果。
七用户态
范例二
之前我们说过UDP可以使用sendmsgrecvmsg也可以使用sendtorecvfrom那么Netlink同样也可以使用sendtorecvfrom。具体实现如下
#include sys/stat.h
#include unistd.h
#include stdio.h
#include stdlib.h
#include sys/socket.h
#include sys/types.h
#include string.h
#include asm/types.h
#include linux/netlink.h
#include linux/socket.h
#include errno.h
#define MAX_PAYLOAD 1024 // maximum payload size
#define NETLINK_TEST 25
int main(int argc, char* argv[])
{struct sockaddr_nl src_addr, dest_addr;struct nlmsghdr *nlh NULL;int sock_fd, retval;int state,state_smg 0;// Create a socketsock_fd socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);if(sock_fd -1){printf(error getting socket: %s, strerror(errno));return -1;}// To prepare bindingmemset(src_addr, 0, sizeof(src_addr));src_addr.nl_family AF_NETLINK;src_addr.nl_pid 100;src_addr.nl_groups 0;//Bindretval bind(sock_fd, (struct sockaddr*)src_addr, sizeof(src_addr));if(retval 0){printf(bind failed: %s, strerror(errno));close(sock_fd);return -1;}// To orepare create mssage headnlh (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));if(!nlh){printf(malloc nlmsghdr error!\n);close(sock_fd);return -1;}memset(dest_addr,0,sizeof(dest_addr));dest_addr.nl_family AF_NETLINK;dest_addr.nl_pid 0;dest_addr.nl_groups 0;nlh-nlmsg_len NLMSG_SPACE(MAX_PAYLOAD);nlh-nlmsg_pid 100;nlh-nlmsg_flags 0;strcpy(NLMSG_DATA(nlh),Hello you!);//send messageprintf(state_smg\n);sendto(sock_fd,nlh,NLMSG_LENGTH(MAX_PAYLOAD),0,(struct sockaddr*)(dest_addr),sizeof(dest_addr));if(state_smg -1){printf(get error sendmsg %s\n,strerror(errno));}memset(nlh,0,NLMSG_SPACE(MAX_PAYLOAD));//receive messageprintf(waiting received!\n);
while(1){printf(In while recvmsg\n);
staterecvfrom(sock_fd,nlh,NLMSG_LENGTH(MAX_PAYLOAD),0,NULL,NULL);if(state0){printf(state1);}printf(Received message: %s\n,(char *) NLMSG_DATA(nlh));memset(nlh,0,NLMSG_SPACE(MAX_PAYLOAD));}close(sock_fd);return 0;
}熟悉UDP编程的同学看到这个程序一定很熟悉除了多了一个Netlink消息头的设置。但是我们发现程序中调用了bind函数这个函数再UDP编程中的客户端不是必须的因为我们不需要把UDP socket与某个地址关联同时再发送UDP数据包时内核会为我们分配一个随即的端口。但是对于Netlink必须要有这一步bind因为Netlink内核可不会为我们分配一个pid。再强调一遍消息头nlmsghdr中的pid是告诉内核接收端要回复的地址但是这个地址存不存在内核并不关心这个地址只有用户端调用了bind后才存在。
我们看到这两个例子都是用户态首先发起的那Netlink是否支持内核态主动发起的情况呢
当然是可以的只是内核一般需要事件触发这里只要和用户态约定号一个地址pid内核直接调用netlink_unicast就可以了。