
前面文章有介绍过通过make-plugin.sh命令行自动生成plugins的架构,vpp 软件架构介绍;本文就来讲解一下plugins插件的加载和使用流程。
plugins插件加载是比较靠前的,应为plugins里面会有node节点的注册,需要在生成node节点注册及node图初始化完成之前。下面是阅读源码后总结的流程:
int main (int argc, char *argv[]) /*main函数位置src\vpp\vnet\main.c*/
| |--解析配置文件是否配置plugin_path路径,如果配置存储到全局变量vlib_plugin_path
| | (!strncmp (argv[i], "plugin_path", 11)) vlib_plugin_path = argv[++i];
| |--vpe_main_init (vm)/*如果没由配置plugin_path路径,如果动态查询*/
| |--vpp_find_plugin_path()/*查询plugin目录,配置到全局变量vlib_plugin_path */
| |--vlib_unix_main()
| |--vlib_plugin_config() /*解析配置文件中plugins配置信息*/
| |--vlib_plugin_early_init()加载插件
| |--vlib_main()
| |-- vlib_register_all_static_nodes (vm)/*node节点注册*/
| |--vlib_node_main_init (vm) /*node 图初始化*/插件必须在vlib_main前执行的原因: plugin库中有很多attribute__((__constructor))类型的构造函数是在dlopen()装载时被调用。 比如下面的宏定义完成注册,都是串联到全局的链表上, VLIB_CONFIG_FUNCTION (acl_plugin_config, "acl-plugin"); VLIB_REGISTER_NODE (acl_in_l2_ip6_node)
通过命令行show plugins可以查询当前环境vpp加载插件信息及插件描述信息(版本号,名称,描述)。
DBGvpp# show plugins
Plugin path is: /usr/lib/x86_64-linux-gnu/vpp_plugins:/usr/lib/vpp_plugins
Plugin Version Description
1. ioam_plugin.so 20.09-rc0~472-g4ee78fbcc Inbound Operations, Administration, and Maintenance (OAM)
2. perfmon_plugin.so 20.09-rc0~472-g4ee78fbcc Performance Monitor
3. tracedump_plugin.so 20.09-rc0~472-g4ee78fbcc Streaming packet trace dump plugin
4. urpf_plugin.so 20.09-rc0~472-g4ee78fbcc Unicast Reverse Path Forwarding (uRPF)
5. tlspicotls_plugin.so 20.09-rc0~472-g4ee78fbcc Transport Layer Security (TLS) Engine, Picotls Based
6. l3xc_plugin.so 20.09-rc0~472-g4ee78fbcc L3 Cross-Connect (L3XC)
7. mdata_plugin.so 20.09-rc0~472-g4ee78fbcc Buffer metadata change tracker.
上面讲到如果配置文件中没有设置plugin_path插件路径时,会动态查询,下面时函数vpp_find_plugin_path 查询插件路径的部分代码如下:
static void vpp_find_plugin_path ()
{
extern char *vat_plugin_path;
char *p, path[PATH_MAX];
int rv;
u8 *s;
/* find executable path */
if ((rv = readlink ("/proc/self/exe", path, PATH_MAX - 1)) == -1)
return;
/* readlink doesn't provide null termination */
path[rv] = 0;
/* strip filename strip bin 跳过执行文件名*/
...
s = format (0, "%s/lib/" CLIB_TARGET_TRIPLET "/vpp_plugins:"
"%s/lib/vpp_plugins", path, path);
vec_add1 (s, 0);
/*保存插件路径*/
vlib_plugin_path = (char *) s;
....
}
上一节通过show plugins命令行查询到当前环境plugins目录:
/usr/lib/x86_64-linux-gnu/vpp_plugins:/usr/lib/vpp_plugins
这里由2点疑问: 1、readlink函数作用? readlink()会将参数path的符号链接内容存储到参数buf所指的内存空间,返回的内容不是以‘\0’作字符串结尾,但会将字符串的字符数返回,这使得添加‘\0’变得简单。若参数bufsiz小于符号连接的内容长度,过长的内容会被截断;如果 readlink 第一个参数指向一个文件而不是符号链接时,readlink 设 置errno 为 EINVAL 并返回 -1。readlink()函数组合了open()、read()和close()的所有操作。 path是一个存在的软连接。 path="/proc/self/exe"标识获取当前执行程序的绝对路径。
在github上写了readlink测测试程序,你可以在你环境上去执行看一下:https://github.com/jin13417/dpdk-vpp-learning/test/readlink.c
2、CLIB_TARGET_TRIPLET 这个宏应该就是x86_64-linux-gnu,怎么来的。 这里是使用了cmake的configure_file来设置的CLIB_TARGET_TRIPLET。你可以通过下面链接来学习cmake的configure_file使用。
https://www.cnblogs.com/gaox97329498/p/10952732.html
1、在源码路径上通过grep -rn CLIB_TARGET_TRIPLET 查询宏定义的位置
/mnt/f/workspce/vpp/src$ grep -rn CLIB_TARGET_TRIPLET
vppinfra/config.h.in:23:#define CLIB_TARGET_TRIPLET "@CMAKE_C_COMPILER_TARGET@"
2、再次查询CMAKE_C_COMPILER_TARGET,如下
:/mnt/f/workspce/vpp/src$ grep -rn CMAKE_C_COMPILER_TARGET
CMakeLists.txt:46:set(CMAKE_C_COMPILER_TARGET ${CMAKE_SYSTEM_PROCESSOR}-linux-gnu)
CMAKE_SYSTEM_PROCESSOR是cmake环境变量,我们可以通过下面命令来查询设置。
$ cmake --system-information | grep CMAKE_SYSTEM_PROCESSOR
CMAKE_SYSTEM_PROCESSOR "x86_64"
3、configure_file设置时在vppinfra/CMakeLists.txt文件中。
###vppinfra/CMakeLists.txt cmake编译文件生成config.h 定义了CLIB_TARGET_TRIPLET 内容
configure_file(
${CMAKE_SOURCE_DIR}/vppinfra/config.h.in
${CMAKE_BINARY_DIR}/vppinfra/config.h
)
4、下面时我编译完成后再vppinfra目录下config.h的内容
cat build-root/install-vpp-native/vpp/include/vppinfra/config.h
...
#ifndef included_clib_config_h
#define included_clib_config_h
#ifndef CLIB_LOG2_CACHE_LINE_BYTES
#define CLIB_LOG2_CACHE_LINE_BYTES 6
#endif
#define CLIB_TARGET_TRIPLET "x86_64-linux-gnu"
#define CLIB_VECTOR_GROW_BY_ONE 0
#endif
vlib_plugin_config函数完成plugins相关配置信息的解析,代码比较简单这里就不说了。下面是vpp默认statup.conf文件中plugins字段的信息内容
plugins {
## Adjusting the plugin path depending on where the VPP plugins are
#path /ws/vpp/build-root/install-vpp-native/vpp/lib/vpp_plugins
## Disable all plugins by default and then selectively enable specific plugins
#plugin default { disable }
plugin dpdk_plugin.so { enable }
plugin acl_plugin.so { enable }
## Enable all plugins by default and then selectively disable specific plugins
# plugin dpdk_plugin.so { disable }
# plugin acl_plugin.so { disable }
}
除了默认配置文件中写的,还又下面不经常使用的参数: name-filter :设置过滤的插件名称 vat-path :vat插件路径 vat-name-filter:设置vat过滤插件名称 plugin acl_plugin.so skip-version-check :跳过版本的检查。在注册插件的时候执行依赖vpp的版本时,才会检查。
1、上述配置中,设置了加载dpdk、acl查询但是命令行查询的时候,还是全部加载上了,这个因为插件默认是全部加载的,只能先disable所有的插件,再设置需要加载的插件才能生效。 2、path、plugin_path设置有什么区别?两个都设置后,那个生效? 这个需要阅读代码的实现了,默认是path的优先级更高。
下面是单元测试插件的注册函数,默认指定是不需要加载的。
VLIB_PLUGIN_REGISTER () =
{
.version = VPP_BUILD_VER,
.description = "C unit tests",
.default_disabled = 1,
};
对应相关结构体字段的描述信息。
/* *INDENT-OFF 注册插件结构体字段描述* */
typedef CLIB_PACKED(struct {
u8 default_disabled; /*是否需要加载*/
const char version[32]; /*插件的版本信息*/
const char version_required[32];/*设置查询依赖的vpp版本信息*/
const char overrides[256];/*不是太理解*/
const char *early_init; /*指定early_init函数名称,在加载so时进行初始化*/
const char *description;/*插件描述信息*/
}) vlib_plugin_registration_t;
/* *INDENT-ON 插件信息结构体* */
typedef struct
{
u8 *name; /*plugin插件名称libcpudef_cplist_plugin.so*/
u8 *filename;/*plugin so文件名称libcpudef_cplist_plugin.so.0.0.0*/
struct stat file_info;/*文件详细信息*/
void *handle;/*dlopen返回的so句柄*/
/* plugin registration 插件注册信息*/
vlib_plugin_registration_t *reg;
char *version;/*版本信息*/
} plugin_info_t;
平时不太关注插件注册的一些信息,但是可能在某些场景中会使用到,不如下面提供了2个比较好的参数 1、version_required:设置当前so依赖的vpp版本信息,如果和vpp版本不一会会报错。 但是也可以在配置文件中设置跳过检查, 2、early_init:指定early_init函数名称,在加载so时执行初始化 使用dlsym函数得到函数的地址,执行初始化动作。
插件加载的流程大概意义就是读取插件目录下的文件,判断是否是.so格式后,逐个调用load_one_plugin函数来加载动态so。代码也比较简单,自己看下吧。
vlib_plugin_early_init()
|---vlib_load_new_plugins()
|---load_one_plugin()vpp本身并不支持插件之间互相访问,但是我们可以通过一些方法来解决。我工作中用的方法就行是src目录下添加一个test目录,参考src/vet/CMakeLists.txt的方式来定义自己的so库。
/*CMakeLists.txt如下*/
##############################################################################
# test Library
##############################################################################
add_vpp_library(test
SOURCES ${TEST_SOURCES}
MULTIARCH_SOURCES ${TEST_MULTIARCH_SOURCES}
INSTALL_HEADERS ${TEST_HEADERS}
API_FILES ${TEST_API_FILES}
LINK_LIBRARIES vppinfra svm vlib ${OPENSSL_LIBRARIES}
DEPENDS vpp_version_h api_headers
)
把2个插件都需要调用函数,或者全局变量放在这里面来声明和定义(当然你也可以直接放在vnet下)。
本文简单描述了vpp插件的加载流程及代码逻辑的一些解读,另外前段时间在vpp群里面有同学问到两个插件之间怎么互相访问。上面也说了大概实现思路。你们有更好的方法,欢迎留言讨论。
本文分享自 DPDK VPP源码分析 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!