linux内核2.6版本中提供的各种用户空间和内核空间的通讯机制中, 只有netlink机制能够提供类似于Windows内核中事件通知机制类似的通信能力: 既可以从内核空间主动发消息给用户空间(Windows上是KeSetEvent; linux上是netlink_unicast/netlink_broadcast), 也可以在用户空间阻塞等待唤醒(Windows是WaitForSingleObject/WaitForMultipleObjects; linux上是recvfrom/recvmsg/select).
以下是使用netlink机制实现内核空间和用户空间的双向消息通讯功能的模板代码:
1.内核空间实现netlink接收消息功能
#include
#include
#include
struct {
u32 pid;
u32 gid;
rwlock_t lock;
} user_proc;
static struct sock *nlfd;
static DEFINE_MUTEX(nl_demo_mutex);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 32)
static void nl_demo_rcv(struct sk_buff *skb)
{
struct nlmsghdr *nlh;
struct nl_krnl_msg *msg; //发给内核空间的消息数据结构
if (!skb) {
return;
}
mutex_lock(&nl_demo_mutex);
nlh = (struct nlmsghdr *)skb->data;
if (skb->len <= NLMSG_LENGTH(sizeof(*msg))) {
msg = (struct nl_krnl_msg *)NLMSG_DATA(nlh);
if ((nlh->nlmsg_len <= NLMSG_LENGTH(sizeof(*msg))) && (skb->len <= nlh->nlmsg_len)) {
if (nlh->nlmsg_type == NLDEMO_U_PID) { //获取用户空间进程的PID和多播组ID
write_lock_bh(&user_proc.lock);
user_proc.pid = nlh->nlmsg_pid;
user_proc.gid = msg->gid;
write_unlock_bh(&user_proc.lock);
} else if (nlh->nlmsg_type == NLDEMO_CLOSE) { //关闭netlink通信功能
write_lock_bh(&user_proc.lock);
if (nlh->nlmsg_pid == user_proc.pid) {
user_proc.pid = 0;
user_proc.gid = 0;
}
write_unlock_bh(&user_proc.lock);
}
}
}
mutex_unlock(&nl_demo_mutex);
}
#else
static void nl_demo_rcv(struct sock *sk, int len)
{
do {
struct sk_buff *skb;
struct nl_krnl_msg *msg;
mutex_lock(&nl_demo_mutex);
while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) {
struct nlmsghdr *nlh = NULL;
if (skb->len <= NLMSG_LENGTH(sizeof(*msg))) {
nlh = (struct nlmsghdr *)skb->data;
msg = (struct nl_krnl_msg *)NLMSG_DATA(nlh);
if ((nlh->nlmsg_len <= NLMSG_LENGTH(sizeof(*msg))) && (skb->len <= nlh->nlmsg_len)) {
if (nlh->nlmsg_type == NLDEMO_U_PID) { //获取用户空间进程的PID和多播组ID
write_lock_bh(&user_proc.lock);
user_proc.pid = nlh->nlmsg_pid;
user_proc.gid = msg->gid;
write_unlock_bh(&user_proc.lock);
} else if (nlh->nlmsg_type == NLDEMO_CLOSE) { //关闭netlink通信功能
write_lock_bh(&user_proc.lock);
if (nlh->nlmsg_pid == user_proc.pid) {
user_proc.pid = 0;
user_proc.gid = 0;
}
write_unlock_bh(&user_proc.lock);
}
}
}
kfree_skb(skb);
}
mutex_unlock(&nl_demo_mutex);
} while (nlfd && nlfd->sk_receive_queue.qlen);
}
#endif
static int __init nl_demo_init(void)
{
rwlock_init(&user_proc.lock);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 32)
nlfd = netlink_kernel_create(&init_net, NL_DEMO_PROTO, NL_DEMO_GROUP, nl_kmec_rcv, &nl_demo_mutex, THIS_MODULE);
#else
nlfd = netlink_kernel_create(NL_DEMO_PROTO, NL_DEMO_GROUP, nl_kmec_rcv, THIS_MODULE);
#endif
if (!nlfd) {
return -1;
}
return 0;
}
2.用户空间创建netlink消息接收线程
#include
#include
#include
void *nl_rcv_daemon(void * param)
{
struct sockaddr_nl local;
struct sockaddr_nl kpeer;
int kpeerlen;
struct nlmsghdr *unlhdr = NULL;
struct nl_usr_msg *umsg; //从内核空间接收的消息
int rcvlen = 0;
skfd = socket(AF_NETLINK, SOCK_RAW, NL_KMEC_PROTO);
if(skfd < 0) {
return (void*)-1;
}
memset(&local, 0, sizeof(local));
local.nl_family = AF_NETLINK;
local.nl_pid = getpid();
local.nl_groups = NL_DEMO_GROUP; //0为单播, 非0为组播; 支持多组播, 传递多个GID的mask即可
if(bind(skfd, (struct sockaddr*)&local, sizeof(local)) != 0) {
return (void*)-1;
}
//receive msg from kernel
unlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(sizeof(*umsg)));
umsg = (struct nl_usr_msg *)NLMSG_DATA(unlhdr);
//receive msg from kernel
while(!g_bIsDone)
{
memset(unlhdr, 0, NLMSG_SPACE(sizeof(*umsg)));
kpeerlen = sizeof(struct sockaddr_nl);
rcvlen = recvfrom(skfd, unlhdr, NLMSG_LENGTH(sizeof(*umsg)),
0, (struct sockaddr*)&kpeer, &kpeerlen);
if (rcvlen == NLMSG_LENGTH(sizeof(*umsg))) {
//todo: process the msg here
}
}
return (void*)0;
}
int nl_demo_init()
{
//create thread
if (pthread_create(&nl_daemon_thread, NULL, nl_rcv_daemon, NULL) != 0) {
return -1;
} else {
return 0;
}
}
3.用户空间初始化netlink通讯的PID和GID
int nl_init_pid()
{
struct nl_krnl_msg kmsg; //发往内核空间的消息
kmsg.gid = NL_DEMO_GROUP; //netlink机制的GID
return nl_send_msg(NLDEMO_U_PID, &kmsg); //初始化netlink机制的PID和GID
}
4.用户空间向内核空间发送消息
int nl_send_msg(uint16_t nlmsg_type, struct nl_krnl_msg *kmsg)
{
struct sockaddr_nl kpeer;
int ret, kpeerlen;
struct nlmsghdr *knlhdr = NULL;
int sendlen = 0;
//send msg to kernel
memset(&kpeer, 0, sizeof(kpeer));
kpeer.nl_family = AF_NETLINK;
kpeer.nl_pid = 0;
kpeer.nl_groups = 0;
knlhdr = (struct nlmsghdr *)calloc(1, NLMSG_SPACE(sizeof(*kmsg)));
knlhdr->nlmsg_len = NLMSG_LENGTH(sizeof(*kmsg));
knlhdr->nlmsg_flags = 0;
knlhdr->nlmsg_seq = 0;
knlhdr->nlmsg_type = nlmsg_type;
knlhdr->nlmsg_pid = getpid();
memcpy((struct nl_krnl_msg *)NLMSG_DATA(knlhdr), kmsg, sizeof(*kmsg));
ret = sendto(skfd, knlhdr, knlhdr->nlmsg_len, 0,
(struct sockaddr*)&kpeer, sizeof(kpeer));
free(knlhdr);
return ret;
}
5.内核空间向用户空间发送消息
int nl_demo_send(struct nl_usr_msg *msg)
{
int ret;
int size;
u32 pid, gid;
unsigned char *old_tail;
struct sk_buff *skb;
struct nlmsghdr *nlh;
struct nl_usr_msg *lmsg; //用户空间消息数据结构
size = NLMSG_SPACE(sizeof(*msg));
skb = alloc_skb(size, GFP_ATOMIC);
if (!skb) {
return -1;
}
old_tail = skb->tail;
/*
* 填写数据报相关信息
*/
nlh = NLMSG_PUT(skb, 0, 0, NLDEMO_K_EVT, sizeof(*msg));
lmsg = NLMSG_DATA(nlh);
memset(lmsg, 0, sizeof(*lmsg));
/*
* 传输到用户空间的数据
*/
*lmsg = *msg;
/*
* 计算经过字节对其后的数据实际长度
*/
nlh->nlmsg_len = skb->tail - old_tail;
read_lock_bh(&user_proc.lock);
pid = user_proc.pid;
gid = user_proc.gid;
read_unlock_bh(&user_proc.lock);
NETLINK_CB(skb).dst_group = gid;
if (gid == 0) {
ret = netlink_unicast(nlfd, skb, pid, MSG_DONTWAIT); //单播
} else {
ret = netlink_broadcast(nlfd, skb, pid, gid, GFP_ATOMIC); //GFP_KERNEL, 多播
}
return ret;
nlmsg_failure: /* 若发送失败,则撤销套接字缓存 */
if (skb)
kfree_skb(skb);
return -1;
}
6.用户空间关闭netlink通讯
int nl_demo_close()
{
void *ret;
struct nl_krnl_msg kmsg;
kmsg.gid = NL_DEMO_GROUP;
g_bIsDone = TRUE;
nl_send_msg(NLDEMO_CLOSE, &kmsg); //关闭netlink通讯
pthread_join(nl_daemon_thread, &ret);
close(skfd);
return 0;
}
7.内核空间撤销netlink通讯
static void __exit nl_demo_uninit(void)
{
if (nlfd) {
sock_release(nlfd->sk_socket);
}
}
参考资料:
http://www.ibm.com/developerworks/cn/linux/l-netlink/?ca=dwcn-newsletter-linux
http://www.ibm.com/developerworks/cn/linux/l-kerns-usrs/