首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 操作系统 > UNIXLINUX >

linux学习札记:netlink实践演练

2013-03-27 
linux学习笔记:netlink实践演练内核和用户空间之间存在如下交互手段:1.内核启动参数 2.模块参数与 3.sysfs

linux学习笔记:netlink实践演练

内核和用户空间之间存在如下交互手段:

1.内核启动参数 2.模块参数与 3.sysfs、4.sysctl、5.系统调用、6.netlink、7.procfs、8.seq_file、9.debugfs 10.relayfs

另外 call_usermodehelper 可以从内核发起用户态的应用程序运行

  其中netlink作为一种进程之间的通讯手段 ,和其他内核与用户空间的通讯手段比较,有很大的优势:

 1.  新增通讯通道的灵活性:

     netlink提供了一套完善的机制,用户既可以静态的增加通讯通道,也可以动态的创建通讯通道,这样用户可以很灵活的根据自己的需要来定制和开发。

  2. 丰富的特性支持:

     netlink可以支持异步双工, 其他机制只支持用户到内核单向的通道,或者只支持内核到用户的单向通道,netlink支持对称的双向通道。

      同时neilink支持单点传输和多点传输,这些优势都是其他通讯机制所不具备的。

 3. 传输效率比较高:  

     和其他用户向内核通讯手段一样,netlink也是借助某些系统调用接口实现的,并且netlink的目标数据接收操作是直接在软中断里面执行的,比有些在内核开辟线程接收数据的方式要快。

 4. 易于扩展:

     内核已为netlink提供的动态机制扩展,新增一个应用通道非常方便,只需要修改少量代码。

      netlink充分体现了linux开发的宗旨:“提供机制而不是策略”,“do one thing and do it well”, 从内核版本的演进历程看来,同一类型的机制,linux提供的功能越来越强大,给用户的选择空间也是越来越丰富。

从学习的角度出发,这里使用静态方式新增了一个netlink通道,并实现了一个用户态和内核态通讯的双向通讯的样例,设计如下:

       user                                                    kernel

          |                                                                |

        send    -> "hello from usr"->  receive and print 

           |                                                               |

 receive and print  < -"hello from usr " <-send

           |                                                               |

         exit

 如上图所述,由用户首先发起一个问候消息给内核,内核收到这个消息以后返回一个问候消息给用户, 以下通过代码来分析netlink的实现:

一、内核部分代码:

1. 头文件和静态申明

这里包含了必要的头文件, 新增了一个 netlink协议号,这个协议号和内核自定义的NETLINK_GENERIC是同一类型,应该定义在<linux/netlink.h>中,为了方便显示放到了这里。

新增内核源码文件 eknetlink.c :

/*kernel example code of netlink*/#include <linux/netlink.h>#include <linux/module.h>#include <linux/skbuff.h>#include <linux/init.h>#include <linux/netdevice.h>#include <linux/netfilter.h>#include <linux/spinlock.h>#include <linux/netlink.h>#include <net/sock.h>#include <net/flow.h>#define NETLINK_EXAMPLE31  /*新增netlink协议号*//*本netlink过滤类型*/enum nf_eknetlink_hooks {    NF_EKNETLINK_IN,                     NF_EKNETLINK_OUT,    NF_EKNETLINK_NUMHOOKS};#define TFR     printk      /*trace function routine*/static struct sock *eknl = NULL;    /*模块内部全局的netlink套接字*/static DEFINE_MUTEX(eknl_mutex);  /*多线程互斥, 必需,有可能多个用户进程(如多个shell窗口)同时向本模块发消息*/static void eknl_lock(void){    mutex_lock(&eknl_mutex);}static void eknl_unlock(void){    mutex_unlock(&eknl_mutex);}

 

2. 内核netlink初始化

   1) .netlink_kernel_create 为 NETLINK_EXAMPLE协议创建了一个套接字。

   2) &init_net 使用当前的网络命名空间。

   3) eknetlink_rcv 使用这个处理用户发送过来的soket数据。

   4) nf_register_hook 注册一个数据包过滤函数。 

static int __init eknetlink_init(void){    int rv = 0;    printk("example kernel netlink_init.\n");    eknl = netlink_kernel_create(&init_net, NETLINK_EXAMPLE, 0,                      eknetlink_rcv, NULL, THIS_MODULE);    if (!eknl) {        printk(KERN_ERR "cannot initialize nfnetlink!\n");        return -1;    }    rv = nf_register_hook(&eknl_ops);    if (rv) {        netlink_kernel_release(eknl);    }    return rv;}static void __exit eknetlink_exit(void){    printk("Removing example kernel NETLINK layer.\n");    netlink_kernel_release(eknl);    return;}module_init(eknetlink_init);module_exit(eknetlink_exit);MODULE_AUTHOR("monan");MODULE_LICENSE("GPL");


3. 内核netlink消息接收函数

/*netlink是基于socket套接字实现的通讯,所以netlink接收到的原始数据是socket数据类型,由struct sk_buff定义,函数中的netlink_rcv_sk()会通过nlh = nlmsg_hdr(skb);解析sk_buff,获取socket数据中的netlink的数据(由struct nlmsghdr定义),并交给eknetlink_rcv_msg,让我们自行处理netlink消息*/static void eknetlink_rcv(struct sk_buff *skb){    TFR("eknetlink_rcv enter.skb(0x%x)\n", (unsigned int) skb);    eknl_lock();    netlink_rcv_skb(skb, &eknetlink_rcv_msg);    eknl_unlock();    TFR("eknetlink_rcv exit.\n");}

/*这里开始解析netlink数据:struct nlmsghdr *nlh */

static int eknetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh){    char* str_buf;    int ret;        TFR("eknetlink_rcv_msg enter.\n");    printk("nlmsg_len(%d) nlmsg_type(0x%x) nlmsg_flags(0x%x) nlmsg_seq(%d) nlmsg_pid(0x%x)\n",    nlh->nlmsg_len, nlh->nlmsg_type, nlh->nlmsg_flags, nlh->nlmsg_seq, nlh->nlmsg_pid);   /*对接收数据进行过滤处理*/   ret = NF_HOOK(AF_NETLINK, NF_EKNETLINK_IN, skb, NULL, NULL, eknetlink_okfn);   if (NF_ACCEPT !=  ret)   {        printk("out NF_HOOK not accept (%d)!\n", ret);        return -1;   }    /*       netlink数据中的用户数据(这里即用户控件发来的"hello kernel"字符串),是netlink消息数据中消息头(struct nlmsghdr)后面紧接着的部分,        长度由struct nlmsghdr.nlmsg_len描述,如果只有消息头没有用户数据,则struct nlmsghdr.nlmsg_len 为0。        */    if (nlh->nlmsg_len > 0) {        str_buf = kmalloc(nlh->nlmsg_len, GFP_KERNEL);         memcpy(str_buf, NLMSG_DATA(nlh), nlh->nlmsg_len);        str_buf[nlh->nlmsg_len - 1] = '\0';        printk("Message received(%d):%s\n", nlh->nlmsg_pid, str_buf) ;        kfree(str_buf);        /*nlh->nlmsg_pid是发送者的用户进程ID,传递,用于描述内核返回消息时的发送对象*/        if(!eknetlink_sendmsg("From kernel: hello user!", nlh->nlmsg_pid)){            printk("eknetlink_rcv_msg send fail. \n");        }    }    TFR("eknetlink_rcv_msg exit.\n");    return 0;}

4. 内核netlink消息发送函数:

/*  message: 要发送的消息字符串  pid :发送的目标,用户进程ID*/int eknetlink_sendmsg(char *message, int pid){    struct sk_buff *skb;    struct nlmsghdr *nlh;    int msize = 0;    int ret;        if(!message || !eknl)    {        return -1;    }    msize = strlen(message) + 1;       /* nlmsg_new 会新申请一个socket buffer ,其大小为        socket消息头大小+  netlink 消息头大小+ 用户消息大小*/     skb = nlmsg_new(msize, GFP_KERNEL);    if(!skb)    {        printk(KERN_ERR "eknetlink_sendnlmsg:alloc_skb_1 error\n");        return -1;    }        nlh = nlmsg_put(skb,0,0,0,msize,0);   /*填充部分netlink消息头*/    NETLINK_CB(skb).pid = 0;        /*描述发送者ID,这里发送者是内核,填0*/    NETLINK_CB(skb).dst_group = 0;    memcpy(NLMSG_DATA(nlh), message, msize);/*填充用户区数据*/    printk("Message send  '%s'.\n",(char *)NLMSG_DATA(nlh));    ret =  NF_HOOK(AF_NETLINK, NF_EKNETLINK_OUT, skb, NULL, NULL, eknetlink_okfn);    if (NF_ACCEPT !=  ret)    {         printk("out NF_HOOK not accept (%d)!\n", ret);         return -1;    }    /*单播消息,目标用户态pid*/    /*需要特别注意的是: skb申请的空间会在这里面释放,     所以不能重复调用此接口发送同一个skb,会造成严重后果*/   return netlink_unicast(eknl, skb, pid, MSG_DONTWAIT); }

 

5. 内核netlink消息过滤:

netfilter 提供了一种过滤机制,上文在eknetlink_init  中 调用nf_register_hook注册了一个过滤钩子,即可以对收发消息进行截取或者过滤。

这个机制不是netlink独有的,是socket网络代码的一部分,但是也可以修改后被netlink借过来使用。

需要在本模块的消息接收和发送接口中主动调用NF_HOOK,这个钩子才能被执行。

查看宏 NF_HOOK 的定义可知:只有当eknl_hook()和eknetlink_okfn()都返回NF_ACCEPT,此消息包才会判定为允许接收。

static unsigned int eknl_hook(unsigned int hook,            struct sk_buff *skb,            const struct net_device *in,            const struct net_device *out,            int (*okfn)(struct sk_buff *)){        TFR("eknl_hook .\n");    return NF_ACCEPT;    /*钩子回调,返回NF_ACCEPT表示数据允许接收*/}
int eknetlink_okfn (struct sk_buff * skb){    TFR("eknetlink_okfn .skb(0x%x)\n", (unsigned int)skb);    return NF_ACCEPT; /*钩子回调的回调,返回NF_ACCEPT表示数据允许接收,*/}
static struct nf_hook_ops eknl_ops __read_mostly = {    .hook       = eknl_hook,    .pf         = AF_NETLINK,       /*设定这个值的同时需要修改NFPROTO_NUMPROTO, 否则nf_register_hook访问nf_hooks会溢出,系统一直起不来。*/    .hooknum    = NF_EKNETLINK_IN,    .priority   = 0,    .owner      = THIS_MODULE,};

修改NFPROTO_NUMPROTO,在文件netfilter.h中:

enum {NFPROTO_UNSPEC =  0,NFPROTO_IPV4   =  2,NFPROTO_ARP    =  3,NFPROTO_BRIDGE =  7,NFPROTO_IPV6   = 10,NFPROTO_DECNET = 12,        NFPROTO_NETLINK  = 16 , /*add for netlink study*/NFPROTO_NUMPROTO,};

 6.  新增编译脚本并编译内核:

Kconfig

config EKNETLINK     tristate "Netlink study example."     default n     help     Netlink example for study, this is kernel  part , cooperation with net link usr example part. 

Makefile

obj-$(CONFIG_EKNETLINK) += eknetlink.o

make menuconfig 打开eknetlink模块编译

make -j4 编译内核

至此内核的netlink新增通道接收和发送流程描述完成,接下来记录用户态的netlink新增通道实现。

 

二、用户部分代码:

在用户态新增源码文件

external/netlink/eunetlink.c

1) 头文件和静态申明

/*user example code for netlink study*/#include <asm/types.h>#include <sys/socket.h>#include <unistd.h>#include <err.h>#include <stdio.h>#include <netinet/in.h>#include <linux/netlink.h>#include <linux/rtnetlink.h>

enum {    EUNETLINK_MSG_OPEN =  NLMSG_MIN_TYPE, /*比NLMSG_MIN_TYPE 小的类型是系统保留的,不能使用,                                      否则消息在内核回调中会被netlink_rcv_skb过滤,eknetlink_rcv_msg得不到调用*/        EUNETLINK_MSG_CLOSE}EUNETLINK_MSG;

#define NETLINK_EXAMPLE  31  /*新增netlink协议类型,和内核态的定义保持一致*/

2) 用户态主函数

int main(int argc, char *argv[]){         int nlsk = eunetlink_open(); /*打开一个netlink 套接字*/      int ret;    if (nlsk<0) {          err(1,"netlink");          return -1;    }         printf("eunetlink open socket = 0x%x\n", nlsk);         ret = eunetlink_send(nlsk);  /*向内核发送"hello"消息*/     printf("eunetlink send ret = 0x%x\n", ret);         eunetlink_recv(nlsk);    /*等待接收内核返回的消息*/         close(nlsk); /*关闭netlink 套接字*/        return 0;}

 

3)用户态消息发送接口

int eunetlink_send(int nlsk){    char buffer[] = "From user : hello kernel!";    struct nlmsghdr* nlhdr;    struct iovec iov;  /* 用于把多个消息通过一次系统调用来发送*/    struct msghdr msg;     struct sockaddr_nl nladdr;    /*必须,消息头数据清零,否则会包含错误的数据,发送失败*/    memset(&msg, 0 ,sizeof(struct msghdr));    memset(&nladdr, 0 ,sizeof(struct sockaddr_nl));        nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(strlen(buffer) + 1));     strcpy(NLMSG_DATA(nlhdr),buffer);     /*填充待发送的消息体*/    nlhdr->nlmsg_len = NLMSG_LENGTH(strlen(buffer) + 1);     nlhdr->nlmsg_pid = getpid();   /*发送者进程ID,内核代码会根据这个ID返回消息*/    nlhdr->nlmsg_flags = NLM_F_REQUEST;        /*必须,不设置的话,内核中netlink_rcv_skb() 会过滤此消息,不会调用户回调*/    nlhdr->nlmsg_type  = EUNETLINK_MSG_OPEN;   /*必须,不设置的话,内核中netlink_rcv_skb() 会过滤此消息,不会调用户回调*/    /*填充目标地址结构体*/    nladdr.nl_family = AF_NETLINK;    nladdr.nl_pid = 0;      /*目标地址是内核,所以这里需要填0*/    nladdr.nl_groups = 0;    printf("Message Send :%s\n",buffer);    #if 0    /*使用struct iovec iov[]数组 和 sendmsg可以实现一次调用发送多个消息请求*/    iov.iov_base = (void *)nlhdr;     iov.iov_len = nlhdr->nlmsg_len;        msg.msg_iov = &iov;    msg.msg_iovlen = 1;    msg.msg_name = (void *)&(nladdr);    msg.msg_namelen = sizeof(nladdr);            return  sendmsg(nlsk, &msg, 0);  #else     /*发送单个个请求*/    return sendto(nlsk, nlhdr, nlhdr->nlmsg_len, 0,             (struct sockaddr*)&nladdr, sizeof(nladdr));#endif     }

4)用户态消息接收

int eunetlink_recv(int sock){   struct sockaddr_nl nladdr;      struct msghdr msg;      struct iovec iov[1];      struct nlmsghdr * nlh;        char buffer[65536]; /*临时buffer,用于接收内核发过来的数据*/      int len;           /*填充待接收消息结构体*/     msg.msg_name = (void *)&(nladdr);  /*设定接收的发送源地址*/       msg.msg_namelen = sizeof(nladdr);       /*设定临时缓冲*/    iov[0].iov_base = (void *)buffer;     iov[0].iov_len = sizeof(buffer);    msg.msg_iov = iov;                        msg.msg_iovlen = sizeof(iov)/sizeof(iov[0]);  /*允许多个临时缓冲*/        len = recvmsg(sock, &msg, 0);  /*阻塞等待接收数(除非特别设定,socket都是阻塞式接收)*/    if (len < 0) {         printf("recvmsg error: %d", len);             return len;     }           /*遍历接收到的消息数据,进行处理        NLMSG_OK会判断数据是不是已经结束        NLMSG_NEXT 会修改nlh和len的值,使之指向buffer中的下一个netlink消息       */         for (nlh = (struct nlmsghdr *)buffer; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) {          if (nlh->nlmsg_type == NLMSG_ERROR) {             // Do some error handling.                   puts("eunetlink_recv: NLMSG_ERROR");          return 0;           }         /*处理有效的用户数据*/        if (nlh->nlmsg_len > 0) {                char *str_buf = malloc(nlh->nlmsg_len);                 memcpy(str_buf, NLMSG_DATA(nlh), nlh->nlmsg_len);                                str_buf[nlh->nlmsg_len - 1] = '\0';                printf("Message received:%s\n",str_buf) ;                        free(str_buf);            }    }    return 0;}

5)新增编译脚本并编译用户应用程序
新增external/netlink/Android.mk

LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE_TAGS := optionalLOCAL_MODULE := eunetlinkLOCAL_SRC_FILES := $(call all-subdir-c-files)include $(BUILD_EXECUTABLE)

 编译用户程序:

mmm external/netlink/


打包system.img:

make snod

 

三、运行和调试

   1)。运行android的goldfish模拟器,使用新编译出来的内核:

  emulator -kernel (YOUR_KERNEL_PATH)/arch/arm/boot/zImage -shell

 运行用户测试程序:

eunetlink

正确执行会显示如下信息:

linux学习札记:netlink实践演练

本篇代码的内核态打印:

linux学习札记:netlink实践演练

2)调试用户态

 运行: strace eunetlink

   会显示所有用户态的系统调用路径和返回值,并且会把入参展开显示,这个对调试很有用:

   这里截取其中本文代码相关的部分:

linux学习札记:netlink实践演练

 

四、总结

       netlink提供了一种很好很强大的的用户与内核之间的通讯机制,本文通过静态的新增一个netlink协议类型,并使用这个新的netlink类型实现用户态和内核态的双向通讯,对linux的netlink通讯方式有了一个初步的认识。

       动态申请 netlink通道机制在genetlink.c中实现,是对NETLINK_GENERIC协议的上层封装, 可以参考内核代码net\tipc\netlink.c中的对他的应用,这个文件实现比较简单,可以作为很好的样例。

说明:本文所含代码的运行环境为 android提供的 goldfish 模拟器平台, Linux 内核版本为 2.6.29

参考文档:

android-goldfish-2.6.29 code

《Linux内核设计与实现》(Linux Kernel Development)第3版 》

Linux 用户态与内核态的交互——netlink 篇:http://bbs.chinaunix.net/thread-2162796-1-1.html

《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/

 

热点排行