VPP 插件plugin 节点node 编排feature 创建--关键流程与框架


Pt.1 Cisco VPP启动流程

Step.0 自定义上电时配置·文件读取

1 VLIB_CONFIG_FUNCTION           //
2 or  
3 VLIB_EARLY_CONFIG_FUNCTION     //

配置文件:

/etc/vpp/startup.conf

e.g. 
VLIB_EARLY_CONFIG_FUNCTION (unix_config, "unix");
从配置文件/etc/vpp/startup.conf 中 添加你的参数列表,格式如下:

unix { arg1 arg2 arg3 … }

 

Step.1 初始化

 1 VLIB_INIT_FUNCTION      //用来定义"插件/节点、API、CLI等,运行环境的初始化函数" 

注册函数到vlib_main_t->init_function_registrations,这个链表在main()函数之前创建。

vlib_main()->vlib_call_all_init_functions()注册的函数在这里被调用初始化,最后执行函数vlib_main_loop()。

像这样由 宏定义和构造函数创建的全局链表 的方式还有如下几个:

 1 ·VLIB_API_INIT_FUNCTION
 2 
 3 ·VLIB_CLI_COMMAND         //用来初始化“插件CLI”  4 
 5 ·VLIB_CONFIG_FUNCTION
 6 
 7 ·VLIB_EARLY_CONFIG_FUNCTION
 8 
 9 ·VLIB_MAIN_LOOP_ENTER_FUNCTION
10 
11 ·VLIB_MAIN_LOOP_EXIT_FUNCTION
12 
13 ·VLIB_REGISTER_NODE
14 
15 ·VLIB_PLUGIN_REGISTER        //用来初始化"插件描述": 版本与名称

 

Step.2  vpp/vnet/main.c的main()函数:主main线程

程序的入口,设置vlib_plugin_main.handoff_structure_get_cb函数指针,指向vpp/vnet/main.c中的函数vnet_get_handoff_structure。

vlib/unix/plugin.c中的vnet_get_handoff_structure()函数调用上面的的函数指针handoff_structure_get_cb。

vnet_get_handoff_structure() 定义了一个静态变量,这个静态变量包含了主要数据指针,比如vlib_main,vnet_main和ethernet_main(看代码没有vlib_main)。每个插件注册的时候,都会通过vlib_plugin_register()将以上数据传递给插件。

Step.3 vlib/unix/main.c的vlib_unix_main()函数:插件加载

vlib_plugin_early_init()函数会通过dlopen加载插件目录下的所有插件,这个目录可以通过命令行指定。默认的插件路径是/usr/lib/vpp_plugins。

dlopen每个插件后,VPP会获取函数vlib_plugin_register的符号地址,所以每个插件都要求实现该函数,之前说过这个函数会传递非常重要的数据。

vlib_call_all_config_functions()函数解析所有的命令行选项,并且针对前期需求配置。

为以下线程创建线程栈,主要有三种类型的线程需要实现:

普通线程:比如统计采集。

EAL线程:处理包的工作。

Processes:这些都是定期执行、相互协作的多线程。比如DHCP租期续订的线程等。VPP主线程的超时到期之后会执行这些。

最后,该函数跳转到thread0()函数。

Step.4  vlib/unix/main.c的thread0()函数

调用vlib/main.c的vlib_main()函数

Step.5 vlib/main.c的vlib_main()函数

VLIB_REGISTER_NODE定义图节点,注册到vlib_main_t->node_registrations,vlib_register_all_static_nodes()遍历这个链表,创建图结点(不是连接,是创建)。

VLIB_INIT_FUNCTION声明的函数,由vlib_call_all_init_functions()调用初始化。

如果结点被创建,vlib/node.c的vlib_node_main_init()会对图结点进行初始化。

VLIB_MAIN_LOOP_ENTER_FUNCTION 注册一个链表,vlib_call_all_main_loop_enter_functions()函数遍历该链表。

调用vlib_main_loop()

Step.6 vlib/main.c的vlib_main_loop()函数

创建前面提到的相互协作的多线程,在while(1)循环中处理不同类型的图结点

·VLIB_NODE_TYPE_PRE_INPUT:类似DBG_CLI的结点

·VLIB_NODE_TYPE_INPUT:这些是主要结点,主要从网卡或者硬件加速器获取数据包

·进程等待信号,这个很重要,因为所有的客户端都要通过共享内存和VPP通信。客户端向共享内存发送一些API消息,并且向VPP发送信号(SIGUSR1)。

输入结点组织数据包,并且将他们发送到合适的中间结点。由dispatch_pending_node()进一步处理这些数据包。

 

 Pt. 2 新建插件plugin

本质是“.so的动态库制品

 参考:https://developer.aliyun.com/article/674304

1 VLIB_INIT_FUNCTION(xxx_init)      //插件运行环境,初始化

2 VLIB_PLUGIN_REGISTER              //插件描述, 初始化

3 VLIB_CLI_COMMAND                 //插件CLI命令,初始化 

 

2.1 编译流程

    autoreconf  -i -f     //生成 configure

    ./configure            //生成 makefile

    make                   //

    make clean         //

 Pt. 3 节点NODE

本质是“逻辑功能单元

3.1 全局结构体 

vlib_main_t:每个线程一份,记录着线程使用到的全局数据信息.
比如

/* Node graph main structure. */ vlib_node_main_t node_main; /* Command line interface. */ vlib_cli_main_t cli_main; /* Packet trace buffer. */ vlib_trace_main_t trace_main;

 3.2 Node相关结构体

vlib_node_t:                    结点的主结构,包括结点的处理功能函数,名称,结点类型等,主要保存一些相对静态信息,几乎不怎么修改的参数,状态信息保存在这里
vlib_node_main_t:           Node graph相关的全局信息,nodes数组、vlib_next_frame_t、vlib_pending_frame_t等数据
vlib_node_registration_t: 注册node结点时使用,保存结点业务逻辑的函数地址,结点类型,结点状态,结点名称等
vlib_node_runtime_t:       实际在调度node过程中使用的结构,主要记录在处理过程中的“频繁变动”的信息变动
vlib_frame_t:                    每个node都有一个对应的vlib_frame_t,用来保存供node使用的数据集合(标量、矢量),这是每个node最终处理数据的内存所在地。
vlib_next_frame_t:           主要是node内部逻辑使用,用于定位该node的下一结点所对应的frame地址
vlib_pending_frame_t:     当一个node处理完数据包,则填充该待处理帧管理表数据结,调度框架便能在下一次调度时找到需要接收该数据包的下一个node。

vlib_process_t:          VLIB_NODE_TYPE_PROCESS类型node专用结构,记录用于模拟task的基础结构:heap上的运行时栈,2种返回时寄存器备份,等

 

 

 3.3 vlib_node_type_t

 

VLIB_NODE_TYPE_INTERNAL  :对数据包真正处理的业务node。
VLIB_NODE_TYPE_INPUT        :收包逻辑node,比如:dpdk,pcap等。
VLIB_NODE_TYPE_PRE_INPUT :目前只有一个epoll node,对socket相关逻辑提供服务,主要使用在控制业务上。
VLIB_NODE_TYPE_PROCESS   :该类型node可以被挂起也可以被恢复,有独立的分配在heap上的运行时栈。类似于在一个线程中实现了多任务调度机制,jmp机制的协程。主要用来修改vpp node内部参数。

 

VLIB_NODE_TYPE_INTERNAL
  内部节点,最典型的节点, 仅在要处理的pending frame时运行;接收缓冲向量,执行操作。

       vpp大部分节点是这个角色,主要对数据流做内部处理,比如ip4-input-no-checksum/ip4-icmp-input等内部功能节点

VLIB_NODE_TYPE_INPUT
  输入节点,通常是设备输入节点。从零开始创建框架并分派到内部节点(internal), 比如dpdk-input/af-packet-input节点,
  input节点收包模式分为轮询和中断两种模式vlib_node_state_t.

VLIB_NODE_TYPE_PRE_INPUT
  在其他所有node之前运行。目前只有一个epoll node,对socket相关逻辑提供服务,主要使用在控制业务上。

VLIB_NODE_TYPE_PROCESS

       该类型的node可以被挂起也可以被恢复,有独立的分配在heap上的运行栈。类似与在一个线程中实现了多任务的调度机制,主要用来修改vpp node内部参数。
  线程节点,和线程一样,可以可以暂停、等待事件、恢复,不同于pthread_thread,他是基于setjump/longjump实现的 “协程”.
  等待一个事件:always_inline f64 vlib_process_wait_for_event_or_clock (vlib_main_t * vm, f64 dt)
  发送一个事件: always_inline void vlib_process_signal_event (vlib_main_t * vm, uword node_index, uword type_opaque, uword data)

 3.4  验证处理结果,决定下一处理流程

      vlib_validate_buffer_enqueue_x1                          // 决定下一走向。

 Pt. 4 feature

/* Hook up input features */

VNET_FEATURE_INIT (ip4_snat_in2out, static)    =

{

.arc_name = "ip4-unicast",

.node_name = "nat44-in2out", /* 必须存在nat44-in2out节点 */

.runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),

};

VNET_FEATURE_ARC_INIT (ip4_unicast, static)  =

{

.arc_name = "ip4-unicast",

.start_nodes = VNET_FEATURES ("ip4-input", "ip4-input-no-checksum"),/* 起始节点,必须存在node */

.last_in_arc = "ip4-lookup", /* 整个arc的最后一个feature必须是ip4-lookup,否则不能初始化成功 */

.arc_index_ptr = &ip4_main.lookup_main.ucast_feature_arc_index,/* 需要将arc索引写入到ucast_feature_arc_index中,供对应模块使用 */

};

 Pt. 4 CLI 命令

ref:  https://blog.csdn.net/qq_29044159/article/details/108136177

插件CLI初始化注册:

 

 

 插件CLI处理过程:

 

 

 

 

插件CLI


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM