前面做了一大堆的准备就是为了分析下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