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関数の続きです。



このエントリーをはてなブックマークに追加