Netlink在2.6版本的内核中变化也是很大的,在最新的2.6.37内核中,其定义已经改成下面这种形式,传递的参数已经达到6个。其中第一个参数和mutex参数都是最新添加的。Mutex也可以为空。这里主要是关于内核空间中的netlink函数的使用。
extern 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) ;
struct net是一个网络名字空间namespace,在不同的名字空间里面可以有自己的转发信息库,有自己的一套net_device等等。默认情况下都是使用 init_net这个全局变量,下面是内核中调用netlink_kernel_create()函数的一个示例。
在内核中,
audit_sock = netlink_kernel_create( & init_net, NETLINK_AUDIT, 0, audit_receive, NULL , THIS_MODULE) ;
模块调用函数 netlink_unicast 来发送单播消息:
int netlink_unicast( struct sock * ssk, struct sk_buff * skb, u32 pid, int nonblock)
参数ssk为函数 netlink_kernel_create()返回的socket,参数skb存放消息,它的data字段指向要发送的netlink消息结构,而 skb的控制块保存了消息的地址信息,前面的宏NETLINK_CB(skb)就用于方便设置该控制块,参数pid为接收消息进程的pid,参数nonblock表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回,而如果为0,该函数在没有接收缓存可利用 定时睡眠。
netlink的内核实现在.c文件 net/core/af_netlink.c中,内核模块要想使用netlink,也必须包含头文件linux/netlink.h。内核使用 netlink需要专门的API,这完全不同于用户态应用对netlink的使用。如果用户需要增加新的netlink协议类型,必须通过修改 linux/netlink.h来实现,当然,目前的netlink实现已经包含了一个通用的协议类型NETLINK_GENERIC以方便用户使用,用户可以直接使用它而不必增加新的协议类型。前面讲到,为了增加新的netlink协议类型,用户仅需增加如下定义到linux/netlink.h就可以:
只要增加这个定义之后,用户就可以在内核的任何地方引用该协议。
在内核中,为了创建一个netlink socket用户需要调用如下函数:
extern 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) ;
struct net是一个网络名字空间namespace,在不同的名字空间里面可以有自己的转发信息库,有自己的一套net_device等等。默认情况下都是使用init_net这个全局变量
参数unit表示netlink协议类型,如 NETLINK_MYTEST,参数input则为内核模块定义的netlink消息处理函数,当有消息到达这个netlink socket时,该input函数指针就会被引用。函数指针input的参数skb实际上就是函数netlink_kernel_create返回的 struct sock指针,sock实际是socket的一个内核表示数据结构,用户态应用创建的socket在内核中也会有一个struct sock结构来表示。
函数input()会在发送进程执行sendmsg()时被调用,这样处理消息比较及时,但是,如果消息特别长时,这样处理将增加系统调用sendmsg()的执行时间,也就是说当用户的程序调用sendmsg ()函数时,如果input()函数处理时间过长,也就是说input()函数不执行不完,用户程序调用的sendmsg()函数就不会返回。只有当内核空间中的input()函数返回时,用户调用的sendmsg()函数才会返回。对于这种情况,可以定义一个内核线程专门负责消息接收,而函数input 的工作只是唤醒该内核线程,这样sendmsg将很快返回。(这里网上的的说明)不过在查看Linux2.6.37版本的内核时并没有发现这种处理过程,一般都是按下面的方法进行处理。
这里注册的netlink协议为NETLINK_XFRM。
nlsk = netlink_kernel_create( net, NETLINK_XFRM, XFRMNLGRP_MAX, xfrm_netlink_rcv, NULL , THIS_MODULE) ; static void xfrm_netlink_rcv( struct sk_buff * skb) { mutex_lock( & xfrm_cfg_mutex) ; netlink_rcv_skb( skb, & xfrm_user_rcv_msg) ; mutex_unlock( & xfrm_cfg_mutex) ; } 在netlink_rcv_skb( ) 函数中进行接收处理。 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_KERNEL,GFP_ATOMIC用于原子的上下文(即不可以睡眠),而GFP_KERNEL用于非原子上下文。
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写的和内核通信的两个程序,一个是用户空间,一个是内核空间。 内核空间:
# 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) ; void sendnlmsg( char * message) ; int pid; int err; struct sock * nl_sk = NULL ; int flag = 0; void sendnlmsg( char * message) { struct sk_buff * skb_1; struct nlmsghdr * nlh; int len = NLMSG_SPACE( MAX_MSGSIZE) ; / *计算消息总长:消息首部加上数据加度*/ int slen = 0; if ( ! message | | ! nl_sk) { return ; } skb_1 = alloc_skb( len, GFP_KERNEL) ; if ( ! skb_1) { printk( KERN_ERR "my_net_link:alloc_skb_1 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, slen+ 1) ; 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; int i= 10; 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- - ) { init_completion( & cmpl) ; wait_for_completion_timeout( & cmpl, 3 * HZ) ; sendnlmsg( "I am from kernel!" ) ; } 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_3: 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( "frankzfz" ) ; MODULE_LICENSE( "GPL" ) ;
下面是用户空间的程序:
# 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 NETLINK_TEST 25 # define MAX_PAYLOAD 1024 // maximum payload size int main( int argc, char * argv[ ] ) { int state; struct sockaddr_nl src_addr, dest_addr; struct nlmsghdr * nlh = NULL ; struct iovec iov; struct msghdr msg; int sock_fd, retval; int state_smg = 0; // Create a socket sock_fd = socket ( AF_NETLINK , SOCK_RAW , NETLINK_TEST) ; if ( sock_fd = = - 1) { printf ( "error getting socket: %s" , strerror ( errno ) ) ; return - 1; } // To prepare binding memset ( & msg, 0, sizeof ( msg) ) ; 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; // multi cast retval = 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 prepare recvmsg nlh = ( 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 = getpid( ) ; nlh- > nlmsg_flags = 0; strcpy ( NLMSG_DATA( nlh) , "Hello you!" ) ; iov. iov_base = ( void * ) nlh; iov. iov_len = NLMSG_SPACE( MAX_PAYLOAD) ; // iov.iov_len = nlh->nlmsg_len; memset ( & msg, 0, sizeof ( msg) ) ; msg. msg_name = ( void * ) & dest_addr; msg. msg_namelen = sizeof ( dest_addr) ; msg. msg_iov = & iov; msg. msg_iovlen = 1; printf ( "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) ) ; printf ( "waiting received!\n" ) ; // Read message from kernel while ( 1) { printf ( "In while recvmsg\n" ) ; state = recvmsg ( sock_fd, & msg, 0) ; if ( state< 0) { printf ( "state<1" ) ; } printf ( "In while\n" ) ; printf ( "Received message: %s\n" , ( char * ) NLMSG_DATA( nlh) ) ; } close ( sock_fd) ; return 0; }
下面是Makefile文件:
obj- m : = netlink_k. o KERNELBUILD : = / lib/ modules/ ` uname - r`/ build default : @echo "BUILE Kmod" @make - C $( KERNELBUILD) M= $ ( shell pwd) modules gcc - o netlink_2 netlink_2. c clean: @echo " CLEAN kmod" @rm - rf * . o @rm - rf . depend . * . cmd * . ko * . mod. c . tmp_versions * . symvers . * . d
其中,netlink_k.c为内核的空间的程序。 先运行内核代码netlink_k.ko,也就是在执行完makefile文件后,会生成一个netlink_k.ko文件,可以使用下面的命令进行安装,insmod netlink_k.ko,使用lsmod查看,当安装成功后,然后,执行./netlink用户空间程序,可以在另一个终端下执行dmesg命令,查看内核通信的情况。这里netlink程序向内核空间发送一个hello you!内核返回给一个I am from kernel!在这里使用了一个定时器,也就是每3秒中发送一次I am from kernel!只有内核把10个字符串全部发送完毕后,用户空间的sendmsg()才会返回,也就是在用户空间的netlink才会输出内核空间发送过来的数据,这里只有一个简单的程序,并没有什么实际的意义,因为,正如前面所说的一般情况下不会在回调函数中处理太多的东西,以免sendmsg()函数返回不及时。下面是使用dmesg命令输出的信息。
[ 873791. 498039] my_net_link_3: create netlink socket ok. [ 873810. 263676] Message received: Hello [ 873813. 260848] my_net_link_4: send message 'I am from kernel!' . [ 873816. 260821] my_net_link_4: send message 'I am from kernel!' . [ 873819. 260860] my_net_link_4: send message 'I am from kernel!' . [ 873822. 260762] my_net_link_4: send message 'I am from kernel!' . [ 873825. 260883] my_net_link_4: send message 'I am from kernel!' . [ 873828. 260669] my_net_link_4: send message 'I am from kernel!' . [ 873831. 260714] my_net_link_4: send message 'I am from kernel!' . [ 873834. 260683] my_net_link_4: send message 'I am from kernel!' . [ 873837. 260666] my_net_link_4: send message 'I am from kernel!' . [ 873840. 260632] my_net_link_4: send message 'I am from kernel!' .