Open vSwitchのソースコードを読む(4) ovs-vswitchdの初期化
2013-05-17 by Daisuke Kotani前回までは、Kernel moduleの処理を読んでいました。今回はkernel moduleを直接管理しているuserlandのプロセスovs-vswitchdを見ていきます。主なソースコードはvswitchd/以下にあります。
ドキュメント
vswitchd/INTERNALSというファイルがありますが、これにはBondingに関する記述しかないので、今回は無視します。
初期化
vswitchd/ovs-vswitchd.cにmain関数があります。初期化の部分はwhile (!existing) {のループの前までです。初期化の前の部分を抜粋。
int
main(int argc, char *argv[])
{
char *unixctl_path = NULL;
struct unixctl_server *unixctl;
struct signal *sighup;
char *remote;
bool exiting;
int retval;
proctitle_init(argc, argv);
set_program_name(argv[0]);
stress_init_command();
proctitle_initはlib/command-line.cにあります。これは何もしていません。set_program_nameはプログラム名をグローバル変数にセットする関数で、これもメインの処理にはあまり関係なさそうです。
stress_init_commandはstress機能に関する管理用のコマンドを登録するものです。
void
stress_init_command(void)
{
unixctl_command_register("stress/list", "", 0, 1,
stress_unixctl_list, NULL);
unixctl_command_register("stress/set", "option period [random | periodic]",
2, 3, stress_unixctl_set, NULL);
unixctl_command_register("stress/enable", "", 0, 0,
stress_unixctl_enable, NULL);
unixctl_command_register("stress/disable", "", 0, 0,
stress_unixctl_disable, NULL);
}
ovs-vswitchdとその他の管理用のコマンド(ovs-appctlなど)はunix socketを使って通信していて、unixctl_command_register関数を使って、ovs-vswitchdのunix socketのサーバにコマンド名と実行する関数を登録しています。 例えば、stress/listというコマンドが送られてきた時に、stress_unixctl_listという関数で処理する、といったものです。
main関数に戻ります。
remote = parse_options(argc, argv, &unixctl_path);
signal(SIGPIPE, SIG_IGN);
sighup = signal_register(SIGHUP);
process_init();
ovsrec_init();
daemonize_start();
if (want_mlockall) {
#ifdef HAVE_MLOCKALL
if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
VLOG_ERR("mlockall failed: %s", strerror(errno));
}
#else
VLOG_ERR("mlockall not supported on this system");
#endif
}
worker_start();
retval = unixctl_server_create(unixctl_path, &unixctl);
if (retval) {
exit(EXIT_FAILURE);
}
unixctl_command_register("exit", "", 0, 0, ovs_vswitchd_exit, &exiting);
bridge_init(remote);
free(remote);
parse_options関数はコマンドライン引数を解析するものです。
signal_register関数(lib/signals.cにあります)は、Linuxのsignalを受け取った時に実行する関数を登録する関数です。
process_init関数はlib/process.cにあるもので、SIGCHLD(子プロセスが死んだ時に親プロセスに通知するシグナル)を受け取った時に実行する関数を設定しています。
ovsrec_init関数はOVS内のいろんなレコードを初期化しているもので、lib/vswitch-idl.cにあります。ovsdbについて知りたいなら読んだほうがよいとは思いますが、今回はovsdbまで読むつもりはないので無視します。
daemonize_start関数はforkしてdaemonを作るための関数。
worker_start関数は文字通りworkerをスタートするものです。ここで言うworkerはOpen vSwitchのRPCを別のプロセスで処理するもので、lib/worker.cにあります。worker_start関数では、requestを受け取って処理してreplyを返すworker_main関数(lib/worker.c)を子プロセスで実行しています。親プロセスでreplyを受け取って処理するのは、main loopの中のworker_run関数です。
unixctl_server_create関数は、stress_init_command関数のところで触れた、管理用のコマンドunix socketを使って通信するためのサーバを作る(listenする)関数です。この通信の部分のコードはlib/unixctl.cにあります。
次のbridge_init関数は、bridgeのための初期化を行っているもので、vswitchd/bridge.cにあります。ovsdbに接続して、どの値を使うかなどを設定したり、各機能を管理する上で必要なコマンドをunixctlに登録したりしています。
ofproto libraryの初期化
様々なdatapathを統一して扱うofprotoライブラリの初期化は、main loopの中のbridge_run関数の中で呼び出されています。bridge_init_ofprotoという関数です。vswitchd/bridge.cにあります。
static void
bridge_init_ofproto(const struct ovsrec_open_vswitch *cfg)
{
struct shash iface_hints;
static bool initialized = false;
int i;
if (initialized) {
return;
}
initialized変数を使って、初期化が1回しか行われれないように制御されています。
shash_init(&iface_hints);
if (cfg) {
for (i = 0; i < cfg->n_bridges; i++) {
const struct ovsrec_bridge *br_cfg = cfg->bridges[i];
int j;
for (j = 0; j < br_cfg->n_ports; j++) {
struct ovsrec_port *port_cfg = br_cfg->ports[j];
int k;
for (k = 0; k < port_cfg->n_interfaces; k++) {
struct ovsrec_interface *if_cfg = port_cfg->interfaces[k];
struct iface_hint *iface_hint;
iface_hint = xmalloc(sizeof *iface_hint);
iface_hint->br_name = br_cfg->name;
iface_hint->br_type = br_cfg->datapath_type;
iface_hint->ofp_port = iface_pick_ofport(if_cfg);
shash_add(&iface_hints, if_cfg->name, iface_hint);
}
}
}
}
ofproto_init(&iface_hints);
shash_destroy_free_data(&iface_hints);
initialized = true;
}
bridgeやポートのリストを作って、ofproto_init関数を呼び出しています。これはofproto/ofproto.cの239行目付近にあります。
void
ofproto_init(const struct shash *iface_hints)
{
struct shash_node *node;
size_t i;
ofproto_class_register(&ofproto_dpif_class);
/* Make a local copy, since we don't own 'iface_hints' elements. */
SHASH_FOR_EACH(node, iface_hints) {
const struct iface_hint *orig_hint = node->data;
struct iface_hint *new_hint = xmalloc(sizeof *new_hint);
const char *br_type = ofproto_normalize_type(orig_hint->br_type);
new_hint->br_name = xstrdup(orig_hint->br_name);
new_hint->br_type = xstrdup(br_type);
new_hint->ofp_port = orig_hint->ofp_port;
shash_add(&init_ofp_ports, node->name, new_hint);
}
for (i = 0; i < n_ofproto_classes; i++) {
ofproto_classes[i]->init(&init_ofp_ports);
}
}
ofproto_class_registerは、各datapathへの様々な処理(生成、main loopの中で実行する処理など)を行う関数へのポインタを持つofproto_class構造体をofproto_classes配列に追加するものです。ofproto/ofproto.cにあります。ofproto_normalize_type関数は、インターフェイスの種類をofproto内で使用されるフォーマットに揃えるものです。最後に各classの初期化関数が呼ばれています。
このコードでは、dpif classのみが登録されています。これのofproto_class構造体は、ofproto/ofproto-dpif.cの最下部のofproto_dpif_classという変数です。initというメンバ変数にinitという関数のポインタが設定されているので、ofproto/ofproto-dpif.cのinit関数を読んでいきます。
static void
init(const struct shash *iface_hints)
{
struct shash_node *node;
/* Make a local copy, since we don't own 'iface_hints' elements. */
SHASH_FOR_EACH(node, iface_hints) {
const struct iface_hint *orig_hint = node->data;
struct iface_hint *new_hint = xmalloc(sizeof *new_hint);
new_hint->br_name = xstrdup(orig_hint->br_name);
new_hint->br_type = xstrdup(orig_hint->br_type);
new_hint->ofp_port = orig_hint->ofp_port;
shash_add(&init_ofp_ports, node->name, new_hint);
}
}
iface_hintsをinit_ofp_portsというhash tableにコピーしててるだけですね。
次回の予定
初期化の部分は終わりにして、次はmain loopを読みます。main関数の続きです。
- Open vSwitchのソースコードを読む(11) flow table のエントリ
- Open vSwitchのソースコードを読む(10) flow table の検索
- Open vSwitchのソースコードを読む(9) dpif
- Open vSwitchのソースコードを読む(8) OpenFlow channel の管理
- Open vSwitchのソースコードを読む(7) ovs-vswitchd の ofproto
Tweet