使用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/


Linux-内核通信之netlink机制-详解

前言: 开发和维护内核是一件很繁杂的工作,因此,只有那些最重要或者与系统性能息息相关的代码才将其安排在内核中。其它程序,比如GUI,管理以及控制部分的代码,一般都会作为用户态程序。用户态和内核态的通讯...
  • sty23122555
  • sty23122555
  • 2016年06月05日 19:07
  • 5586

linux 内核与用户空间通信之netlink使用方法

Linux中的进程间通信机制源自于Unix平台上的进程通信机制。Unix的两大分支AT&T Unix和BSD Unix在进程通信实现机制上的各有所不同,前者形成了运行在单个计算机上的System V ...
  • HAOMCU
  • HAOMCU
  • 2012年03月20日 09:41
  • 32167

linux 内核与用户空间通信之netlink使用方法

Linux中的进程间通信机制源自于Unix平台上的进程通信机制。Unix的两大分支AT&T Unix和BSD Unix在进程通信实现机制上的各有所不同,前者形成了运行在单个计算机上的System V ...
  • freeking101
  • freeking101
  • 2016年07月05日 10:12
  • 942

linux的netlink机制

netlink作为一种用户空间和内核空间通信的机制已经有一定年头了,它不光为了内核和用户通信,还可以作为IPC机制进行进程间通信。其实netlink定义了一个框架,人们可以基于这个框架用它来做可以做的...
  • dog250
  • dog250
  • 2010年02月09日 21:16
  • 7573

linux的netlink接口详解(上)

内核版本:3.14.38 netlink是一种用于内核态和用户态进程之间进行数据传输的特殊的IPC机制。 特点:     1) 用户态采用socket风格的API     2) 除了预定义的协议类...
  • banruoju
  • banruoju
  • 2017年04月06日 11:30
  • 1173

netlink详解--以本人项目为实例

二、2.10代码用户进程配置方式: 2.1、查改内核方式的比较: 查改内核有如下几种方式: 1、  直接通过文件系统(procfs/sysfs); 2、  增加自己的系统调用; 3、  使用...
  • u010246947
  • u010246947
  • 2013年12月18日 14:40
  • 2278

linux 内核与用户空间通信之netlink使用方法

Linux中的进程间通信机制源自于Unix平台上的进程通信机制。Unix的两大分支AT&T Unix和BSD Unix在进程通信实现机制上的各有所不同,前者形成了运行在单个计算机上的System V ...
  • freeking101
  • freeking101
  • 2016年07月05日 10:12
  • 942

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

from:http://blog.chinaunix.net/uid-23069658-id-3400761.html
  • conceptcon
  • conceptcon
  • 2014年05月08日 15:36
  • 1789

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

分类: LINUX 引言          Alan Cox在内核1.3版本的开发阶段最先引入了Netlink,刚开始时Netlink是以字符驱动接口的方式提供内核与用户空间的双向数据通信...
  • daydring
  • daydring
  • 2014年04月17日 09:47
  • 1683

Netlink 内核实现分析(二):通信

Netlink 是一种用于内核与用户空间通信的IPC(Inter Process Commumicate)机制,本文主要分析内核空间和用户空间使用netlink进行通信的具体流程。...
  • luckyapple1028
  • luckyapple1028
  • 2016年04月03日 16:15
  • 3756
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:使用netlink机制实现内核空间和用户空间的双向消息通讯
举报原因:
原因补充:

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