使用netlink机制实现内核空间和用户空间的双向消息通讯

原创 2012年03月29日 00:08:03

linux内核2.6版本中提供的各种用户空间和内核空间的通讯机制中, 只有netlink机制能够提供类似于Windows内核中事件通知机制类似的通信能力: 既可以从内核空间主动发消息给用户空间(Windows上是KeSetEvent; linux上是netlink_unicast/netlink_broadcast), 也可以在用户空间阻塞等待唤醒(Windows是WaitForSingleObject/WaitForMultipleObjects; linux上是recvfrom/recvmsg/select).

以下是使用netlink机制实现内核空间和用户空间的双向消息通讯功能的模板代码:

1.内核空间实现netlink接收消息功能

#include <linux/skbuff.h>
#include <net/sock.h>
#include <linux/netlink.h>

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 <sys/socket.h>
#include <netinet/in.h>
#include <linux/netlink.h>

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/


相关文章推荐

netlink内核与用户空间双向通信

一开始学习netlink时找了一些附有完整代码的例子,但是在自己的机器上跑不起来。由于不擅长看内核源码,只好在各个论坛上找教程帖子对比着看,后来终于发现问题,从2.6.24开始,linux内部对net...

Linux Netlink 内核与用户间进行双向数据传输

1. Netlink简介     Netlink 是一种特殊的 socket,它是一种在内核与用户间进行双向数据传输的一种方式,用户态应用使用标准的 socket API 就可以使用 Netli...
  • jack_a8
  • jack_a8
  • 2015年01月30日 11:58
  • 1780

内核空间与用户空间通讯之NetLink机制(上)

Alan Cox在内核1.3版本的开发阶段最先引入了Netlink,刚开始时Netlink是以字符驱动接口的方式提供内核与用户空间的双向数据通信;随后,在2.1内核开发过程中,Alexey Kuzne...

用户空间和内核空间通讯之【Netlink 】

今天我们来动手演练一下Netlink的用法,看看它到底是如何实现用户-内核空间的数据通信的。我们依旧是在2.6.21的内核环境下进行开发。       在文件里包含了Netlink协议簇已经定义好的...

用户空间和内核空间通讯之【Netlink 中】

今天我们来动手演练一下Netlink的用法,看看它到底是如何实现用户-内核空间的数据通信的。我们依旧是在2.6.21的内核环境下进行开发。       在文件里包含了Netlink协议簇已经定义好的...

用户空间和内核空间通讯之【Netlink 上】

引言          Alan Cox在内核1.3版本的开发阶段最先引入了Netlink,刚开始时Netlink是以字符驱动接口的方式提供内核与用户空间的双向数据通信;随后,在2.1内核开发过程中...

用户空间和内核空间通讯之【Netlink 上、中、下】

from:http://blog.chinaunix.net/uid-23069658-id-3400761.html

用户空间和内核空间通讯之【Netlink 下】

关于Netlink多播机制的用法         在上一篇博文中我们所遇到的情况都是用户空间作为消息进程的发起者,Netlink还支持内核作为消息的发送方的情况。这一般用于内核主动向用户空间报告...

用户与内核空间数据交换的方式(9)-netlink

原文:http://www.embeddedlinux.org.cn/html/yingjianqudong/201304/17-2545.html Netlink 是一种特殊的 s...

使用netlink机制在内核进程和用户空间进程通信

内核模块代码: netlink_drv.c #include #include #include #include #include #include ...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:使用netlink机制实现内核空间和用户空间的双向消息通讯
举报原因:
原因补充:

(最多只允许输入30个字)