前面做了一大堆的準備就是為了分析下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](http://blog.csdn.net/yuzhihui_no1?viewmode=list)
- 前言
- OVS datapath模塊分析:packet處理流程
- openVswitch(OVS)源代碼分析之簡介
- openVswitch(OVS)源代碼分析之數據結構
- openVswitch(OVS)源代碼分析之工作流程(收發數據包)
- openVswitch(OVS)源代碼分析之工作流程(數據包處理)
- openVswitch(OVS)源代碼分析之工作流程(key值得提取)
- openVswitch(OVS)源代碼分析之工作流程(flow流表查詢)
- openVswitch(OVS)源代碼的分析技巧(哈希桶結構體為例)
- openVswitch(OVS)源代碼分析之工作流程(哈希桶結構體的解釋)
- openVswitch(OVS)源代碼之linux RCU鎖機制分析
- openVswitch(OVS)源代碼分析 upcall調用(之linux中的NetLink通信機制)
- openVswitch(OVS)源代碼分析 upcall調用(一)