VPP初始化
VLIB_INIT_FUNCTION
用来定义构造函数,注册函数到vlib_main_t->init_function_registrations
,这个链表在main()
函数之前创建。vlib_main()-> vlib_call_all_init_functions()
注册的函数在这里被调用初始化,最后执行函数vlib_main_loop()
。
像这样由宏定义和构造函数创建的全局链表的方式还有如下几个:
VLIB_API_INIT_FUNCTION
VLIB_CLI_COMMAND
VLIB_CONFIG_FUNCTION
VLIB_EARLY_CONFIG_FUNCTION
VLIB_MAIN_LOOP_ENTER_FUNCTION
VLIB_MAIN_LOOP_EXIT_FUNCTION
VLIB_REGISTER_NODE
main函数
vpp/vnet/main.c
的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()
将以上数据传递给插件。
最后调用vlib_unix_main()
。
vlib_unix_main函数
vlib_unix_main
函数位于vlib/unix/main.c
中。vlib_plugin_early_init()
函数会通过dlopen加载插件目录下的所有插件,这个目录可以通过命令行指定。默认的插件路径是 /usr/lib/vpp_plugins
。
dlopen每个插件后,VPP会获取函数vlib_plugin_register
的符号地址,所以每个插件都要求实现该函数,之前说过这个函数会传递非常重要的数据。vlib_call_all_config_functions()
函数解析所有的命令行选项,并且针对前期需求配置。
为以下线程创建线程栈,主要有三种类型的线程需要实现:
- 普通线程:比如统计采集。
- EAL线程:处理包的工作。
- Processes:这些都是定期执行、相互协作的多线程。比如DHCP租期续订的线程等。VPP主线程的超时到期之后会执行这些。
最后,该函数跳转到thread0()
函数。
thread0函数
thread0函数位于vlib/unix/main.c
中,主要是调用vlib/main.c
的vlib_main()
函数。
vlib_main函数
vlib_main函数位于vlib/main.c
中。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()
。
vlib_main_loop函数
vlib_main_loop函数位于vlib/main.c
中。
创建前面提到的相互协作的多线程,在while(1)
循环中处理不同类型的图结点。
VLIB_NODE_TYPE_PRE_INPUT
:类似DBG_CLI的结点。VLIB_NODE_TYPE_INPUT
:这些是主要结点,主要从网卡或者硬件加速器获取数据包。- 进程等待信号,这个很重要,因为所有的客户端都要通过共享内存和VPP通信。客户端向共享内存发送一些API消息,并且向VPP发送信号(SIGUSR1)。
输入结点组织数据包,并且将他们发送到合适的中间结点。由dispatch_pending_node()
进一步处理这些数据包。
VPP API
启动时,VPP的memclnt_process
线程会一直循环接收客户端发送的所有API消息。
当一个客户端启动的时候,他的自旋线程会一直等待接收VPP的异步响应。Instantiation of this thread is done as a side-effect of connecting to vpp in connect_to_vpe()
function。
客户端将API消息放入单向的共享内存队列,并且发SIGUSR1给VPP,如果VPP的输入队列由空变为非空。 The main thread that sent the API will call W; that waits until the side-effect thread either sets vam->reply_ready or will timeout after 1 second.
VPP的memclnt_process函数调用适当的处理程序并且通过单向共享内存队列回复客户端。再次,如果队列从空转向非空,vpp的发信号通知客户端的RX线程。客户端的RX线程调用适当的处理,设置vam->reply_ready。