初始化
这里需要了解vpp启动过程中存在初始化宏函数的执行顺序。当前unix cli相关资源的使用就依赖这个顺序来保证的。下面先来了解一下:
1、early config function 宏注册函数首先执行,主要获取startup配置文件中配置信息,init函数或其他初始化资源需要使用的配置,需要提前读取到。 比如buffers(获取报文缓存区资源大小,需要在vlib_buffer_main_init函数前)、cpu(cpu核绑定数据,需要在vlib_thread_init函数之前)等。
VLIB_EARLY_CONFIG_FUNCTION (unix_config, "buffers");
2、init funtion类函数执行初始化资源,大部分函数都使用此函数进行初始化,主要是一些报文转发过程中需要的一些内存资源。有些资源必须在config function配置之前进行初始化。必须vpp cli相关的资源。
VLIB_INIT_FUNCTION (linux_epoll_input_init);
3、confg function类函数,读取非early startup.conf 文件配置资源信息。
VLIB_CONFIG_FUNCTION (dpdk_config, "dpdk");
4、worker init 类函数在start worker线程前,有些node节点的running数据需要在创建线程前进行初始化,主要是因为在创建worker线程时,需要对node节点进行copy(每个worker核都有自己独立一套转发node资源,避免加锁。)。比如下面l2_input_classify_worker举例
VLIB_WORKER_INIT_FUNCTION (l2_input_classify_worker_init);/*以l2_input_classify_worker为例*/
5、执行main loop 报文调度处理前一些函数。
比如下面start_workers 用来创建worker及相应的资源。详细的可以看一下公众号以前的文章--创建自己的核绑定线程
VLIB_MAIN_LOOP_ENTER_FUNCTION (start_workers);
vpp最新版本中epool已经支持了多核模式,但是在linux_epoll_input_init初始化时只创建了main核的epool_fd套接字。具体代码如下:
clib_error_t *
linux_epoll_input_init (vlib_main_t * vm)
{
linux_epoll_main_t *em;
clib_file_main_t *fm = &file_main;
vlib_thread_main_t *tm = vlib_get_thread_main ();
/*每个worker线程对应一个linux_epoll_mains,这里cache一致性问题,使用64字节d对齐 */
vec_validate_aligned (linux_epoll_mains, tm->n_vlib_mains,
CLIB_CACHE_LINE_BYTES);
vec_foreach (em, linux_epoll_mains)
{
/* Allocate some events.申请epool_wait 返回的事件集合 */
vec_resize (em->epoll_events, VLIB_FRAME_SIZE);
if (linux_epoll_mains == em)
{/*当前只为main核创建epool文件描述符*/
em->epoll_fd = epoll_create (1);
if (em->epoll_fd < 0)
return clib_error_return_unix (0, "epoll_create");
}
else/*其他workers核未创建*/
em->epoll_fd = -1;
}
/*设置全局file_main file_update函数*/
fm->file_update = linux_epoll_file_update;
return 0;
}
/*main函数执行前注册linux_epoll_input_init函数*/
VLIB_INIT_FUNCTION (linux_epoll_input_init);
由unix_cli_config 函数完成unix cli server 端socket创建并使用vppinfra/file模块相关api加入到epool监听中。
VLIB_CONFIG_FUNCTION (unix_cli_config, "unix-cli");
static clib_error_t *
unix_cli_config (vlib_main_t * vm, unformat_input_t * input)
{
......
/*设置server及PF_local套接字*/
s->flags = CLIB_SOCKET_F_IS_SERVER | /* listen, don't connect */
CLIB_SOCKET_F_ALLOW_GROUP_WRITE; /* PF_LOCAL socket only */
/*socket 初始化*/
error = clib_socket_init (s);
/*socket 设置回调处理函数、文件描述符、描述信息*/
template.read_function = unix_cli_listen_read_ready;
template.file_descriptor = s->fd;
template.description = format (0, "cli listener %s", s->config);
/*添加到file_main中,会调用file_update函数加入到epool中*/
clib_file_add (fm, &template);
}
这里需要注意:clib_file_add 返回值是在file_mian->file_pool 内存池中的索引,因为unix cli server是不无需释放的,所以并没有报错返回数值。在vppsb代码在enable tap-inject后,会创建一个套接字接收内核-》vpp的报文。但是并没有存储clib_file_add 的返回值,会导致pool资源泄漏问题。下面是enable/disable/enable tap-inject操作后信息:很明显已经存在资源泄漏。
learning_vpp# show unix files
FD Thread Read Write Error File Name Description
11 0 0 0 0 socket:[49423] stats segment listener /run/vpp/stats.sock
13 0 1 0 0 pipe:[49426] DPDK logging pipe
33 0 2 0 0 socket:[49438] cli listener 0.0.0.0:5002
34 0 0 0 0 socket:[49440] socksvr /run/vpp/api.sock
35 0 156 1 0 socket:[49668] 127.0.0.1:45350
37 0 0 0 0 /dev/net/tun
38 0 0 0 0 /dev/net/tun
39 0 0 0 0 /dev/net/tun
40 0 36 0 0 socket:[49705]
37 0 0 0 0 /dev/net/tun
38 0 0 0 0 /dev/net/tun
39 0 0 0 0 /dev/net/tun
cli listener 0.0.0.0:5002:就是创建后cli server监听描述符
由linux_epoll_input_node节点轮询方式执行epool_wait等待unix cli客户端的连接请求。
/* *epool node节点未pre input类型* */
VLIB_REGISTER_NODE (linux_epoll_input_node,static) = {
.function = linux_epoll_input,
.type = VLIB_NODE_TYPE_PRE_INPUT,
.name = "unix-epoll-input",
};
static_always_inline uword
linux_epoll_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
vlib_frame_t * frame, u32 thread_index)
{
.....
/*epoll_pwait 用于轮询I/O事件的发生*/
static sigset_t unblock_all_signals;
n_fds_ready = epoll_pwait (em->epoll_fd,
em->epoll_events,vec_len (em->epoll_events),
timeout_ms, &unblock_all_signals);
for (e = em->epoll_events; e < em->epoll_events + n_fds_ready; e++)
{
u32 i = e->data.u32;
clib_file_t *f;
clib_error_t *errors[4];
int n_errors = 0;
/*
* Under rare scenarios, epoll may still post us events for the
* deleted file descriptor. We just deal with it and throw away the
* events for the corresponding file descriptor.
*/
f = fm->file_pool + i;
if (PREDICT_FALSE (pool_is_free (fm->file_pool, f)))
{/*异常判断 pool资源已被释放无法处理信息*/
.....
}
else if (PREDICT_TRUE (!(e->events & EPOLLERR)))
{
if (e->events & EPOLLIN)
{/*处理读事件*/
f->read_events++;
errors[n_errors] = f->read_function (f);
/* Make sure f is valid if the file pool moves */
if (pool_is_free_index (fm->file_pool, i))
continue;
f = pool_elt_at_index (fm->file_pool, i);
n_errors += errors[n_errors] != 0;
}
if (e->events & EPOLLOUT)
{/*处理写事件*/
f->write_events++;
errors[n_errors] = f->write_function (f);
n_errors += errors[n_errors] != 0;
}
}
else /*处理异常记录*/
{
if (f->error_function)
{
f->error_events++;
errors[n_errors] = f->error_function (f);
n_errors += errors[n_errors] != 0;
}
else
close (f->file_descriptor);
}
}
我们可以通过vpp cli show unix errors来查询异常信息记录
learning_vpp# show unix errors
no Unix errors so far #表示当前没有异常发生
至此,unix cli server已经建立完成等待cli连接。
相关结构体关系:如下图所示
相关原图放在github上,有兴趣的自己下载观看:https://github.com/jin13417/dpdk-vpp-learning/tree/main/new_map
vpp unix cli-listen中提供了三种配置方式
unix {
#cli-listen /run/vpp/cli.sock
#unix域套接字。只支持使用vppctl命令登录
#cli-listen localhost:5002
#INADDR_LOOPBACK, 也就是绑定地址LOOPBAC, 往往是127.0.0.1, 只能收到127.0.0.1上面的连接请求。
#只支持本地Telnet 0 5002 登录
cli-listen 0.0.0.0:5002
#INADDR_ANY是ANY,是绑定地址0.0.0.0上的监听, 能收到任意一块网卡的连接。支持远程telnet 登录
}
下面用例中使用第二种配置方式,当在Telnet 0 5002 登录,在linux_epool_input中epool_wait监听到unix cli listen fd可读事件后调用unix_cli_listen_read_ready进行相应的处理。如下所示:
/*epool监听到unix cli listen fd可读事件后调用unix_cli_listen_read_ready处理函数*/
unix_cli_listen_read_ready()
| |---clib_socket_accept (s, &client)
| |---client->fd = accept (server->fd, 0, 0);/*接收telnet连接请求*/
| |---设置客户端fd非阻塞模式,保存客户端地址信息,设置client->flag客
| |. 户端标识并设置对应的处理函数。
| |----cf_index = unix_cli_file_add (cm, client_name, client.fd);
| |---关键处理函数,下一节再详细分析,创建一个porcess类型node的节点为
| | 处理cli会话的输入信息。
| |---默认要以字符模式运行Telnet会话,需要设置telnet一些基本信息。
本文主要介绍了unix cli命令行server端初始化流程及处理cli链接请求流程。cli会话建立以后的动作后续再详细介绍。在阅读源码中发现vpp在代码在使用vppinfra基础库中函数还是不够安全,所以我们不能直接copy相关的处理逻辑,有时间的话还是要用心阅读一下底层的实现逻辑,了解其底层内存使用分布情况。
相关vppinfra文章请阅读:vppinfra--- file.h: unix file handling
本文分享自 DPDK VPP源码分析 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!