本文转载自:http://blog.csdn.net/zcabcd123/article/details/8272360

这是一篇学习笔记,主要是对《Linux 系统内核空间与用户空间通信的实现与分析》中的源码imp2的分析。其中的源码,可以到以下URL下载:

http://www-128.ibm.com/developerworks/cn/Linux/l-netlink/imp2.tar.gz

[size=3]参考文档[/size] 
linux 系统内核空间与用户空间通信的实现与分析》 陈鑫 
http://www-128.ibm.com/developerworks/cn/linux/l-netlink/?ca=dwcn-newsletter-linux 
《在 Linux 下用户空间与内核空间数据交换的方式》 杨燚 
http://www-128.ibm.com/developerworks/cn/linux/l-kerns-usrs/

[size=3]理论篇[/size] 
在 Linux 2.4 版以后版本的内核中,几乎全部的中断过程与用户态进程的通信都是使用 netlink 套接字实现的,例如iprote2网络管 理工具,它与内核的交互就全部使用了netlink,著名的内核包过滤框架Netfilter在与用户空间的通读,也在最新版本中改变为netlink, 无疑,它将是Linux用户态与内核态交流的主要方法之一。它的通信依据是一个对应于进程的标识,一般定为该进程的 ID。当通信的一端处于中断过程时, 该标识为 0。当使用 netlink 套接字进行通信,通信的双方都是用户态进程,则使用方法类似于消息队列。但通信双方有一端是中断过程,使用方法则 不同。netlink 套接字的最大特点是对中断过程的支持,它在内核空间接收用户空间数据时不再需要用户自行启动一个内核线程,而是通过另一个软中断调 用用户事先指定的接收函数。工作原理如图:

 
  
如图所示,这里使用了软中断而不是内核线程来接收数据,这样就可以保证数据接收的实时性。 
当 netlink 套接字用于内核空间与用户空间的通信时,在用户空间的创建方法和一般套接字使用类似,但内核空间的创建方法则不同,下图是 netlink 套接字实现此类通信时创建的过程: 
  

用户空间 
用户态应用使用标准的socket与内核通讯,标准的socket API 的函数, socket(), bind(), sendmsg(), recvmsg() 和 close()很容易地应用到 netlink socket。 
为了创建一个 netlink socket,用户需要使用如下参数调用 socket():

socket(AF_NETLINK, SOCK_RAW, netlink_type)

netlink对应的协议簇是 AF_NETLINK,第二个参数必须是SOCK_RAW或SOCK_DGRAM, 第三个参数指定netlink协议类型,它可以是一个自定义的类型,也可以使用内核预定义的类型:

#define NETLINK_ROUTE          0       /* Routing/device hook                          */
#define NETLINK_W1             1       /* 1-wire subsystem                             */
#define NETLINK_USERSOCK       2       /* Reserved for user mode socket protocols      */
#define NETLINK_FIREWALL       3       /* Firewalling hook                             */
#define NETLINK_INET_DIAG      4       /* INET socket monitoring                       */
#define NETLINK_NFLOG          5       /* netfilter/iptables ULOG */
#define NETLINK_XFRM           6       /* ipsec */
#define NETLINK_SELINUX        7       /* SELinux event notifications */
#define NETLINK_ISCSI          8       /* Open-iSCSI */
#define NETLINK_AUDIT          9       /* auditing */
#define NETLINK_FIB_LOOKUP     10
#define NETLINK_CONNECTOR      11
#define NETLINK_NETFILTER      12      /* netfilter subsystem */
#define NETLINK_IP6_FW         13
#define NETLINK_DNRTMSG        14      /* DECnet routing messages */
#define NETLINK_KOBJECT_UEVENT 15      /* Kernel messages to userspace */
#define NETLINK_GENERIC        16

同样地,socket函数返回的套接字,可以交给bing等函数调用:

static int skfd;
skfd = socket(PF_NETLINK, SOCK_RAW, NL_IMP2);
if(skfd < 0)
{
      printf("can not create a netlink socket\n");
      exit(0);
}

bind函数需要绑定协议地址,netlink的socket地址使用struct sockaddr_nl结构描述:

struct sockaddr_nl
{
  sa_family_t    nl_family;
  unsigned short nl_pad;
  __u32          nl_pid;
  __u32          nl_groups;
};

成员 nl_family为协议簇 AF_NETLINK,成员 nl_pad 当前没有使用,因此要总是设置为 0,成员 nl_pid 为接 收或发送消息的进程的 ID,如果希望内核处理消息或多播消息,就把该字段设置为 0,否则设置为处理消息的进程 ID。成员 nl_groups 用于 指定多播组,bind 函数用于把调用进程加入到该字段指定的多播组,如果设置为 0,表示调用者不加入任何多播组:

struct sockaddr_nl local;
memset(&local, 0, sizeof(local));
local.nl_family = AF_NETLINK;
local.nl_pid = getpid(); /*设置pid为自己的pid值*/
local.nl_groups = 0;
/*绑定套接字*/ if(bind(skfd, (struct sockaddr*)&local, sizeof(local)) != 0)
{
printf("bind() error\n");
     return -1;
}

用户空间可以调用send函数簇向内核发送消息,如sendto、sendmsg等,同样地,也可以使用struct sockaddr_nl来描述一个对端地址,以待send函数来调用,与本地地址稍不同的是,因为对端为内核,所以nl_pid成员需要设置为0:

struct sockaddr_nl kpeer;
memset(&kpeer, 0, sizeof(kpeer));
kpeer.nl_family = AF_NETLINK;
kpeer.nl_pid = 0;
kpeer.nl_groups = 0;

另一个问题就是发内核发送的消息的组成,使用我们发送一个IP网络数据包的话,则数据包结构为“IP包头+IP数据”,同样地,netlink的消息结构是“netlink消息头部+数据”。Netlink消息头部使用
struct nlmsghdr结构来描述:

{
  __u32 nlmsg_len;   /* Length of message */
  __u16 nlmsg_type;  /* Message type*/
  __u16 nlmsg_flags; /* Additional flags */
  __u32 nlmsg_seq;   /* Sequence number */
  __u32 nlmsg_pid;   /* Sending process PID */
};

字段 nlmsg_len 指定消息的总长度,包括紧跟该结构的数据部分长度以及该结构的大小,一般地,我们使用netlink提供的宏NLMSG_LENGTH来计算这个长度,仅需向NLMSG_LENGTH宏提供要发送的数据的长度,它会自动计算对齐后的总长度:

/*计算包含报头的数据报长度*/
#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr)))
/*字节对齐*/ #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )

后面还可以看到很多netlink提供的宏,这些宏可以为我们编写netlink宏提供很大的方便。 
字段 nlmsg_type 用于应用内部定义消息的类型,它对 netlink 内核实现是透明的,因此大部分情况下设置为 0,字 段 nlmsg_flags 用于设置消息标志,对于一般的使用,用户把它设置为 0 就可以,只是一些高级应用(如 netfilter 和路 由 daemon 需要它进行一些复杂的操作),字段 nlmsg_seq 和 nlmsg_pid 用于应用追踪消息,前者表示顺序号,后者为消息来源 进程 ID。

struct msg_to_kernel		/*自定义消息首部,它仅包含了netlink的消息首部*/
{
  struct nlmsghdr hdr;
};
struct msg_to_kernel message;
memset(&message, 0, sizeof(message));
message.hdr.nlmsg_len = NLMSG_LENGTH(0); /*计算消息,因为这里只是发送一个请求消息,没有多余的数据,所以,数据长度为0*/
message.hdr.nlmsg_flags = 0;
message.hdr.nlmsg_type = IMP2_U_PID; /*设置自定义消息类型*/
message.hdr.nlmsg_pid = local.nl_pid; /*设置发送者的PID*/ 这样,有了本地地址、对端地址和发送的数据,就可以调用发送函数将消息发送给内核了:
  /*发送一个请求*/ sendto(skfd, &message, message.hdr.nlmsg_len, 0,
 (struct sockaddr*)&kpeer, sizeof(kpeer));

当发送完请求后,就可以调用recv函数簇从内核接收数据了,接收到的数据包含了netlink消息首部和要传输的数据:

/*接收的数据包含了netlink消息首部和自定义数据结构*/

struct u_packet_info
{
  struct nlmsghdr hdr;
  struct packet_info icmp_info;
};
struct u_packet_info info;
while(1)
{
    kpeerlen = sizeof(struct sockaddr_nl);
      /*接收内核空间返回的数据*/
      rcvlen = recvfrom(skfd, &info, sizeof(struct u_packet_info),
0, (struct sockaddr*)&kpeer, &kpeerlen);  
       /*处理接收到的数据*/
……
}

同样地,函数close用于关闭打开的netlink socket。程序中,因为程序一直循环接收处理内核的消息,需要收到用户的关闭信号才会退出,所以关闭套接字的工作放在了自定义的信号函数sig_int中处理:

/*这个信号函数,处理一些程序退出时的动作*/
static void sig_int(int signo)
{
  struct sockaddr_nl kpeer;
  struct msg_to_kernel message;
  memset(&kpeer, 0, sizeof(kpeer));
  kpeer.nl_family = AF_NETLINK;
  kpeer.nl_pid    = 0;
  kpeer.nl_groups = 0;
  memset(&message, 0, sizeof(message));
  message.hdr.nlmsg_len = NLMSG_LENGTH(0);
  message.hdr.nlmsg_flags = 0;
  message.hdr.nlmsg_type = IMP2_CLOSE;
  message.hdr.nlmsg_pid = getpid();
  /*向内核发送一个消息,由nlmsg_type表明,应用程序将关闭*/
  sendto(skfd, &message, message.hdr.nlmsg_len, 0, (struct sockaddr *)(&kpeer),         sizeof(kpeer));
  close(skfd);
  exit(0);
}

这个结束函数中,向内核发送一个“我已经退出了”的消息,然后调用close函数关闭netlink套接字,退出程序。

内核空间

与应用程序内核,内核空间也主要完成三件工作: 
n 创建netlink套接字 
n 接收处理用户空间发送的数据 
n 发送数据至用户空间

API函数netlink_kernel_create用于创建一个netlink socket,同时,注册一个回调函数,用于接收处理用户空间的消息:

struct sock *
netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len));

参数unit表示netlink协议类型,如NL_IMP2,参数input则为内核模块定义的netlink消息处理函数,当有消息到达这个 netlink socket时,该input函数指针就会被引用。函数指针input的参数sk实际上就是函数 netlink_kernel_create返回的struct sock指针,sock实际是socket的一个内核表示数据结构,用户态应用创建的 socket在内核中也会有一个struct sock结构来表示。

static int __init init(void)
{
  rwlock_init(&user_proc.lock); /*初始化读写锁*/
  /*创建一个netlink socket,协议类型是自定义的ML_IMP2,kernel_reveive为接受处理函数*/
  nlfd = netlink_kernel_create(NL_IMP2, kernel_receive);
  if(!nlfd) /*创建失败*/
  {
      printk("can not create a netlink socket\n");
      return -1;
  }
  /*注册一个Netfilter 钩子*/
  return nf_register_hook(&imp2_ops);
}
module_init(init);

用户空间向内核发送了两种自定义消息类型:IMP2_U_PID和IMP2_CLOSE,分别是请求和关闭。kernel_receive 函数分别处理这两种消息:

DECLARE_MUTEX(receive_sem);							/*初始化信号量*/
static void kernel_receive(struct sock *sk, int len)
{
do
    {
struct sk_buff *skb;
if(down_trylock(&receive_sem)) /*获取信号量*/
return;
/*从接收队列中取得skb,然后进行一些基本的长度的合法性校验*/
while((skb = skb_dequeue(&sk->receive_queue)) != NULL)
        {
{
struct nlmsghdr *nlh = NULL;
if(skb->len >= sizeof(struct nlmsghdr))
{
/*获取数据中的nlmsghdr 结构的报头*/
nlh = (struct nlmsghdr *)skb->data;
if((nlh->nlmsg_len >= sizeof(struct nlmsghdr))
&& (skb->len >= nlh->nlmsg_len))
{
/*长度的全法性校验完成后,处理应用程序自定义消息类型,主要是对用户PID的保存,即为内核保存“把消息发送给谁”*/
if(nlh->nlmsg_type == IMP2_U_PID) /*请求*/
{
write_lock_bh(&user_proc.pid);
user_proc.pid = nlh->nlmsg_pid;
write_unlock_bh(&user_proc.pid);
}
else if(nlh->nlmsg_type == IMP2_CLOSE) /*应用程序关闭*/
{
write_lock_bh(&user_proc.pid);
if(nlh->nlmsg_pid == user_proc.pid)
user_proc.pid = 0;
write_unlock_bh(&user_proc.pid);
}
}
}
}
kfree_skb(skb);
        }
up(&receive_sem); /*返回信号量*/
    }while(nlfd && nlfd->receive_queue.qlen);
}

因为内核模块可能同时被多个进程同时调用,所以函数中使用了信号量和锁来进行互斥。skb = skb_dequeue(&sk-& gt;receive_queue)用于取得socket sk的接收队列上的消息,返回为一个struct sk_buff的结 构,skb->data指向实际的netlink消息。 
程序中注册了一个Netfilter钩子,钩子函数是get_icmp,它截获ICMP数据包,然后调用send_to_user函数将数据发送 给应用空间进程。发送的数据是info结构变量,它是struct packet_info结构,这个结构包含了来源/目的地址两个成员。 Netfilter Hook不是本文描述的重点,略过。 
send_to_user 用于将数据发送给用户空间进程,发送调用的是API函数netlink_unicast 完成的:

int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock);

参数sk为函数netlink_kernel_create()返回的套接字,参数skb存放待发送的消息,它的data字段指向要发送的 netlink消息结构,而skb的控制块保存了消息的地址信息, 参数pid为接收消息进程的pid,参数nonblock表示该函数是否为非阻塞,如 果为1,该函数将在没有接收缓存可利用时立即返回,而如果为0,该函数在没有接收缓存可利用时睡眠。 
向用户空间进程发送的消息包含三个部份:netlink 消息头部、数据部份和控制字段,控制字段包含了内核发送netlink消息时,需要设 置的目标地址与源地址,内核中消息是通过sk_buff来管理的, linux/netlink.h中定义了NETLINK_CB宏来方便消息的地址设 置:

#define NETLINK_CB(skb)         (*(struct netlink_skb_parms*)&((skb)->cb))

例如:

NETLINK_CB(skb).pid = 0;
NETLINK_CB(skb).dst_pid = 0;
NETLINK_CB(skb).dst_group = 1;

字段pid表示消息发送者进程ID,也即源地址,对于内核,它为 0, dst_pid 表示消息接收者进程 ID,也即目标地址,如果目标为组 或内核,它设置为 0,否则 dst_group 表示目标组地址,如果它目标为某一进程或内核,dst_group 应当设置为 0。

static int send_to_user(struct packet_info *info)
{
int ret;
int size;
unsigned char *old_tail;
struct sk_buff *skb;
struct nlmsghdr *nlh;
struct packet_info *packet;
/*计算消息总长:消息首部加上数据加度*/
size = NLMSG_SPACE(sizeof(*info));
/*分配一个新的套接字缓存*/
skb = alloc_skb(size, GFP_ATOMIC);
old_tail = skb->tail;
/*初始化一个netlink消息首部*/
nlh = NLMSG_PUT(skb, 0, 0, IMP2_K_MSG, size-sizeof(*nlh));
/*跳过消息首部,指向数据区*/
packet = NLMSG_DATA(nlh);
/*初始化数据区*/
memset(packet, 0, sizeof(struct packet_info));
/*填充待发送的数据*/
packet->src = info->src;
packet->dest = info->dest;
/*计算skb两次长度之差,即netlink的长度总和*/
nlh->nlmsg_len = skb->tail - old_tail;
/*设置控制字段*/
NETLINK_CB(skb).dst_groups = 0;
/*发送数据*/
read_lock_bh(&user_proc.lock);
ret = netlink_unicast(nlfd, skb, user_proc.pid, MSG_DONTWAIT);
read_unlock_bh(&user_proc.lock);
}

函数初始化netlink 消息首部,填充数据区,然后设置控制字段,这三部份都包含在skb_buff中,最后调用netlink_unicast函数把数据发送出去。 
函数中调用了netlink的一个重要的宏NLMSG_PUT,它用于初始化netlink 消息首部:

#define NLMSG_PUT(skb, pid, seq, type, len) \
({ if (skb_tailroom(skb) < (int)NLMSG_SPACE(len)) goto nlmsg_failure; \
   __nlmsg_put(skb, pid, seq, type, len); })
static __inline__ struct nlmsghdr *
__nlmsg_put(struct sk_buff *skb, u32 pid, u32 seq, int type, int len)
{
struct nlmsghdr *nlh;
int size = NLMSG_LENGTH(len);
nlh = (struct nlmsghdr*)skb_put(skb, NLMSG_ALIGN(size));
nlh->nlmsg_type = type;
nlh->nlmsg_len = size;
nlh->nlmsg_flags = 0;
nlh->nlmsg_pid = pid;
nlh->nlmsg_seq = seq;
return nlh;
}

这个宏一个需要注意的地方是调用了nlmsg_failure标签,所以在程序中应该定义这个标签。 
在内核中使用函数sock_release来释放函数netlink_kernel_create()创建的netlink socket:

void sock_release(struct socket * sock);

程序在退出模块中释放netlink sockets和netfilter hook:

static void __exit fini(void)
{
  if(nlfd)
    {
      sock_release(nlfd->socket); /*释放netlink socket*/
    }
  nf_unregister_hook(&imp2_ops); /*撤锁netfilter 钩子*/
} 转自:
http://www.chinaunix.net/jh/4/822500.html
代码摘自:
http://www-128.ibm.com/developerworks/cn/linux/l-netlink/imp2.tar.gz
flw2兄修改了如上代码,使之可以在2.6.25上可以运行:
http://linux.chinaunix.net/bbs/viewthread.php?tid=1015818&extra=&page=2
可以在2.6.9上运行的代码:
imp2_k.c #ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/netfilter_ipv4.h>
#include <linux/inet.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/netlink.h>
#include <linux/spinlock.h>
#include <asm/semaphore.h>
#include <net/sock.h>
#include "imp2.h"
DECLARE_MUTEX(receive_sem);
static struct sock *nlfd;
struct
{
  __u32 pid;
  rwlock_t lock;
}user_proc;
static void kernel_receive(struct sock *sk, int len)
{
  do
    {
      struct sk_buff *skb;
      if(down_trylock(&receive_sem))
        return;
      while((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL)
        {
          {
            struct nlmsghdr *nlh = NULL;
            if(skb->len >= sizeof(struct nlmsghdr))
              {
                nlh = (struct nlmsghdr *)skb->data;
                if((nlh->nlmsg_len >= sizeof(struct nlmsghdr))
                  && (skb->len >= nlh->nlmsg_len))
                  {
                    if(nlh->nlmsg_type == IMP2_U_PID)
                      {
                        write_lock_bh(&user_proc.pid);
                        user_proc.pid = nlh->nlmsg_pid;
                        write_unlock_bh(&user_proc.pid);
                      }
                    else if(nlh->nlmsg_type == IMP2_CLOSE)
                      {
                        write_lock_bh(&user_proc.pid);
                        if(nlh->nlmsg_pid == user_proc.pid)
                          user_proc.pid = 0;
                        write_unlock_bh(&user_proc.pid);
                      }
                  }
              }
          }
          kfree_skb(skb);
        }
      up(&receive_sem);
    }while(nlfd && nlfd->sk_receive_queue.qlen);
}
static int send_to_user(struct packet_info *info)
{
  int ret;
  int size;
  unsigned char *old_tail;
  struct sk_buff *skb;
  struct nlmsghdr *nlh;
  struct packet_info *packet;
  size = NLMSG_SPACE(sizeof(*info));
  skb = alloc_skb(size, GFP_ATOMIC);
  old_tail = skb->tail;
  nlh = NLMSG_PUT(skb, 0, 0, IMP2_K_MSG, size-sizeof(*nlh));
  packet = NLMSG_DATA(nlh);
  memset(packet, 0, sizeof(struct packet_info));
  packet->src = info->src;
  packet->dest = info->dest;
  nlh->nlmsg_len = skb->tail - old_tail;
  NETLINK_CB(skb).dst_groups = 0;
  read_lock_bh(&user_proc.lock);
  ret = netlink_unicast(nlfd, skb, user_proc.pid, MSG_DONTWAIT);
  read_unlock_bh(&user_proc.lock);
  return ret;
 nlmsg_failure:
  if(skb)
    kfree_skb(skb);
  return -1;
}
static unsigned int get_icmp(unsigned int hook,
                             struct sk_buff **pskb,
                             const struct net_device *in,
                             const struct net_device *out,
                             int (*okfn)(struct sk_buff *))
{
  struct iphdr *iph = (*pskb)->nh.iph;
  struct packet_info info;
  if(iph->protocol == IPPROTO_ICMP)
    {
      read_lock_bh(&user_proc.lock);
      if(user_proc.pid != 0)
        {
          read_unlock_bh(&user_proc.lock);
          info.src = iph->saddr;
          info.dest = iph->daddr;
          send_to_user(&info);
        }
      else
        read_unlock_bh(&user_proc.lock);
    }
  return NF_ACCEPT;
}
static struct nf_hook_ops imp2_ops =
{
  .hook = get_icmp,
  .pf = PF_INET,
  .hooknum = NF_IP_PRE_ROUTING,
  .priority = NF_IP_PRI_FILTER -1,
};
static int __init init(void)
{
  rwlock_init(&user_proc.lock);
  nlfd = netlink_kernel_create(NL_IMP2, kernel_receive);
  if(!nlfd)
    {
      printk("can not create a netlink socket\n");
      return -1;
    }
  return nf_register_hook(&imp2_ops);
}
static void __exit fini(void)
{
  if(nlfd)
    {
      sock_release(nlfd->sk_socket);
    }
  nf_unregister_hook(&imp2_ops);
}
module_init(init);
module_exit(fini);
可以在2.6.29上运行的代码:
imp2_k.c

#ifndef __KERNEL__
#define __KERNEL__
#endif

#ifndef MODULE
#define MODULE
#endif

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/netfilter_ipv4.h>
#include <linux/inet.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/netlink.h>
#include <linux/spinlock.h>
#include <linux/semaphore.h>
#include <net/sock.h>
#include "imp2.h"

DECLARE_MUTEX(receive_sem);

static struct sock *nlfd;

struct {
    __u32 pid;
    rwlock_t lock;
}user_proc;

static void kernel_receive(struct sk_buff *skb)
{
    struct nlmsghdr *nlh = NULL;
    //printk("%s: skb->user: %d\n", __func__, atomic_read(&skb->users)); return;

if(down_trylock(&receive_sem))
        return;

if(skb->len < sizeof(struct nlmsghdr))
        goto out;

nlh = nlmsg_hdr(skb);

if((nlh->nlmsg_len >= sizeof(struct nlmsghdr))
            && (skb->len >= nlh->nlmsg_len)) {
        if(nlh->nlmsg_type == IMP2_U_PID) {
            write_lock_bh(&user_proc.lock);
            user_proc.pid = nlh->nlmsg_pid;
            write_unlock_bh(&user_proc.lock);
        } else if(nlh->nlmsg_type == IMP2_CLOSE) {
            write_lock_bh(&user_proc.lock);
            if(nlh->nlmsg_pid == user_proc.pid)
                user_proc.pid = 0;
            write_unlock_bh(&user_proc.lock);
        }
    }

//kfree_skb(skb);

out:
    up(&receive_sem);
}

static int send_to_user(struct packet_info *info)
{
    int ret;
    int size;
    unsigned char *old_tail;
    struct sk_buff *skb;
    struct nlmsghdr *nlh;
    struct packet_info *packet;

size = NLMSG_SPACE(sizeof(*info));

skb = alloc_skb(size, GFP_ATOMIC);
    old_tail = skb->tail;

nlh = NLMSG_PUT(skb, 0, 0, IMP2_K_MSG, size-sizeof(*nlh));
    packet = NLMSG_DATA(nlh);
    memset(packet, 0, sizeof(struct packet_info));

packet->src = info->src;
    packet->dest = info->dest;

nlh->nlmsg_len = skb->tail - old_tail;
    NETLINK_CB(skb).dst_group = 0;

read_lock_bh(&user_proc.lock);
    ret = netlink_unicast(nlfd, skb, user_proc.pid, MSG_DONTWAIT);
    read_unlock_bh(&user_proc.lock);

return ret;

nlmsg_failure:
    if(skb)
        kfree_skb(skb);
    return -1;
}

static unsigned int get_icmp(unsigned int hook,
        struct sk_buff *pskb,
        const struct net_device *in,
        const struct net_device *out,
        int (*okfn)(struct sk_buff *))
{
    printk("%s\n", __func__);
    struct iphdr *iph = (struct iphdr *)skb_network_header(pskb);
    struct packet_info info;

if(iph->protocol == IPPROTO_ICMP) {
        read_lock_bh(&user_proc.lock);
        if(user_proc.pid != 0) {
            read_unlock_bh(&user_proc.lock);
            info.src = iph->saddr;
            info.dest = iph->daddr;
            send_to_user(&info);
        }
        else
            read_unlock_bh(&user_proc.lock);
    }

return NF_STOLEN;
}

static struct nf_hook_ops imp2_ops =
{
    .hook = get_icmp,
    .pf = PF_INET,
    .hooknum = NF_INET_PRE_ROUTING,
    .priority = NF_IP_PRI_FIRST,
};

static int __init init(void)
{
    rwlock_init(&user_proc.lock);

//nlfd = netlink_kernel_create(NL_IMP2, kernel_receive);

nlfd = netlink_kernel_create(&init_net, NL_IMP2, 0, kernel_receive, NULL,THIS_MODULE);

if(!nlfd) {
        printk("can not create a netlink socket\n");
        return -1;
    }
    return nf_register_hook(&imp2_ops);
}

static void __exit fini(void)
{
    if(nlfd) {
        sock_release(nlfd->sk_socket);
    }
    nf_unregister_hook(&imp2_ops);
}

module_init(init);
module_exit(fini);

linux用户态和内核态通信之netlink机制【转】的更多相关文章

  1. Linux用户态与内核态通信的几种方式

    本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. Linux 用 ...

  2. Linux 用户态与内核态的交互【转载】

    Linux 用户态与内核态的交互  在 Linux 2.4 版以后版本的内核中,几乎全部的中断过程与用户态进程的通信都是使用 netlink 套接字实现的,例如iprote2网络管理工具,它与内核的交 ...

  3. Linux用户态和内核态

    究竟什么是用户态,什么是内核态,这两个基本概念以前一直理解得不是很清楚,根本原因个人觉得是在于因为大部分时候我们在写程序时关注的重点和着眼的角度放在了实现的功能和代码的逻辑性上,先看一个例子: 1)例 ...

  4. Linux探秘之用户态与内核态

    一. Unix/Linux的体系架构 如上图所示,从宏观上来看,Linux操作系统的体系架构分为用户态和内核态(或者用户空间和内核).内核从本质上看是一种软件——控制计算机的硬件资源,并提供上层应用程 ...

  5. Linux 用户态和内核态

    1.特权级特权级用来管理和控制程序执行.如Intel x86架构的CPU,有0~3四个特权级,0级最高,3级最低.硬件在执行每条指令时都会检查指令具有的特权级.硬件提供了特权级使用机制,对操作系统来说 ...

  6. 【转载】 Linux用户态和内核态

    [说明]转载自 http://my.oschina.net/liubin/blog/27795 究竟什么是用户态,什么是内核态,这两个基本概念以前一直理解得不是很清楚,根本原因个人觉得是在于因为大部分 ...

  7. Linux操作系统学习_用户态与内核态之切换过程

    因为操作系统的很多操作会消耗系统的物理资源,例如创建一个新进程时,要做很多底层的细致工作,如分配物理内存,从父进程拷贝相关信息,拷贝设置页目录.页表等,这些操作显然不能随便让任何程序都可以做,于是就产 ...

  8. 【Linux 系统】Linux探秘之用户态与内核态

    一. Unix/Linux的体系架构 如上图所示,从宏观上来看,Linux操作系统的体系架构分为用户态和内核态(或者用户空间和内核).内核从本质上看是一种软件——控制计算机的硬件资源,并提供上层应用程 ...

  9. (转)linux用户态和内核态理解

    原文:https://blog.csdn.net/buptapple/article/details/21454167 Linux探秘之用户态与内核态-----------https://www.cn ...

随机推荐

  1. 扩增子分析QIIME2-3数据导出Exporting data

    # 激活工作环境 source activate qiime2-2017.8 # 建立工作目录 mkdir -p qiime2-exporting-tutorial cd qiime2-exporti ...

  2. java jvm eclipse 性能调优

    低配配置 -Dfile.encoding=UTF-8-Xms960m-Xmx960m-Xmn384m-Xverify:none-Xss256k-XX:MaxTenuringThreshold=2-XX ...

  3. mysql外键是多个id组成的字符串,查询方法

    借鉴:mysql使用instr达到in(字符串)的效果 结论:select * from 表名where INSTR(CONCAT(字符串),CONCAT(表id)) 问题来源:一表中的某字段是另一表 ...

  4. 【2018百度之星资格赛】 A 问卷调查 - 位运算&动规

    题目地址:http://acm.hdu.edu.cn/showproblem.php?pid=6344 参考博客:在此感谢http://www.cnblogs.com/LQLlulu/p/941923 ...

  5. UVA - 11536 Smallest Sub-Array(尺取法)

    题目: 思路: 读完题之后第一时间想到的是尺取法来做这个题,结果让自己写写崩了,还是练得少!! 到网上搜了一下学习了大佬的标记方法,用一个变量来判断是不是都已经出现,要比每次都判断一下快超多. 代码: ...

  6. I Think I Need a Houseboat POJ - 1005(数学)

    题目大意 在二维坐标内选定一个点,问你当洪水以半圆形扩散且每年扩散50单位,哪一年这个点被被洪水侵蚀? 解法 代码 #include <iostream> #include <cst ...

  7. java解析从接口获取的json内容并写到excle(只写与标题匹配的值,并非把所有的接口返回值都写进去)

    需求:从接口中获取的一个json数组中有多个对象,每个对象中的值并非都需要,只需查出标题中的几项对应的值即可.且还需要按某个字段排序后依次写到excel 实现方法如下: package jansonD ...

  8. Linux之sed:删除某行以及替换

    sed是一个很好的文件处理工具,本身是一个管道命令,主要是以行为单位进行处理,可以将数据行进行替换.删除.新增.选取等特定工作,下面先了解一下sed的用法sed命令行格式为:         sed ...

  9. js获取昨天,最近7天,最近30天通用方法

    function formatDate (val) { // 格式化时间 let start = new Date(val) let y = start.getFullYear() let m = ( ...

  10. dstat系统分析工具的使用

    1.安装 方法一:yum #yum install -y dstat 方法二:rpm 官网下载地址: http://dag.wieers.com/rpm/packages/dstat #wget ht ...