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

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/


你可能感兴趣的:(Linux内核)