赵占旭的博客

[转]Cisco VPP CLI分析

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

基本概念


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

核心函数


1
2
//主线程会调用unix_cli_config,开始cli部分的初始化工作
VLIB_CONFIG_FUNCTION (unix_cli_config, "unix-cli");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/** 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//该函数启动一个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调度的前置知识。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/** 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//该函数用在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;
}

注意:所有文章非特别说明皆为原创。为保证信息与源同步,转载时请务必注明文章出处!谢谢合作 :-)

原始链接:http://zhaozhanxu.com/2016/11/15/VPP/2016-11-15-VPP-CLI/

许可协议: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。