在 Linux 2.4 版以后版本的内核中,几乎全部的中断过程与用户态进程的通信都是使用 netlink 套接字实现的,同时还使用 netlink 实现了 ip queue 工具,但 ip queue 的使用有其局限性,不能自由地用于各种中断过程。内核的帮助文档和其他一些 Linux 相关文章都没有对 netlink 套接字在中断过程和用户空间通信的应用上作详细的说明,使得很多用户对此只有一个模糊的概念。
Unicast Communication between Kernel and Application
在下面的例子中,一个用户空间进程发送一个netlink消息给内核模块,内核模块应答一个消息给发送进程,这里是用户空间的代码:
#include
<
sys
/
socket.h
>
#include
<
linux
/
netlink.h
>
#define
MAX_PAYLOAD 1024 /* maximum payload size*/
struct
sockaddr_nl src_addr, dest_addr;
struct
msghdr msg;
struct
nlmsghdr
*
nlh
=
NULL;
struct
iovec iov;
int
sock_fd;
void
main()
{
sock_fd
=
socket(PF_NETLINK, SOCK_RAW,NETLINK_TEST);
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 you!
"
);
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(fd,
&
msg,
0
);
/*
Read message from kernel
*/
memset(nlh,
0
, NLMSG_SPACE(MAX_PAYLOAD));
recvmsg(fd,
&
msg,
0
);
printf(
"
Received message payload: %s
"
,
NLMSG_DATA(nlh));
/*
Close Netlink Socket
*/
close(sock_fd);
}
这里是内核代码:
struct
sock
*
nl_sk
=
NULL;
void
nl_data_ready (
struct
sock
*
sk,
int
len)
{
wake_up_interruptible(sk
->
sleep);
}
void
netlink_test()
{
struct
sk_buff
*
skb
=
NULL;
struct
nlmsghdr
*
nlh
=
NULL;
int
err;
u32 pid;
nl_sk
=
netlink_kernel_create(NETLINK_TEST,
nl_data_ready);
/*
wait for message coming down from user-space
*/
skb
=
skb_recv_datagram(nl_sk,
0
,
0
,
&
err);
nlh
=
(
struct
nlmsghdr
*
)skb
->
data;
printk(
"
%s: received netlink message payload:%s
"
,
__FUNCTION__, NLMSG_DATA(nlh));
pid
=
nlh
->
nlmsg_pid;
/*
pid of sending process
*/
NETLINK_CB(skb).groups
=
0
;
/*
not in mcast group
*/
NETLINK_CB(skb).pid
=
0
;
/*
from kernel
*/
NETLINK_CB(skb).dst_pid
=
pid;
NETLINK_CB(skb).dst_groups
=
0
;
/*
unicast
*/
netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT);
sock_release(nl_sk
->
socket);
}
在内核模块被加载到内核,当我们运行用户程序,我们将看到下面的信息:
Received message payload: Hello you!
然后用dmesg我们可以看到内核输出:
netlink_test: received netlink message payload:
Hello you!
Multicast Communication between Kernel and Applications
这个例子中,两个应用程序在监听同一个netlink广播组.内核模块发送一个netlink消息给这个广播组,所用的应用程序都收到它,如下是用户程序代码:
#include
<
sys
/
socket.h
>
#include
<
linux
/
netlink.h
>
#define
MAX_PAYLOAD 1024 /* maximum payload size*/
struct
sockaddr_nl src_addr, dest_addr;
struct
nlmsghdr
*
nlh
=
NULL;
struct
iovec iov;
int
sock_fd;
void
main()
{
sock_fd
=
socket(PF_NETLINK, SOCK_RAW, NETLINK_TEST);
memset(
&
src_addr,
0
,
sizeof
(local_addr));
src_addr.nl_family
=
AF_NETLINK;
src_addr.nl_pid
=
getpid();
/*
self pid
*/
/*
interested in group 1<<0
*/
src_addr.nl_groups
=
1
;
bind(sock_fd, (
struct
sockaddr
*
)
&
src_addr,
sizeof
(src_addr));
memset(
&
dest_addr,
0
,
sizeof
(dest_addr));
nlh
=
(
struct
nlmsghdr
*
)malloc(
NLMSG_SPACE(MAX_PAYLOAD));
memset(nlh,
0
, NLMSG_SPACE(MAX_PAYLOAD));
iov.iov_base
=
(
void
*
)nlh;
iov.iov_len
=
NLMSG_SPACE(MAX_PAYLOAD);
msg.msg_name
=
(
void
*
)
&
dest_addr;
msg.msg_namelen
=
sizeof
(dest_addr);
msg.msg_iov
=
&
iov;
msg.msg_iovlen
=
1
;
printf(
"
Waiting for message from kernel
"
);
/*
Read message from kernel
*/
recvmsg(fd,
&
msg,
0
);
printf(
"
Received message payload: %s
"
,
NLMSG_DATA(nlh));
close(sock_fd);
}
内核代码:
#define
MAX_PAYLOAD 1024
struct
sock
*
nl_sk
=
NULL;
void
netlink_test()
{
sturct sk_buff
*
skb
=
NULL;
struct
nlmsghdr
*
nlh;
int
err;
nl_sk
=
netlink_kernel_create(NETLINK_TEST,
nl_data_ready);
skb
=
alloc_skb(NLMSG_SPACE(MAX_PAYLOAD),GFP_KERNEL);
nlh
=
(
struct
nlmsghdr
*
)skb
->
data;
nlh
->
nlmsg_len
=
NLMSG_SPACE(MAX_PAYLOAD);
nlh
->
nlmsg_pid
=
0
;
/*
from kernel
*/
nlh
->
nlmsg_flags
=
0
;
strcpy(NLMSG_DATA(nlh),
"
Greeting from kernel!
"
);
/*
sender is in group 1<<0
*/
NETLINK_CB(skb).groups
=
1
;
NETLINK_CB(skb).pid
=
0
;
/*
from kernel
*/
NETLINK_CB(skb).dst_pid
=
0
;
/*
multicast
*/
/*
to mcast group 1<<0
*/
NETLINK_CB(skb).dst_groups
=
1
;
/*
multicast the message to all listening processes
*/
netlink_broadcast(nl_sk, skb,
0
,
1
, GFP_KERNEL);
sock_release(nl_sk
->
socket);
}
我们运行用户程序:
./nl_recv &
Waiting for message from kernel
./nl_recv &
Waiting for message from kernel
然后我们加载内核模块到内核空间,会看到如下信息::
Received message payload: Greeting from kernel!
Received message payload: Greeting from kernel!
以下是一个简单的测试内核事件的应用程序:
#define
MAX_PAYLOAD 1024
struct
sockaddr_nl src_addr, dest_addr;
char
*
KernelMsg
=
NULL;
struct
iovec iov;
int
sock_fd;
struct
msghdr msg;
int
msglen;
#define
DEVICE_ADD "add"
#define
DEVICE_REMOVE "remove"
#define
DEVICE_NAME "event0"
#define
DEVICE_NAMELEN 6
void
*
DeviceManagement(
void
*
arg)
{
sock_fd
=
socket(PF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);
int
msgle;
memset(
&
src_addr,
0
,
sizeof
(src_addr));
src_addr.nl_family
=
AF_NETLINK;
src_addr.nl_pid
=
pthread_self()
<<
16
|
getpid();
src_addr.nl_groups
=
1
;
bind(sock_fd, (
struct
sockaddr
*
)
&
src_addr,
sizeof
(src_addr));
memset(
&
dest_addr,
0
,
sizeof
(dest_addr));
KernelMsg
=
(
struct
nlmsghdr
*
)malloc(MAX_PAYLOAD);
memset(KernelMsg,
0
, MAX_PAYLOAD);
iov.iov_base
=
(
void
*
)KernelMsg;
iov.iov_len
=
MAX_PAYLOAD;
msg.msg_name
=
(
void
*
)
&
dest_addr;
msg.msg_namelen
=
sizeof
(dest_addr);
msg.msg_iov
=
&
iov;
msg.msg_iovlen
=
1
;
while
(
1
)
{
//
printf("Waiting for message from kernel ");
recvmsg(sock_fd,
&
msg,
0
);
//
printf("Receved message payload: %s ", KernelMsg);
msglen
=
strlen(KernelMsg);
//
printf("Device: %s ", KernelMsg+msglen-DEVICE_NAMELEN);
if
(
!
strncmp(DEVICE_NAME, KernelMsg
+
msglen
-
DEVICE_NAMELEN, DEVICE_NAMELEN))
{
if
(
!
strncmp(DEVICE_ADD, KernelMsg, strlen(DEVICE_ADD)))
{
printf(
"
Add event0 device
"
);
USBKeyboardReady
=
1
;
}
else
if
(
!
strncmp(DEVICE_REMOVE, KernelMsg, strlen(DEVICE_REMOVE)))
{
printf(
"
Remove event0 device
"
);
USBKeyboardReady
=
0
;
}
}
}
close(sock_fd);
}
示例:一个用户进程发送数据到内核,然后通过内核发送给另一个用户进程。
内核进程:netlink-exam-kern.c
//
kernel module: netlink-exam-kern.c
#ifndef __KERNEL__
#define
__KERNEL__
#endif
#ifndef MODULE
#define
MODULE
#endif
#include
<
linux
/
config.h
>
#include
<
linux
/
module.h
>
#include
<
linux
/
netlink.h
>
#include
<
linux
/
sched.h
>
#include
<
net
/
sock.h
>
#include
<
linux
/
proc_fs.h
>
#define
BUF_SIZE 16384
#define
NL 30
static
struct
sock
*
netlink_exam_sock;
static
unsigned
char
buffer[BUF_SIZE];
static
unsigned
int
buffer_tail
=
0
;
static
int
exit_flag
=
0
;
static
DECLARE_COMPLETION(exit_completion);
static
void
recv_handler(
struct
sock
*
sk,
int
length)
{
wake_up(sk
->
sk_sleep);
}
static
int
process_message_thread(
void
*
data)
{
struct
sk_buff
*
skb
=
NULL;
struct
nlmsghdr
*
nlhdr
=
NULL;
int
len;
DEFINE_WAIT(wait);
daemonize(
"
mynetlink
"
);
while
(exit_flag
==
0
)
{
prepare_to_wait(netlink_exam_sock
->
sk_sleep,
&
wait, TASK_INTERRUPTIBLE);
schedule();
finish_wait(netlink_exam_sock
->
sk_sleep,
&
wait);
while
((skb
=
skb_dequeue(
&
netlink_exam_sock
->
sk_receive_queue))
!=
NULL)
{
nlhdr
=
(
struct
nlmsghdr
*
)skb
->
data;
if
(nlhdr
->
nlmsg_len
<
sizeof
(
struct
nlmsghdr))
{
printk(
"
Corrupt netlink message.
"
);
continue
;
}
len
=
nlhdr
->
nlmsg_len
-
NLMSG_LENGTH(
0
);
if
(len
+
buffer_tail
>
BUF_SIZE)
{
printk(
"
netlink buffer is full.
"
);
}
else
{
memcpy(buffer
+
buffer_tail, NLMSG_DATA(nlhdr), len);
buffer_tail
+=
len;
}
nlhdr
->
nlmsg_pid
=
0
;
nlhdr
->
nlmsg_flags
=
0
;
NETLINK_CB(skb).pid
=
0
;
NETLINK_CB(skb).dst_pid
=
0
;
NETLINK_CB(skb).dst_group
=
1
;
netlink_broadcast(netlink_exam_sock, skb,
0
,
1
, GFP_KERNEL);
}
}
complete(
&
exit_completion);
return
0
;
}
static
int
netlink_exam_readproc(
char
*
page,
char
**
start, off_t off,
int
count,
int
*
eof,
void
*
data)
{
int
len;
if
(off
>=
buffer_tail)
{
*
eof
=
1
;
return
0
;
}
else
{
len
=
count;
if
(count
>
PAGE_SIZE)
{
len
=
PAGE_SIZE;
}
if
(len
>
buffer_tail
-
off)
{
len
=
buffer_tail
-
off;
}
memcpy(page, buffer
+
off, len);
*
start
=
page;
return
len;
}
}
static
int
__init netlink_exam_init(
void
)
{
netlink_exam_sock
=
netlink_kernel_create(NL,
0
, recv_handler, THIS_MODULE);
if
(
!
netlink_exam_sock)
{
printk(
"
Fail to create netlink socket.
"
);
return
1
;
}
kernel_thread(process_message_thread, NULL, CLONE_KERNEL);
create_proc_read_entry(
"
netlink_exam_buffer
"
,
0444
, NULL, netlink_exam_readproc,
0
);
return
0
;
}
static
void
__exit netlink_exam_exit(
void
)
{
exit_flag
=
1
;
wake_up(netlink_exam_sock
->
sk_sleep);
wait_for_completion(
&
exit_completion);
sock_release(netlink_exam_sock
->
sk_socket);
}
module_init(netlink_exam_init);
module_exit(netlink_exam_exit);
MODULE_LICENSE(
"
GPL
"
);
编译成模块:
ifneq ($(KERNELRELEASE),)
debug
-
objs :
=
netlink
-
exam
-
kern.o
obj
-
m :
=
netlink
-
exam
-
kern1.o
CFLAGS
+=
-
w
-
Wimplicit
-
function
-
declaration
else
PWD :
=
$(shell pwd)
KVER
?=
$(shell uname
-
r)
KDIR :
=
/
lib
/
modules
/
$(KVER)
/
build
all:
$(MAKE)
-
C $(KDIR) M
=
$(PWD)
clean:
rm
-
rf .
*
.cmd
*
.o
*
.mod.c
*
.ko .tmp_versions
endif
用户发送进程:netlink-exam-user-send.c
//
application sender: netlink-exam-user-send.c
#include
<
stdio.h
>
#include
<
stdlib.h
>
#include
<
unistd.h
>
#include
<
string
.h
>
#include
<
sys
/
types.h
>
#include
<
sys
/
socket.h
>
#include
<
linux
/
netlink.h
>
#define
MAX_MSGSIZE 1024
int
main(
int
argc,
char
*
argv[])
{
FILE
*
fp;
struct
sockaddr_nl saddr, daddr;
struct
nlmsghdr
*
nlhdr
=
NULL;
struct
msghdr msg;
struct
iovec iov;
int
sd;
char
text_line[MAX_MSGSIZE];
int
ret
=
-
1
;
if
(argc
<
2
)
{
printf(
"
Usage: %s atextfilename
"
, argv[
0
]);
exit(
1
);
}
if
((fp
=
fopen(argv[
1
],
"
r
"
))
==
NULL)
{
printf(
"
File %s dosen't exist.
"
);
exit(
1
);
}
sd
=
socket(AF_NETLINK, SOCK_RAW,
30
);
memset(
&
saddr,
0
,
sizeof
(saddr));
memset(
&
daddr,
0
,
sizeof
(daddr));
saddr.nl_family
=
AF_NETLINK;
saddr.nl_pid
=
getpid();
saddr.nl_groups
=
0
;
bind(sd, (
struct
sockaddr
*
)
&
saddr,
sizeof
(saddr));
daddr.nl_family
=
AF_NETLINK;
daddr.nl_pid
=
0
;
daddr.nl_groups
=
0
;
nlhdr
=
(
struct
nlmsghdr
*
)malloc(NLMSG_SPACE(MAX_MSGSIZE));
while
(fgets(text_line, MAX_MSGSIZE, fp))
{
memcpy(NLMSG_DATA(nlhdr), text_line, strlen(text_line));
memset(
&
msg,
0
,
sizeof
(
struct
msghdr));
nlhdr
->
nlmsg_len
=
NLMSG_LENGTH(strlen(text_line));
nlhdr
->
nlmsg_pid
=
getpid();
/*
self pid
*/
nlhdr
->
nlmsg_flags
=
0
;
iov.iov_base
=
(
void
*
)nlhdr;
iov.iov_len
=
nlhdr
->
nlmsg_len;
msg.msg_name
=
(
void
*
)
&
daddr;
msg.msg_namelen
=
sizeof
(daddr);
msg.msg_iov
=
&
iov;
msg.msg_iovlen
=
1
;
ret
=
sendmsg(sd,
&
msg,
0
);
if
(ret
==
-
1
)
{
perror(
"
sendmsg error:
"
);
}
}
close(sd);
return
0
;
}
用户接收进程:netlink-exam-user-recv.c
//
application sender: netlink-exam-user-send.c
#include
<
stdio.h
>
#include
<
stdlib.h
>
#include
<
unistd.h
>
#include
<
string
.h
>
#include
<
sys
/
types.h
>
#include
<
sys
/
socket.h
>
#include
<
linux
/
netlink.h
>
#define
MAX_MSGSIZE 1024
int
main(
int
argc,
char
*
argv[])
{
FILE
*
fp;
struct
sockaddr_nl saddr, daddr;
struct
nlmsghdr
*
nlhdr
=
NULL;
struct
msghdr msg;
struct
iovec iov;
int
sd;
char
text_line[MAX_MSGSIZE];
int
ret
=
-
1
;
if
(argc
<
2
)
{
printf(
"
Usage: %s atextfilename
"
, argv[
0
]);
exit(
1
);
}
if
((fp
=
fopen(argv[
1
],
"
r
"
))
==
NULL)
{
printf(
"
File %s dosen't exist.
"
);
exit(
1
);
}
sd
=
socket(AF_NETLINK, SOCK_RAW,
30
);
memset(
&
saddr,
0
,
sizeof
(saddr));
memset(
&
daddr,
0
,
sizeof
(daddr));
saddr.nl_family
=
AF_NETLINK;
saddr.nl_pid
=
getpid();
saddr.nl_groups
=
0
;
bind(sd, (
struct
sockaddr
*
)
&
saddr,
sizeof
(saddr));
daddr.nl_family
=
AF_NETLINK;
daddr.nl_pid
=
0
;
daddr.nl_groups
=
0
;
nlhdr
=
(
struct
nlmsghdr
*
)malloc(NLMSG_SPACE(MAX_MSGSIZE));
while
(fgets(text_line, MAX_MSGSIZE, fp))
{
memcpy(NLMSG_DATA(nlhdr), text_line, strlen(text_line));
memset(
&
msg,
0
,
sizeof
(
struct
msghdr));
nlhdr
->
nlmsg_len
=
NLMSG_LENGTH(strlen(text_line));
nlhdr
->
nlmsg_pid
=
getpid();
/*
self pid
*/
nlhdr
->
nlmsg_flags
=
0
;
iov.iov_base
=
(
void
*
)nlhdr;
iov.iov_len
=
nlhdr
->
nlmsg_len;
msg.msg_name
=
(
void
*
)
&
daddr;
msg.msg_namelen
=
sizeof
(daddr);
msg.msg_iov
=
&
iov;
msg.msg_iovlen
=
1
;
ret
=
sendmsg(sd,
&
msg,
0
);
if
(ret
==
-
1
)
{
perror(
"
sendmsg error:
"
);
}
}
close(sd);
return
0
;
}
from:http://blog.youkuaiyun.com/dipperkun/archive/2007/12/19/1953526.aspx
本文详细介绍Linux内核中Netlink套接字的使用方法,包括单播和多播通信原理及应用实例,帮助读者理解内核与用户空间进程间的数据交换机制。
src_addr.nl_pid
616

被折叠的 条评论
为什么被折叠?



