思科VPP源码分析(CLI支持分析)

基本概念

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

核心函数

//主线程会调用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_main_t *um = &unix_main;
  unix_cli_main_t *cm = &unix_cli_main;
  int flags;
  clib_error_t *error = 0;
  unix_cli_file_t *cf;
  u32 cf_index;
  struct termios tio;
  struct sigaction sa;
  struct winsize ws;
  u8 *term;

  /* We depend on unix flags being set. */
  //确保unix_config执行且只执行了一次
  if ((error = vlib_call_config_function (vm, unix_config)))
    return error;

  if (um->flags & UNIX_FLAG_INTERACTIVE)
    {
      //支持交互模式
      /* Set stdin to be non-blocking. */
      /*非阻塞模式,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 stdin is a tty and we are using chacracter mode, enable
       * history on the CLI and set the tty line discipline accordingly. */
      if (isatty (UNIX_CLI_STDIN_FD) && um->cli_line_mode == 0)
    {
      /* Capture terminal resize events */
      memset (&sa, 0, sizeof (sa));
      sa.sa_handler = unix_cli_resize_interrupt;
      if (sigaction (SIGWINCH, &sa, 0) < 0)
        clib_panic ("sigaction");

      /* Retrieve the current terminal size */
      ioctl (UNIX_CLI_STDIN_FD, TIOCGWINSZ, &ws);
      cf->width = ws.ws_col;
      cf->height = ws.ws_row;

      if (cf->width == 0 || cf->height == 0)
        /* We have a tty, but no size. Stick to line mode. */
        goto notty;

      /* Setup the history */
      cf->history_limit = um->cli_history_limit;
      cf->has_history = cf->history_limit != 0;

      /* Setup the pager */
      cf->no_pager = um->cli_no_pager;

      /* We're going to be in char by char mode */
      cf->line_mode = 0;

      /* Save the original tty state so we can restore it later */
      tcgetattr (UNIX_CLI_STDIN_FD, &um->tio_stdin);
      um->tio_isset = 1;

      /* Tweak the tty settings */
      tio = um->tio_stdin;
      /* echo off, canonical mode off, ext'd input processing off */
      tio.c_lflag &= ~(ECHO | ICANON | IEXTEN);
      tio.c_cc[VMIN] = 1;   /* 1 byte at a time */
      tio.c_cc[VTIME] = 0;  /* no timer */
      tcsetattr (UNIX_CLI_STDIN_FD, TCSAFLUSH, &tio);

      /* See if we can do ANSI/VT100 output */
      term = (u8 *) getenv ("TERM");
      if (term != NULL)
        cf->ansi_capable = unix_cli_terminal_type (term,
                               strlen ((char *)
                                   term));
    }
      else
    {
    notty:
      /* No tty, so make sure these things are off */
      cf->no_pager = 1;
      cf->history_limit = 0;
      cf->has_history = 0;
      cf->line_mode = 1;
    }

      /* Send banner and initial prompt */
      //一些欢迎信息拷贝到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)
{
  unix_main_t *um = &unix_main;
  unix_cli_file_t *cf;
  unix_file_t template = { 0 };
  vlib_main_t *vm = um->vlib_main;
  vlib_node_t *n;

  name = (char *) format (0, "unix-cli-%s", name);

  //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;
}

你可能感兴趣的:(VPP)