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) ;
structnet是一个网络名字空间namespace,在不同的名字空间里面可以有自己的转发信息库,有自己的一套net_device等等。默认情况下都是使用init_net这个全局变量,参数unit表示netlink协议类型,如NETLINK_MYTEST,参数input则为内核模块定义的netlink消息处理函数,当有消息到达这个netlinksocket时,该input函数指针就会被引用。
下面是内核中调用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用户需要调用如下函数:
函数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 ) ;//pid=接受进程id,skb_1包含消息
}
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) ;//创建socket
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 netlinksocket
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!' .