本小节分析vswitchd
模块,该模块作为管理底层datapath的重要模块,实现了openflow的主要逻辑,以及对交换机的具体管理和除简单转发外的其他逻辑。可见,该模块十分重要,
主要生成
ovs-vswitchd
文件,其中主文件为vswitchd/
ovs-vswitchd.c
。
struct hmap_node node;
char *name;
char *type;
uint8_t ea[ETH_ADDR_LEN];
uint8_t default_ea[ETH_ADDR_LEN];
const struct ovsrec_bridge *cfg;
struct ofproto *ofproto;
struct hmap ports;
struct hmap ifaces;
struct hmap iface_by_name;
struct list ofpp_garbage;
struct hmap if_cfg_todo;
struct hmap mirrors;
struct ovsrec_port synth_local_port;
struct ovsrec_interface synth_local_iface;
struct ovsrec_interface *synth_local_ifacep;
struct hmap_node hmap_node;
const struct ofproto_class *ofproto_class;
char *type;
char *name;
uint64_t fallback_dpid;
uint64_t datapath_id;
unsigned flow_eviction_threshold;
bool forward_bpdu;
char *mfr_desc;
char *hw_desc;
char *sw_desc;
char *serial_desc;
char *dp_desc;
enum ofp_config_flags frag_handling;
struct hmap ports;
struct shash port_by_name;
struct oftable *tables;
int n_tables;
struct connmgr *connmgr;
int state;
struct list pending;
unsigned int n_pending;
struct hmap deletions;
int n_add, n_delete, n_modify;
long long int first_op, last_op;
long long int next_op_report;
long long int op_backoff;
unsigned long int *vlan_bitmap;
bool vlans_changed;
int min_mtu;
port_run_fast(ofport);
}
int retval = handle_upcalls(ofproto, FLOW_MISS_MAX_BATCH - work);
if (retval <= 0) {
return -retval;
}
work += retval;
}
enum dpif_upcall_type type;
struct ofpbuf *packet;
struct nlattr *key;
size_t key_len;
uint64_t userdata;
handle_flow_miss(ofproto, miss, flow_miss_ops, &n_ops);
dpif_ops[i] = &flow_miss_ops[i].dpif_op;
}
dpif_operate(ofproto->dpif, dpif_ops, n_ops);
int error = run_connection(conn);
if (error && error != EAGAIN) {
kill_connection(conn);
}
}
char *unixctl_path = NULL;
struct unixctl_server *unixctl;
struct signal *sighup;
char *remote;
bool exiting;
int retval;
proctitle_init(argc, argv); //backup orignal argvs
set_program_name(argv[0]);
stress_init_command(); //register stress cmds to the commands
remote = parse_options(argc, argv, &unixctl_path);
signal(SIGPIPE, SIG_IGN); //ignore the pipe read end signal
sighup = signal_register(SIGHUP); //register the SIGHUP signal handler
process_init(); //create notification pipe and register signal for child process exit
ovsrec_init(); //todo: make clear here
daemonize_start(); //daemonize the process
if (want_mlockall) {
if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
VLOG_ERR("mlockall failed: %s", strerror(errno));
}
VLOG_ERR("mlockall not supported on this system");
}
worker_start(); //start a worker subprocess, call worker_main (receive data and process)
retval = unixctl_server_create(unixctl_path, &unixctl);//create a unix domain socket
if (retval) {
exit(EXIT_FAILURE);
}
unixctl_command_register("exit", "", 0, 0, ovs_vswitchd_exit, &exiting);
bridge_init(remote);//ini the bridge, configure from the ovsdb server, register ctrl commands
free(remote);
exiting = false;
while (!exiting) {
worker_run(); //reply with the worker subprocess
if (signal_poll(sighup)) {
vlog_reopen_log_file();
}
memory_run();//monitor the memory
if (memory_should_report()) {
struct simap usage;
simap_init(&usage);
bridge_get_memory_usage(&usage);
memory_report(&usage);
simap_destroy(&usage);
}
bridge_run_fast(); //check each bridge and run it's handler
bridge_run(); //main process part, process of pkts
bridge_run_fast();
unixctl_server_run(unixctl);
netdev_run(); //run periodic functions by all network devices.
worker_wait();
signal_wait(sighup);
memory_wait();
bridge_wait();
unixctl_server_wait(unixctl);
netdev_wait();
if (exiting) {
poll_immediate_wake();
}
poll_block();
}
bridge_exit();
unixctl_server_destroy(unixctl);
signal_unregister(sighup);
return 0;
while (!exiting) {
worker_run(); //reply with the worker subprocess
if (signal_poll(sighup)) {
vlog_reopen_log_file();
}
memory_run();//monitor the memory
if (memory_should_report()) {
struct simap usage;
simap_init(&usage);
bridge_get_memory_usage(&usage);
memory_report(&usage);
simap_destroy(&usage);
}
bridge_run_fast(); //check each bridge and run it's handler
bridge_run(); //main process part, process of pkts
bridge_run_fast();
unixctl_server_run(unixctl);
netdev_run(); //run periodic functions by all network devices.
worker_wait();
signal_wait(sighup);
memory_wait();
bridge_wait();
unixctl_server_wait(unixctl);
netdev_wait();
if (exiting) {
poll_immediate_wake();
}
poll_block();
ofproto_run_fast(br->ofproto);
}
list_push_back(&server->conns, &conn->node);
conn->rpc = jsonrpc_open(stream);
const struct netdev_class *netdev_class = node->data;
if (netdev_class->run) {
netdev_class->run();
}
((STRUCT *) (void *) ((char *) (POINTER) - offsetof (STRUCT, MEMBER)))
((OVS_TYPEOF(OBJECT)) (void *)
\
((char *) (POINTER) - OBJECT_OFFSETOF(OBJECT, MEMBER)))
((OBJECT) = OBJECT_CONTAINING(POINTER, OBJECT, MEMBER), 1)
for (ASSIGN_CONTAINER(ITER, (LIST)->next, MEMBER);
\
&(ITER)->MEMBER != (LIST);
\
ASSIGN_CONTAINER(ITER, (ITER)->MEMBER.next, MEMBER))
for (ASSIGN_CONTAINER(ITER, (LIST)->prev, MEMBER);
\
&(ITER)->MEMBER != (LIST);
\
ASSIGN_CONTAINER(ITER, (ITER)->MEMBER.prev, MEMBER))
for (ASSIGN_CONTAINER(ITER, (LIST)->next, MEMBER);
\
(&(ITER)->MEMBER != (LIST)
\
? ASSIGN_CONTAINER(NEXT, (ITER)->MEMBER.next, MEMBER) \
: 0);
\
(ITER) = (NEXT))
struct hmap_node **buckets;
struct hmap_node *one;
size_t mask;
size_t n;
size_t hash;
struct hmap_node *next;
for (ASSIGN_CONTAINER(NODE, hmap_first(HMAP), MEMBER);
\
&(NODE)->MEMBER != NULL;
\
ASSIGN_CONTAINER(NODE, hmap_next(HMAP, &(NODE)->MEMBER), MEMBER))
整体分析
Vswitchd
模块主要包括bridge
、ofproto
等子模块。作为主要逻辑实现模块,负责解析和执行其他各个openflow
命令。
bridge模块
负责所管理的所有datapath
,对外的接口很简单,包括
void bridge_init(const char *remote);
void bridge_exit(void);
void bridge_run(void);
void bridge_run_fast(void);
void bridge_wait(void);
void bridge_get_memory_usage(struct simap *usage);
数据结构主要在bridge.c
中定义了bridge
结构,定义为
struct bridge {
};
其中,最重要的是ofproto
指针,指向一个openflow switch
,负责进行openflow switch
的所有处理。实际上,vswitchd
的主要功能就是不断检测并调用所有bridge
上的ofproto
,执行其上的处理函数。
ofproto
类型定义在ofproto/ofproto-provider.h
中。
struct ofproto {
};
其中最关键的是ofproto_class
,是ofproto
交换机的具体实现,定义了对于of
协议的处理(包括run
和run_fast
函数,前者处理更为全面,调用了后者)。处理函数的实现在ofproto/ofproto-dpif.c
中。
run_fast()
位于ofproto-dpif.c
中。
分析run_fast
函数,主要完成了两个需要周期性及时完成的事情。
首先对各个port
上调用port_run_fast
,检查是否要发送连续性检查的网包消息(CCM
,参考IEEE 802.1aq
),如果是,则发出。
HMAP_FOR_EACH (ofport, up.hmap_node, &ofproto->up.ports) {
然后,检查是否有upcall
,对所有的来自datapath
的upcall
进行处理。
while (work < FLOW_MISS_MAX_BATCH) {
handle_upcalls()
位于ofproto-dpif.c
中。
该函数从对应的dpif
中获取到upcalls
后,对upcalls
进行类型检查。对于SFLOW_UPCALL
和BAD_UPCALL
,进行对应处理后释放存有upcall
消息的buf
,而对于MISS_UPCALL
类型,则调用handle_miss_upcalls
进行后续的处理。
其中,upcall
的类型为dpif_upcall
(lib/dpif.h
),定义为
struct dpif_upcall {
};
handle_miss_upcalls ()
位于ofproto-dpif.c
中。
该函数从upcall
中提取相关的流信息,把属于同一个key
的网包放到一起,最后放进todo list
中。最后检查todo list
中的每个元素,调用handle_flow_miss()
进行处理。
HMAP_FOR_EACH (miss, hmap_node, &todo) {
}
处理完毕后,调用dpif_operate()
(位于vswitchd/dpif.c
)执行查找到的行动。
for (i = 0; i < n_ops; i++) {
handle_miss_upcall ()
位于ofproto/ofproto-dpif.c
中。
该函数处理给定的某个miss_upcall
。首先,先判断是否发生了精确匹配,如果发生了,则直接按照匹配结果调用handle_flow_miss_with_facet()
;如果没有精确匹配结果,则调用handle_flow_miss_without_facet()
。
unixctl_server相关
主循环中调用了unixctl_server_run()
函数。该函数首先获取到远端server
的连接,然后,执行连接中的命令,代码为。
LIST_FOR_EACH_SAFE (conn, next, node, &server->conns) {
ovs-vswitchd.c
主文件,其中main()
为入口主函数,执行一系列的初始化,并配置各个队列,最后是主循环,进行任务处理。分析主要代码如下
Int main(int argc, char *argv[])
{
#ifdef HAVE_MLOCKALL
#else
#endif
}
proctitle_init(argc, argv)
复制出输入的参数列表到新的存储中,让argv
指向这块内存,主要是为了后面的proctitle_set()
函数(在daemonize_start()->monitor_daemon()
中调用,可能修改原argv
存储)做准备。
set_program_name(argv[0])
设置程序名称、版本、编译日期等信息。
stress_init_command()
注册stress
相关命令(list
、set
、enable
、disable
)到commands
结构。
remote = parse_options(argc, argv, &unixctl_path)
解析参数,其中unixctl_path
存储unixctrl
域的sock
名,作为接受外部控制命令的渠道;而remote
存储连接到ovsdb
的信息,即连接到配置数据库的sock
名。
signal(SIGPIPE, SIG_IGN)
忽略pipe
读结束的信号。
sighup = signal_register(SIGHUP)
注册对
SIGHUP
信号(终端挂起)的处理函数。处理函数为写到fds[1]
中空字符。
process_init()
注册对SIGCHLD
信号(子进程结束)的处理函数。处理函数为执行all_process
上的所有进程。
ovsrec_init()
数据表结构初始化。包括13
张数据表。表的具体结构请参考ovsdb
的相关文档。
daemonize_start()
让进程变成守护程序。
worker_start()
开启一个worker
子进程。子进程与主进程交互数据。
unixctl_server_create(unixctl_path, &unixctl)
创建一个unixctl server
(存放在unixctl
),并监听在unixctl_path
指定的punix
路径。
unixctl_command_register("exit", "", 0, 0, vs_vswitchd_exit, &exiting)
注册unixctl
命令。
bridge_init(remote)
从remote
数据库获取配置信息,并初始化bridge
。
主循环
exiting = false;
}
worker_run()
执行从worker
子进程中获取的RPC reply
,执行其中的cb_reply
回调函数。主要过程为
rxbuf_run(&client_rx, client_sock, sizeof(struct worker_reply));
reply->reply_cb(&client_rx.payload, client_rx.fds,
client_rx.n_fds, reply->reply_aux);
bridge_run_fast()
执行在all_bridge
上的每个bridge
的ofproto
上的run_fast
。主要是监听和处理来自datapath
的upcall
,主要过程为
HMAP_FOR_EACH (br, node, &all_bridges) {
bridge_run()
主要的对网包进行慢速处理过程。包括完成必要的配置更新(在配置更新中会从数据库读入配置信息,生成必要的bridge
和dp
等数据结构),以及执行在all_bridge
上的每个bridge
的ofproto
上的run()
,并做相应的信息统计。
其中,ofproto
上的run()
主要依次调用了如下函数:
调用dpif_run()
处理所有注册的netlink notifier
的汇报事件。
调用run_fast()
处理常见的周期性事件,包括对upcalls
的处理等。
可选调用netflow_run()
和sflow_run()
,进行对netflow
和sflow
的支持
可选调用port_run()
进行发送CCM
。
可选调用bundle_run()
处理LACP
、bonding
等杂项。
可选调用stp_run()
进行STP
支持。
mac_learning_run()
获取超时的mac entry
,并将其删除掉。
可选调用governor_run()
进行限速处理。
unixctl_server_run(unixctl)
从unixctl
指定的server
中获取数据,并执行对应的配置命令。主要过程为
struct unixctl_conn *conn = xzalloc(sizeof *conn);
netdev_run()
执行在netdev_classes
上定义的每个netdev_class
实体,调用它们的run()
。主要过程为
SHASH_FOR_EACH(node, &netdev_classes) {
}
循环等待事件处理
包括woker
、signal
、memory
、bridge
、unixctl_server
、netdev
等事件,被poll_fd_wait()
注册。
poll_block(void)
阻塞,直到之前被poll_fd_wait()
注册过的事件发生,或者等待时间超过poll_timer_wait()
注册的最短时间。
清理工作
退出bridge
,关闭unixctl
连接,取消对sighup
信号的处理注册。
bridge_exit();
unixctl_server_destroy(unixctl);
signal_unregister(sighup);
通用类型
基础宏定义
首先分析下几个常见的基础宏。
CONTAINER_OF
宏:返回拥有某个给定member
的struct
结构的起始地址。其中,struct
为所定义的数据结构,其中有一个变量名字为member
,pointer
为指向member
变量的一个指针。该宏返回整个数据结构的起始地址。定义为
#define CONTAINER_OF(POINTER, STRUCT, MEMBER)
\
OBJECT_CONTAINING
宏:返回含有某个给定member
的对象object
的地址。
#define OBJECT_CONTAINING(POINTER, OBJECT, MEMBER)
\
ASSIGN_CONTAINER
宏:返回含有某个给定member
的对象object
和1
。
#define ASSIGN_CONTAINER(OBJECT, POINTER, MEMBER) \
普通列表
ovs
代码中大量使用了列表的结构。列表的声明在lib/list.h
中。列表的抽象结构用户不必关心,用户只需要维护好自己关心的节点的数据结构即可。
使用一个list
:
struct list L = LIST_INITIALIZER(&L)
。
正向遍历:
#define LIST_FOR_EACH(ITER, MEMBER, LIST)
\
逆向遍历
#define LIST_FOR_EACH_REVERSE(ITER, MEMBER, LIST)
\
安全遍历
#define LIST_FOR_EACH_SAFE(ITER, NEXT, MEMBER, LIST)
\
Hash列表
列表的声明在lib/hmap.h
中。
Hmap
列表,包含两个指针,指向其中含有的节点,定义为
struct hmap {
};
而hmap_node
类型,包括一个hash
值和一个后继指针,定义为
struct hmap_node {
};
对hmap
的列表遍历似乎通过如下宏来实现的
#define HMAP_FOR_EACH(NODE, MEMBER, HMAP)
\
其中ASSIGN_CONTAINER
有三个参数,分别是一个输出指针,一个输入指针和一个成员。输入指针指向该成员,而输出指针获取指向包含有该成员的类型的地址。
所以遍历宏实际上,遍历了hmap
的每个节点,并将包含该节点的数据结构逐个返回。
ofproto
ofproto_class
log消息
#define VLOG_FATAL(...) vlog_fatal(THIS_MODULE, __VA_ARGS__)
#define VLOG_ABORT(...) vlog_abort(THIS_MODULE, __VA_ARGS__)
#define VLOG_EMER(...) VLOG(VLL_EMER, __VA_ARGS__)
#define VLOG_ERR(...) VLOG(VLL_ERR, __VA_ARGS__)
#define VLOG_WARN(...) VLOG(VLL_WARN, __VA_ARGS__)
#define VLOG_INFO(...) VLOG(VLL_INFO, __VA_ARGS__)
#define VLOG_DBG(...) VLOG(VLL_DBG, __VA_ARGS__)