openVswitch(OVS)源代码分析 upcall调用(之linux中的NetLink通信机制)

        前面做了一大堆的准备就是为了分析下upcall调用,但是现在因为工作重心已经从OpenVswitch上转移到了openstack,所以根本没时间去研究OpenVswitch了。(openstack是用Python写的,我大学没接触过Python,所以现在要一边学Python一边学openstack)后面的OpenVswitch分析更新的时间可能会有点久。

        由于前面做了很多准备,所以这里不能只分析NetLink通信机制(否则可能会感觉没意思了),首先来分析下upcall函数调用的原因。如果看了前面的源码分析的就会知道,在什么情况下会调用upcall函数呢?就是在一个数据包查找不到相应的流表项时,才会调用upcall函数(比如一个数据包第一次进入这个内核,里面没有为这个数据包设定相应的流表规则)。upcall函数的调用其实就是把数据包的信息下发到用户 空间去,而由内核空间到用户空间的通信则要用到linux中的NetLink机制。所以熟悉下NetLink通信可以知道upcall函数调用需要什么样的参数以及整个函数的作用和功能。

        现在来测试下NetLink的使用,NetLink由两部分程序构成,一部分是用户空间的,另外一部分是内核空间的。用户空间的和大多数socket编程一样,只是用的协议时AF_NETLINK,其他基本都市一样的步骤。

        下面是NetLine程序中的用户代码NetLinke_user.c:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/socket.h>
#include<linux/netlink.h>

#define NETLINK_TEST 30
#define MAX_MSG 1024

int main(void)
{
/*按代码规范所有变量都要定义在函数的开始部分,但为了便于理解,所以顺序定义变量*/
        /*
         *    struct sockaddr_nl addr;
         *    struct nlmsghdr  *nlhdr;
         *    struct iovec        iov;
         *    struct msghdr       msg;
         *    int              sockId;
         */

        //创建socket套接字
        int socketId = socket(AF_NETLINK,SOCK_RAW,NETLINK_TEST);
        if (0 > socketId){
                printf("The error in socket_create!\n");
                return -1;
        }

        //套接字地址设置
        struct sockaddr_nl addr;
        memset(&addr,0,sizeof(struct sockaddr_nl));
        addr.nl_family = AF_NETLINK; //一定是这个协议
        addr.nl_pid = 0; //消息是发给内核的,所以为0;或者内核多播数据给用户空间
        addr.nl_groups = 0; // 单播或者发给内核

        //将打开的套接字和addr绑定
        int ret = bind(socketId,(struct sockaddr*)(&addr),sizeof(addr));
        if (0 > ret){
                printf("The error in bind!\n");
                close(socketId);
                return -1;
        }

        //NetLink消息头设置
        struct nlmsghdr *nlhdr = NULL;
        nlhdr = (struct nlmsghdr*)malloc(NLMSG_SPACE(MAX_MSG));
        if (!nlhdr){
                printf("The error in nlmsghdr_malloc!\n");
                close(socketId);
                return -1;
	 }
        nlhdr->nlmsg_len = NLMSG_SPACE(MAX_MSG);
        nlhdr->nlmsg_pid = getpid();//内核如果要返回消息会查找这个pid
        nlhdr->nlmsg_flags = 0;
        strcpy(NLMSG_DATA(nlhdr),"This is data what will be sent!\n");

        //设置消息缓存指针
        struct iovec iov;
        memset(&iov,0,sizeof(struct iovec));
        iov.iov_base = (void*)nlhdr;
        iov.iov_len  = NLMSG_SPACE(MAX_MSG);

        //设置消息结构体
        struct msghdr msg;
        memset(&msg,0,sizeof(struct msghdr));
        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;

        //发送消息给内核
        ret = sendmsg(socketId,&msg,0);
        if (0 > ret){
                printf("The error in sendmsg!\n");
                close(socketId);
                free(nlhdr);
                return -1;
        }

        /********************接受消息部分*********************/
        printf("begin receive message!\n");

        //对接受消息的字段进行清零,因为发送时,里面存储了发送数据
        memset((char*)NLMSG_DATA(nlhdr),0,MAX_MSG);
        recvmsg(socketId,&msg,0);
        //打印接受到的消息
        printf("receive message:===========\n%s\n",NLMSG_DATA(nlhdr));
        //收尾工作,对资源的处理
        close(socketId);
        free(nlhdr);
        return 0;

}

        NetLink程序内核代码本来有两种情况的:一、单播给某个指定的pid;二、多播个nl_gloups.下面的代码只有单播的功能,没有多播。多播实验了很久也没成功,所以就搁着了。

        下面是NetLink程序中的NetLink_kernel.c内核代码:

/*=======================KERNEL==========================================*/
#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/types.h>
#include<net/sock.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/sched.h>
#include<linux/netlink.h>

#define NETLINK_TEST 30
#define MAX_MSG      1024

// 内核sock
struct sock* socketId = NULL;

// 单播数据
char kernel_to_user_msg_unicast[] = "hello userspace uncast!";
int  unicastMsgLen = NLMSG_SPACE(sizeof(kernel_to_user_msg_unicast));

// 单播,groups一定要为0,pid则为单播pid
int kernel_unicast_user(u32 pid)
{
        struct sk_buff *skb_sent = NULL;
        struct nlmsghdr *nlhdr   = NULL;

        // 创建网络数据包结构体
        skb_sent = alloc_skb(unicastMsgLen,GFP_KERNEL);
        if (!skb_sent){
                printk(KERN_ALERT"error in uncast alloc_skb!\n");
                return -1;
        }
        // nlhdr = NLMSG_NEW(skb_sent,0,0,NLMSG_DONE,unicastMsgLen,0);
        nlhdr = nlmsg_put(skb_sent, 0, 0, 0, unicastMsgLen,0);

        // 填充发送数据
       memcpy(NLMSG_DATA(nlhdr),kernel_to_user_msg_unicast,unicastMsgLen);

        // 设置控制块
        NETLINK_CB(skb_sent).pid = 0;
        NETLINK_CB(skb_sent).dst_group = 0;

        // 单播发送
        if (0 > netlink_unicast(socketId,skb_sent,pid,0)){
                printk(KERN_ALERT"error in netlink_unicast\n");
                return -1;
        }
        return 0;
}
EXPORT_SYMBOL(kernel_unicast_user);// 导出函数符号

// 注册的回调函数,处理接受到数据
static void kernel_recv_user(struct sk_buff *__skb)
{
        struct sk_buff *skb = NULL;
        struct nlmsghdr *nlhdr = NULL;

        skb = skb_get(__skb);// 从一个缓冲区中引用指针出来

        if (skb->len >= NLMSG_SPACE(0)){  //其实就是判断是否为空
                nlhdr = nlmsg_hdr(skb);// 宏nlmsg_hdr(skb)的实现为(struct nlmsghdr*)skb->data

                // 开始打印接受到的消息
                printk(KERN_INFO"base info =======\n len:%d, type:%d, flags:%d, pid:%d\n",
                                nlhdr->nlmsg_len,nlhdr->nlmsg_type,nlhdr->nlmsg_flags,nlhdr->nlmsg_pid);
                printk(KERN_INFO"data info =======\n data:%s\n",(char*)NLMSG_DATA(nlhdr));
        }
                kernel_unicast_user(nlhdr->nlmsg_pid);
}

// 模块插入时触发的函数,一般用来做初始化用,也是模块程序的入口
static int __init netlink_init(void)
{
        printk(KERN_ALERT"netlink_init()!\n");

        socketId = netlink_kernel_create(&init_net,NETLINK_TEST,0,kernel_recv_user,NULL,THIS_MODULE);
        if (!socketId){
                printk(KERN_ALERT"error in sock create!\n");
                return -1;
        }
        return 0;
}

// 模块退出时触发的函数,目的一般是用来清理和收尾工作
static void __exit netlink_exit(void)
{
        printk(KERN_ALERT"netlink_exit()!\n");
        netlink_kernel_release(socketId);
}

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("yuzhihui");

module_init(netlink_init);
module_exit(netlink_exit);
        上面的两个程序就是linux中内核空间和用户空间通信的实例,其实这个NetLink通信实验并不是非常重要,对理解OpenVswitch来说,个人只是兴趣所好,特意去看了下NetLink的工作原理。至于多播的功能实现,如果有知道麻烦给个链接。

        由于时间问题,做这个实验和写这个blog相差了一个多月,很多当时注意到问题,或者想分享的细节都已经记不清了。所以感觉比较简陋,当然也必不可少有些不正确之处,希望发现的可以指正,谢谢!!

          转载请注明作者和原文出处,原文地址:http://blog.csdn.net/yuzhihui_no1?viewmode=list



你可能感兴趣的:(云计算,虚拟化,openvswitch,linux内核,linux内核网络协议栈)