注:本文是转载,但不是100%的转载,可能稍微有些出入,原文地址点击这里

基本概念


这个只是辅助部分,大多数人不关心,我就随便写写。

核心函数


//主线程会调用unix_cli_config,开始cli部分的初始化工作
VLIB_CONFIG_FUNCTION (unix_cli_config, "unix-cli");
/** Handle configuration directives in the @em unix section. */
static clib_error_t *unix_cli_config (vlib_main_t * vm, unformat_input_t * input)
{
    ...

    //确保unix_config执行且只执行了一次
    if ((error = vlib_call_config_function (vm, unix_config)))
      return error;

    if (um->flags & UNIX_FLAG_INTERACTIVE)
    {
        //支持交互模式
        //非阻塞模式,UNIX_CLI_STDIN_FD会交由VLIB_NODE_TYPE_PRE_INPUT类型的
        //unix-epoll-input node来监视
        if ((flags = fcntl (UNIX_CLI_STDIN_FD, F_GETFL, 0)) < 0)
          flags = 0;
        (void) fcntl (UNIX_CLI_STDIN_FD, F_SETFL, flags | O_NONBLOCK);

        //生成或者复用一个VLIB_NODE_TYPE_PROCESS类型node,
        //并调度激活node,等待来处理该cli session
        cf_index = unix_cli_file_add (cm, "stdin", UNIX_CLI_STDIN_FD);
        cf = pool_elt_at_index (cm->cli_file_pool, cf_index);
        cm->stdin_cli_file_index = cf_index;

        //下面是调整cli界面参数的,暂时不分析了。
        if (isatty (UNIX_CLI_STDIN_FD) && um->cli_line_mode == 0)
        {
            ...
        }

        //一些欢迎信息拷贝到cf->output_vector中,更新epoll状态,等待时机输出。
        unix_cli_file_welcome (cm, cf);
    }

    //下面是CLI socket支持
    /* If we have socket config, LISTEN, otherwise, don't */
    clib_socket_t *s = &um->cli_listen_socket;
    if (s->config && s->config[0] != 0)
    {
        /* CLI listen. */
        unix_file_t template = { 0 };

        s->flags = SOCKET_IS_SERVER;  /* listen, don't connect */
        //socket初始化,监听模式,代码不难
        error = clib_socket_init (s);

        if (error)
          return error;

        template.read_function = unix_cli_listen_read_ready;
        template.file_descriptor = s->fd;
        //socket加入到epoll
        unix_file_add (um, &template);
    }

    /* Set CLI prompt. */
    if (!cm->cli_prompt)
      cm->cli_prompt = format (0, "VLIB: ");

    return 0;
}
//该函数启动一个CLI session,关键是要提前理解VLIB_NODE_TYPE_PROCESS类型node运行机制
/** Store a new CLI session.
 * @param name The name of the session.
 * @param fd   The file descriptor for the session I/O.
 * @return The session ID.
 */
static u32 unix_cli_file_add (unix_cli_main_t * cm, char *name, int fd)
{
    ...

    //CLI退出时,会把处理该CLI的node放入这个复用链表池,以待继续使用
    if (vec_len (cm->unused_cli_process_node_indices) > 0)
    {
        uword l = vec_len (cm->unused_cli_process_node_indices);

        /* Find node and give it new name. */
        n = vlib_get_node (vm, cm->unused_cli_process_node_indices[l - 1]);
        vec_free (n->name);
        n->name = (u8 *) name;

        vlib_node_set_state (vm, n->index, VLIB_NODE_STATE_POLLING);

        _vec_len (cm->unused_cli_process_node_indices) = l - 1;
    }
    else
    {
        //池子里没有,就新生成一个
        static vlib_node_registration_t r = {
            .function = unix_cli_process,
            .type = VLIB_NODE_TYPE_PROCESS,
            .process_log2_n_stack_bytes = 16,
        };

        r.name = name;
        vlib_register_node (vm, &r);
        vec_free (name);

        n = vlib_get_node (vm, r.index);
    }

    pool_get (cm->cli_file_pool, cf);
    memset (cf, 0, sizeof (*cf));

    template.read_function = unix_cli_read_ready;
    template.write_function = unix_cli_write_ready;
    template.file_descriptor = fd;
    template.private_data = cf - cm->cli_file_pool;

    cf->process_node_index = n->index;
    //该文件fd加入epoll
    cf->unix_file_index = unix_file_add (um, &template);
    cf->output_vector = 0;
    cf->input_vector = 0;

    //启动该VLIB_NODE_TYPE_PROCESS类型node,它会进入“睡眠”状态,等待外部命令行输入来激活node
    vlib_start_process (vm, n->runtime_index);

    vlib_process_t *p = vlib_get_process_from_node (vm, n);
    p->output_function = unix_vlib_cli_output;
    p->output_function_arg = cf - cm->cli_file_pool;

    return cf - cm->cli_file_pool;
}

VLIB_NODE_TYPE_PROCESS类型node被CLI输入激活时,会从vlib_process_wait_for_event (vm)下一行开始执行,理解这里需要VLIB_NODE_TYPE_PROCESS类型node调度的前置知识。

/** Handle system events. */
static uword unix_cli_process (vlib_main_t * vm,
            vlib_node_runtime_t * rt, vlib_frame_t * f)
{
    unix_cli_main_t *cm = &unix_cli_main;
    uword i, *data = 0;

    while (1)
    {
        unix_cli_process_event_type_t event_type;
        vlib_process_wait_for_event (vm);
        //激活该node的事件类型
        event_type = vlib_process_get_events (vm, &data);

        switch (event_type)
        {
            //有CLI 命令来了,开始解析
            case UNIX_CLI_PROCESS_EVENT_READ_READY:
                for (i = 0; i < vec_len (data); i++)
                  //解析命令行
                  unix_cli_process_input (cm, data[i]);
                break;

                //CLI要退出了
            case UNIX_CLI_PROCESS_EVENT_QUIT:
                /* Kill this process. */
                for (i = 0; i < vec_len (data); i++)
                  //如果是标准输入上来的退出命令,那就是退出进程,否则只需要退出该CLI session
                  unix_cli_kill (cm, data[i]);
                goto done;
        }

        if (data)
          _vec_len (data) = 0;
    }

done:
    vec_free (data);

    vlib_node_set_state (vm, rt->node_index, VLIB_NODE_STATE_DISABLED);

    /* Add node index so we can re-use this process later. */
    //该node完成使命,不需要在主循环上“睡眠”了,丢给复用池
    vec_add1 (cm->unused_cli_process_node_indices, rt->node_index);

    return 0;
}
//该函数用在epoll监听到有数据到来时,调用它接受数据。逻辑不难。
/** Called when a CLI session file descriptor has data to be read. */
static clib_error_t *unix_cli_read_ready (unix_file_t * uf)
{
    unix_main_t *um = &unix_main;
    unix_cli_main_t *cm = &unix_cli_main;
    unix_cli_file_t *cf;
    uword l;
    int n, n_read, n_try;

    cf = pool_elt_at_index (cm->cli_file_pool, uf->private_data);

    n = n_try = 4096;
    while (n == n_try)
    {
        l = vec_len (cf->input_vector);
        vec_resize (cf->input_vector, l + n_try);

        n = read (uf->file_descriptor, cf->input_vector + l, n_try);

        /* Error? */
        if (n < 0 && errno != EAGAIN)
          return clib_error_return_unix (0, "read");

        n_read = n < 0 ? 0 : n;
        _vec_len (cf->input_vector) = l + n_read;
    }

    if (!(n < 0))
      vlib_process_signal_event (um->vlib_main,
                  cf->process_node_index,
                  (n_read == 0
                   ? UNIX_CLI_PROCESS_EVENT_QUIT
                   : UNIX_CLI_PROCESS_EVENT_READ_READY),
                  /* event data */ uf->private_data);

    return /* no error */ 0;
}

//epoll把需要回馈给用户的CLI输出,写入。
/** Called when a CLI session file descriptor can be written to without
 * blocking. */
static clib_error_t *unix_cli_write_ready (unix_file_t * uf)
{
    unix_cli_main_t *cm = &unix_cli_main;
    unix_cli_file_t *cf;
    int n;

    cf = pool_elt_at_index (cm->cli_file_pool, uf->private_data);

    /* Flush output vector. */
    n = write (uf->file_descriptor,
                cf->output_vector, vec_len (cf->output_vector));

    if (n < 0 && errno != EAGAIN)
      return clib_error_return_unix (0, "write");

    else if (n > 0)
      unix_cli_del_pending_output (uf, cf, n);

    return /* no error */ 0;
}
最后修改:2021 年 08 月 18 日
如果觉得我的文章对你有用,请随意赞赏