原文发表在我的博客主页,转载请注明出处!

一.前言

OpenvSwitch,虚拟交换机,以下简称OVS,是云计算和SDN领域非常重要的一个开源交换机,如果需要深入研究云计算和SDN的数据平面,读懂OVS的源码是非常重要的,现有的关于OVS的资料都是OpenvSwitch2.3.*版本的,而ubuntu14.04已经问世好久,其支持OVS2.4.0+版本的源码分析却没有找见。本文参考了大量的资料,从一个初学者的角度出发(侧重于OpenFlow协议的实现),对OVS2.4.0源码按照数据流程进行简单的分析。

二.概述

关于OVS的概述可以参见我的另一篇博客

在阅读代码的时候,推荐Source InsightSublime Text 3

常用修改建议:

在工作中一般在这几个地方来修改内核代码以达到自己的目的:第一个是datapath.c中的ovs_dp_process_received_packet(struct vportp, struct sk_buffskb)函数内添加相应的代码来达到自己的目的,因为对于每个数据包来说这个函数都是必经之地;第二个就是自己去设计自己的流表了;第三个和第二个是相关联的,就是根据流表来设计自己的action,完成自己想要的功能。

OpenFlow修改建议:

主要关注ofproto中的文件,如ofproto.c和connmgr.c文件,其中ofproto.c中的handle_openflow函数是做SDN相关工作的主要修改的地方。

三.源码分析

  • 从main函数开始
int
main(int argc, char *argv[])
{
char *unixctl_path = NULL;
struct unixctl_server *unixctl;
char *remote;
bool exiting;
int retval; set_program_name(argv[0]); //设置程序名称、版本、编译日期等信息
retval = dpdk_init(argc,argv);
if (retval < 0) {
return retval;
} argc -= retval;
argv += retval; ovs_cmdl_proctitle_init(argc, argv); //复制出输入的参数列表到新的存储中,让argv指向这块内存,主要是为了后面的proctitle_set()函数(在deamonize_start()->monitor_daemon()中调用,可能修改原argv存储)做准备
service_start(&argc, &argv);
remote = parse_options(argc, argv, &unixctl_path); //解析参数,其中unixctl_path存储unixctrl域的sock名,作为接收外部控制命令的渠道;而remote存储连接到ovsdb的信息,即连接到配置数据库的sock名
fatal_ignore_sigpipe(); //忽略pipe读信号的结束
ovsrec_init(); //数据表结构初始化,包括13张数据表 daemonize_start(); //让进程变为守护程序 if (want_mlockall) {
#ifdef HAVE_MLOCKALL
if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
VLOG_ERR("mlockall failed: %s", ovs_strerror(errno));
}
#else
VLOG_ERR("mlockall not supported on this system");
#endif
} retval = unixctl_server_create(unixctl_path, &unixctl); //创建一个unixctl server(存放unixctl),并监听在////unixctl_path指定的punix路径
if (retval) {
exit(EXIT_FAILURE);
}
unixctl_command_register("exit", "", 0, 0, ovs_vswitchd_exit, &exiting); //注册unixctl命令 bridge_init(remote); //读取数据库做一些初始化工作
free(remote); exiting = false;
while (!exiting) {
memory_run();
if (memory_should_report()) {
struct simap usage; simap_init(&usage);
bridge_get_memory_usage(&usage);
memory_report(&usage);
simap_destroy(&usage);
}
bridge_run();
unixctl_server_run(unixctl); //从unixctl指定的server中获取数据,并执行对应的配置命令
netdev_run(); //执行在netdev_classes上定义的每个netdev_classs实体,调用他们的run() memory_wait();
bridge_wait();
unixctl_server_wait(unixctl);
netdev_wait();
if (exiting) {
poll_immediate_wake();
}
poll_block(); //阻塞,直到之前被poll_fd_wait()注册过的事件发生,或者等待时间超过
if (should_service_stop()) {
exiting = true;
}
}
bridge_exit();
unixctl_server_destroy(unixctl);
service_stop(); return 0;
}
  • 进入bridge_run()函数,这个函数在Bridge.c文件中,ofproto_class类型在ofproto_classes[]变量中声明。而ofproto_classes[]变量是通过ofproto_init()函数来初始化的,在ofproto.c文件中,继续调用ofproto_class_register()函数,初始化之后仅含有一个变量——ofproto_dpif_class。而这个类定义在ofproto-dpif.c文件中,声明了各个变量和操作函数。
void
bridge_run(void)
{
static struct ovsrec_open_vswitch null_cfg;
const struct ovsrec_open_vswitch *cfg; bool vlan_splinters_changed; ovsrec_open_vswitch_init(&null_cfg); ovsdb_idl_run(idl); if (ovsdb_idl_is_lock_contended(idl)) {
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
struct bridge *br, *next_br; VLOG_ERR_RL(&rl, "another ovs-vswitchd process is running, "
"disabling this process (pid %ld) until it goes away",
(long int) getpid()); HMAP_FOR_EACH_SAFE (br, next_br, node, &all_bridges) {
bridge_destroy(br);
}
/* Since we will not be running system_stats_run() in this process
* with the current situation of multiple ovs-vswitchd daemons,
* disable system stats collection. */
system_stats_enable(false);
return;
} else if (!ovsdb_idl_has_lock(idl)
|| !ovsdb_idl_has_ever_connected(idl)) {
/* Returns if not holding the lock or not done retrieving db
* contents. */
return;
}
cfg = ovsrec_open_vswitch_first(idl); /* Initialize the ofproto library. This only needs to run once, but
* it must be done after the configuration is set. If the
* initialization has already occurred, bridge_init_ofproto()
* returns immediately. */
bridge_init_ofproto(cfg); /* Once the value of flow-restore-wait is false, we no longer should
* check its value from the database. */
if (cfg && ofproto_get_flow_restore_wait()) {
ofproto_set_flow_restore_wait(smap_get_bool(&cfg->other_config,
"flow-restore-wait", false));
} bridge_run__(); /* Re-configure SSL. We do this on every trip through the main loop,
* instead of just when the database changes, because the contents of the
* key and certificate files can change without the database changing.
*
* We do this before bridge_reconfigure() because that function might
* initiate SSL connections and thus requires SSL to be configured. */
if (cfg && cfg->ssl) {
const struct ovsrec_ssl *ssl = cfg->ssl; stream_ssl_set_key_and_cert(ssl->private_key, ssl->certificate);
stream_ssl_set_ca_cert_file(ssl->ca_cert, ssl->bootstrap_ca_cert);
} /* If VLAN splinters are in use, then we need to reconfigure if VLAN
* usage has changed. */
vlan_splinters_changed = false;
if (vlan_splinters_enabled_anywhere) {
struct bridge *br; HMAP_FOR_EACH (br, node, &all_bridges) {
if (ofproto_has_vlan_usage_changed(br->ofproto)) {
vlan_splinters_changed = true;
break;
}
}
} if (ovsdb_idl_get_seqno(idl) != idl_seqno || vlan_splinters_changed) {
struct ovsdb_idl_txn *txn; idl_seqno = ovsdb_idl_get_seqno(idl);
txn = ovsdb_idl_txn_create(idl);
bridge_reconfigure(cfg ? cfg : &null_cfg); if (cfg) {
ovsrec_open_vswitch_set_cur_cfg(cfg, cfg->next_cfg);
discover_types(cfg);
} /* If we are completing our initial configuration for this run
* of ovs-vswitchd, then keep the transaction around to monitor
* it for completion. */
if (initial_config_done) {
/* Always sets the 'status_txn_try_again' to check again,
* in case that this transaction fails. */
status_txn_try_again = true;
ovsdb_idl_txn_commit(txn);
ovsdb_idl_txn_destroy(txn);
} else {
initial_config_done = true;
daemonize_txn = txn;
}
} if (daemonize_txn) {
enum ovsdb_idl_txn_status status = ovsdb_idl_txn_commit(daemonize_txn);
if (status != TXN_INCOMPLETE) {
ovsdb_idl_txn_destroy(daemonize_txn);
daemonize_txn = NULL; /* ovs-vswitchd has completed initialization, so allow the
* process that forked us to exit successfully. */
daemonize_complete(); vlog_enable_async(); VLOG_INFO_ONCE("%s (Open vSwitch) %s", program_name, VERSION);
}
} run_stats_update();
run_status_update();
run_system_stats();
}
  • 继续调用bridge_run__函数,在里面先是调用了ofproto_type_run(type)函数,接着调用了ofproto_run(br->ofproto)函数,接下来一个一个看
static void
bridge_run__(void)
{
struct bridge *br;
struct sset types;
const char *type; /* Let each datapath type do the work that it needs to do. */
sset_init(&types);
ofproto_enumerate_types(&types);
SSET_FOR_EACH (type, &types) {
ofproto_type_run(type);
}
sset_destroy(&types); /* Let each bridge do the work that it needs to do. */
HMAP_FOR_EACH (br, node, &all_bridges) {
ofproto_run(br->ofproto); //处理all_bridge上的每个bridge
}
}
  • 先看ofproto_type_run(type)函数,调用type_run()函数,这个函数来自于ofproto_dpif.c文件中的type_run()函数,在这个函数中,如果上层同意接收数据,则调用udpif_set_threads(backer->dpif, n_handlers, n_revalidators);通知udpif它需要多少个线程去处理upcalls。接着会调用udpif_start_threads(udpif, n_handlers, n_revalidators),继续调用udpif_upcall_handler(),这个处理线程从dpif(datapath interface)upcalls,对其进行处理,然后安装相应的流表,然后继续调用recv_upcalls(handler)函数,在这个函数中会调用process_upcall()函数来处理upcall。
  • ofproto_run()函数在ofproto.c文件中,里面调用了ofproto_class->run(p),根据前面的分析,这个调用了ofproto-dpif.c文件中的ofproto_dpif_class的run,他还调用了connmgr_run(p->connmgr, handle_openflow)函数来处理来自控制器的OpenFlow消息:
int
ofproto_run(struct ofproto *p)
{
int error;
uint64_t new_seq; error = p->ofproto_class->run(p);
if (error && error != EAGAIN) {
VLOG_ERR_RL(&rl, "%s: run failed (%s)", p->name, ovs_strerror(error));
} run_rule_executes(p); /* Restore the eviction group heap invariant occasionally. */
if (p->eviction_group_timer < time_msec()) {
size_t i; p->eviction_group_timer = time_msec() + 1000; for (i = 0; i < p->n_tables; i++) {
struct oftable *table = &p->tables[i];
struct eviction_group *evg;
struct rule *rule; if (!table->eviction_fields) {
continue;
} if (table->n_flows > 100000) {
static struct vlog_rate_limit count_rl =
VLOG_RATE_LIMIT_INIT(1, 1);
VLOG_WARN_RL(&count_rl, "Table %"PRIuSIZE" has an excessive"
" number of rules: %d", i, table->n_flows);
} ovs_mutex_lock(&ofproto_mutex);
CLS_FOR_EACH (rule, cr, &table->cls) {
if (rule->idle_timeout || rule->hard_timeout) {
if (!rule->eviction_group) {
eviction_group_add_rule(rule);
} else {
heap_raw_change(&rule->evg_node,
rule_eviction_priority(p, rule));
}
}
} HEAP_FOR_EACH (evg, size_node, &table->eviction_groups_by_size) {
heap_rebuild(&evg->rules);
}
ovs_mutex_unlock(&ofproto_mutex);
}
} if (p->ofproto_class->port_poll) {
char *devname; while ((error = p->ofproto_class->port_poll(p, &devname)) != EAGAIN) {
process_port_change(p, error, devname);
}
} new_seq = seq_read(connectivity_seq_get());
if (new_seq != p->change_seq) {
struct sset devnames;
const char *devname;
struct ofport *ofport; /* Update OpenFlow port status for any port whose netdev has changed.
*
* Refreshing a given 'ofport' can cause an arbitrary ofport to be
* destroyed, so it's not safe to update ports directly from the
* HMAP_FOR_EACH loop, or even to use HMAP_FOR_EACH_SAFE. Instead, we
* need this two-phase approach. */
sset_init(&devnames);
HMAP_FOR_EACH (ofport, hmap_node, &p->ports) {
uint64_t port_change_seq; port_change_seq = netdev_get_change_seq(ofport->netdev);
if (ofport->change_seq != port_change_seq) {
ofport->change_seq = port_change_seq;
sset_add(&devnames, netdev_get_name(ofport->netdev));
}
}
SSET_FOR_EACH (devname, &devnames) {
update_port(p, devname);
}
sset_destroy(&devnames); p->change_seq = new_seq;
} connmgr_run(p->connmgr, handle_openflow); return error;
}
  • 上面函数调用ofproto-dpif.c中的run函数

    在run()函数中,会调用connmgr_send_packet_in()函数给每个控制器发送OFPT_PACKET_IN消息,这个函数调用schedule_packet_in()函数进行发包调度。

    可选调用netflow_run()和sflow_run()函数,进行对netflow和sflow的支持

  • 在ofproto_run()函数后面会调用connmgr_run()函数,之后调用ofconn_run函数,然后在这个函数里面,rconn_run()负责连接控制器;rconn_recv()函数负责从控制器接收数据,handle_openflow()函数负责处理从控制器得到的消息(这个函数在ofproto.c文件中)

  • 最后回到ovs-vswitchd.c文件中

    unixctl_server_run(unixctl):从unixctl指定的server中获取数据,并执行对应的配置命令

    netdev_run():执行在netdev_classes上定义的每个netdev_class实体,调用它们的run()。

    接着进行循环等待事件处理,包括memory, bridge, unixctl_server, netdev这些被poll_fd_wait()注册过的事件

    poll_block:阻塞,直到之前被poll_fd_wait()注册过的事件发生,或者等待时间超过poll_timer_wait()注册的最短时间

    退出bridge,关闭unixctl连接,取消信号的处理

四.总结

前面从初学者的角度,按照数据包流向,对OVS2.4.0源码进行了分析。对于研究SDN的人来说,ofproto模块是非常重要的,可以进一步详细阅读其源码。

OpenvSwitch2.4.0源码解读的更多相关文章

  1. AFNetworking 3.0 源码解读 总结(干货)(下)

    承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...

  2. AFNetworking 3.0 源码解读(十一)之 UIButton/UIProgressView/UIWebView + AFNetworking

    AFNetworking的源码解读马上就结束了,这一篇应该算是倒数第二篇,下一篇会是对AFNetworking中的技术点进行总结. 前言 上一篇我们总结了 UIActivityIndicatorVie ...

  3. AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking

    我们应该看到过很多类似这样的例子:某个控件拥有加载网络图片的能力.但这究竟是怎么做到的呢?看完这篇文章就明白了. 前言 这篇我们会介绍 AFNetworking 中的3个UIKit中的分类.UIAct ...

  4. AFNetworking 3.0 源码解读(九)之 AFNetworkActivityIndicatorManager

    让我们的APP像艺术品一样优雅,开发工程师更像是一名匠人,不仅需要精湛的技艺,而且要有一颗匠心. 前言 AFNetworkActivityIndicatorManager 是对状态栏中网络激活那个小控 ...

  5. AFNetworking 3.0 源码解读(八)之 AFImageDownloader

    AFImageDownloader 这个类对写DownloadManager有很大的借鉴意义.在平时的开发中,当我们使用UIImageView加载一个网络上的图片时,其原理就是把图片下载下来,然后再赋 ...

  6. AFNetworking 3.0 源码解读(七)之 AFAutoPurgingImageCache

    这篇我们就要介绍AFAutoPurgingImageCache这个类了.这个类给了我们临时管理图片内存的能力. 前言 假如说我们要写一个通用的网络框架,除了必备的请求数据的方法外,必须提供一个下载器来 ...

  7. AFNetworking 3.0 源码解读(六)之 AFHTTPSessionManager

    AFHTTPSessionManager相对来说比较好理解,代码也比较短.但却是我们平时可能使用最多的类. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilit ...

  8. AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization

    这篇就讲到了跟请求相关的类了 关于AFNetworking 3.0 源码解读 的文章篇幅都会很长,因为不仅仅要把代码进行详细的的解释,还会大概讲解和代码相关的知识点. 上半篇: URI编码的知识 关于 ...

  9. AFNetworking 3.0 源码解读(四)之 AFURLResponseSerialization

    本篇是AFNetworking 3.0 源码解读的第四篇了. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager AFNetworking 3 ...

随机推荐

  1. Android实现小圆点显示未读功能

    代码地址如下:http://www.demodashi.com/demo/13541.html 前言 以前我们实现这个功能都是用 BadgeView.java,大体就是将这个java类复制到自己的项目 ...

  2. 6. Laravel5学习笔记:IOC/DI的理解

    介绍 IOC 控制反转 Inversion of Control 依赖关系的转移 依赖抽象而非实践 DI 依赖注入 Dependency Injection 不必自己在代码中维护对象的依赖 容器自己主 ...

  3. 数字图像和视频处理的基础-第4周运动预计matlab练习题

    In this problem you will perform block matching motion estimation between two consecutive video fram ...

  4. ORA-01589: 要打开数据库则必须使用 RESETLOGS 或 NORESETLOGS 选项

    产生这个的原因可能是由于数据库突然停止,没有来得及将缓存区中的LOG归档,导致下次开启时不能匹配日志文件. 数据库中的三个日志文件挨个试,第二个就匹配上了

  5. spring 代理 演变过程

    动态代理演变 拿JDBC开事务举例子 最初 写代码 每个CUD 都需要开启事务 所以出现很多累赘代码 因此提出静态代理的构想,把事务交给后台做,程序员只需要 调用update(sql)就行了,upda ...

  6. 安装 Tomcat

    安装 Tomcat(.exe)  而 .rar文件则只需解压即可使用. 点击 apache-tomcat-7.0.55.exe 进行安装: 在“Configuration”: Server Shutd ...

  7. 修改easyui panel 默认样式

    有这么个需求需要修改easyui panel头部中的背景色.于是根据panel中的最终被浏览器解析出来的类名,直接修改这个css样式,设置backgroud-color这个属性,发现不管用. 于是,就 ...

  8. 关于数组中加入相同的view的试验

    随便新建一个工程,然后在控制器中粘贴如下代码 - (void)viewDidLoad { [super viewDidLoad]; UIView * view = [[UIView alloc]ini ...

  9. 数据结构(逻辑结构,物理结构,特点) C#多线程编程的同步也线程安全 C#多线程编程笔记 String 与 StringBuilder (StringBuffer) 数据结构与算法-初体验(极客专栏)

    数据结构(逻辑结构,物理结构,特点) 一.数据的逻辑结构:指反映数据元素之间的逻辑关系的数据结构,其中的逻辑关系是指数据元素之间的前后件关系,而与他们在计算机中的存储位置无关.逻辑结构包括: 集合 数 ...

  10. Android studio 使用心得(三)—从Eclipse迁移到Android studio

    断断续续的也算是把eclipse上的代码成功迁移到android studio上来了,现在,我同事继续用eclipse,我用android studio,svn上还是之前eclipse的项目,迁移成功 ...