由于前面做了很多准备,所以这里不能只分析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