Vswitchdovs中运行在用户空间的守护进程, 实现ovs主要的功能逻辑, 本文将着重分析其启动过程.

数据结构

bridge/port/iface/ofproto/ofport

在数通领域, 交换机和桥很多时候可以是在说一个东西, 它工作在二层, 可以添加多个端口, 从一个端口上收到的报文会根据MAC表从其他某个端口转发出去. 在ovs中, 它也还是一个东西, 不过ovs用两个数据结构描述它们: 交换机(ofproto)与桥(bridge). 它们在ovs中是一一对应的, 也就是说一个bridge就有一个对应的ofproto. 相比而言, bridge更贴近用户, ofproto跟底层联系更多. 想想添加一个桥的命令是 ovs-vsctl add-br 就可见一斑了.
交换机需要添加端口才能具有实际作用, ovsportofprot描述端口, 前者对应bridge, 更贴近用户配置, 而后者对应ofproto,更贴近底层. ovs还有一个结构叫iface, 一般而言, 一个port包含一个iface, 但存在一种聚合(bond)的情况, 我们可以把多个iface捆在一起, 将它们一并归纳到一个port.此时portiface就是一对多的关系了.

以下为前面提到的几个数据结构之间的联系

centos openvswitch开源_ide

Vswitchd启动

入口

Vswitchd 进程的入口在ovs-vswitchd.c ,下面我们将主要关注主干流程的分析,而忽略其中的一些旁枝末节

int main(int argc, char *argv[])
{
     remote = parse_options(argc, argv, &unixctl_path)
     bridge_init(remote);
     while (!existing){
           bridge_run();     
           bridge_wait();
           poll_block();
     }
     .....exit();
     return 0;
}
初始化 bridge 模块

bridge 模块的初始化在 bridge_init 中完成, bridge_init主要的动作是连接数据库ovsdb, 并从中读取配置信息.

void bridge_init(const char *remote)
{
    idl = ovsdb_idl_create(remote, &ovsrec_idl_class, true, true);
 
    ovsdb_idl_omit_alert(idl, &ovsrec_open_vswitch_col_cur_cfg);
    ......
}

注意, 像ovsrec_open_vswitch_col_cur_cfg这样的全局变量的定义是编译时自动生成的, 从官方下载的代码中最初是没有它的定义的.

运行 bridge 模块

bridge 模块的初始化在 bridge_run 中进行

void bridge_run(void)
{
    cfg = ovsrec_open_vswitch_first(idl);
    bridge_init_ofproto(cfg);
    bridge_run__();
    if (ovsdb_idl_get_seqno(idl) != idl_seqno || if_notifier_changed(ifnotifier))
    {
        bridge_reconfigure(cfg ? cfg : &null_cfg)
    }
    ...
}

首先读取数据库ovsdb中的配置到cfg, 此时, cfg中就有了诸如用户创建了多少个bridge, 每个bridge中有多少个port, iface等信息.

bridge_init_ofproto中, 会进行ofproto library初始化, 之前提到了, bridge更靠近用户配置, ofproto更靠近底层, 这里就是进行底层库的初始化

void ofproto_init(const struct shash *iface_hints)
{
    ofproto_class_register(&ofproto_dpif_class);
    .....
    for (i = 0; i < n_ofproto_classes; i++) {
        ofproto_classes[i]->init(&init_ofp_ports);
    }
}

ofproto library首先进行ofproto class类的注册, ovs当前仅支持一种类, 即ofproto_dpif_class, 所以后面调用init, 实际上也只会调用ofproto_dpif_class->init().
观察ofproto的数据结构中 的前几项

struct ofproto{
   const struct ofproto_class *ofproto_class;
   char *type;             /* Datapath type */ 
   char *name;             /* Datapath name */
   ...
}

其中, ofproto_class即为生产者的class, 在当前ovs中, 只会指向ofproto_dpif_class, typeofproto的类型, 也可称为provider, 当前的ofproto_dpif_class有两种provider , 记录在dpif_classes中, 它的来源是base_dpif_classes[]

static const struct dpif_class *base_dpif_classes[] = {
  &dpif_netlink_class,
  &dpif_netdev_class,
}

可以把dpif_classes看成一张注册表, 里面有两个条目

centos openvswitch开源_ide_02


ofproto_dpif_class实现的enumerate_types方法可以列举当前支持的datapath的类型, 在当前的ovs实现中,就是"system"和"netdev"

再来看 bridge_run__(void)

static void bridge_run__(void)
{
    ofproto_enumerate_types(&types);
    SSET_FOR_EACH(type, &types){
        ofproto_type_run(type);
    }
    HMAP_FOR_EACH(br, node, &all_bridges){
       ofproto_run(br->ofproto);
    }
}

首先bridge_run__将当前支持的type列举出来, 再逐个调用ofproto_type_run ,最终调用到ofproto_dpif_class->type_run(),这里在第一次运行时, 由于all_dpif_backers为空,所以就直接返回了.
然后, bridge_run__all_bridges上记录的每个bridge调用ofproto_run, 同样在第一次运行在这里时all_bridges为空, 所以也不会有实际的动作.