Vswitchd
是ovs
中运行在用户空间的守护进程, 实现ovs
主要的功能逻辑, 本文将着重分析其启动过程.
数据结构
bridge/port/iface/ofproto/ofport
在数通领域, 交换机和桥很多时候可以是在说一个东西, 它工作在二层, 可以添加多个端口, 从一个端口上收到的报文会根据MAC表从其他某个端口转发出去. 在ovs
中, 它也还是一个东西, 不过ovs
用两个数据结构描述它们: 交换机(ofproto
)与桥(bridge
). 它们在ovs
中是一一对应的, 也就是说一个bridge
就有一个对应的ofproto
. 相比而言, bridge
更贴近用户, ofproto
跟底层联系更多. 想想添加一个桥的命令是 ovs-vsctl add-br
就可见一斑了.
交换机需要添加端口才能具有实际作用, ovs
用port
和ofprot
描述端口, 前者对应bridge
, 更贴近用户配置, 而后者对应ofproto
,更贴近底层. ovs
还有一个结构叫iface
, 一般而言, 一个port
包含一个iface
, 但存在一种聚合(bond)的情况, 我们可以把多个iface
捆在一起, 将它们一并归纳到一个port
.此时port
和iface
就是一对多的关系了.
以下为前面提到的几个数据结构之间的联系
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
, type
为ofproto
的类型, 也可称为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
看成一张注册表, 里面有两个条目
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
为空, 所以也不会有实际的动作.