二、VPP启动流程分析

本文介绍了VPP的启动流程,包括VPP与DPDK的交互,DPDK将数据包收到rte_mbuf后转化为vlib_buf供后续node处理。还阐述了VPP初始化的四件事,如加载静态库、注册处理节点等,并详细分析了相关函数,展示了注册node后处理函数生效的过程。

今天我们介绍一下VPP的启动流程。如果把vpp看成一个应用程序,那么他是怎么工作的呢:
首先,vpp有许多插件so,每个插件下面有许多node,比如我们如果要新加一个功能为ip地址过滤,那么我们其实就是加一个node,当数据经过node时,调用我们提前注册的function函数就实现了我们的功能。接下来就理一下vpp从启动后到node生效的过程是怎样的。
开始之前先说下DPDK是怎么和vpp交互的。从vpp看来,dpdk就是一个插件,只不过这个插件运行得比较靠前,当数据包到来之后,dpdk将其收到rte_mbuf中,后面再将rte_mbuf转化为vlib_buf,后面的node就是从vlib_buf中取出数据再来处理,简单讲下rte_mbuf和vlib_buf的关系,以及他们是如何转化的:

在这里插入图片描述
#define rte_mbuf_from_vlib_buffer(x) (((struct rte_mbuf *)x) - 1)
#define vlib_buffer_from_rte_mbuf(x) ((vlib_buffer_t *)(x+1))
这两个宏定义用于在vlib buffer和DPDK的rte_mbuf之间进行转换。rte_mbuf 是DPDK中用于表示数据包的结构体,而 vlib_buffer_t 是VPP中用于表示数据包的结构体。以下是对这两个宏的解释:

rte_mbuf_from_vlib_buffer(x): 这个宏将一个 vlib_buffer_t 转换成对应的 rte_mbuf。vlib_buffer_t 中的数据的指针指向 rte_mbuf 的后一个字节,所以通过将 x 强制转换为 struct rte_mbuf *,然后减去1,就可以得到对应的 rte_mbuf。

vlib_buffer_from_rte_mbuf(x): 这个宏将一个 rte_mbuf 转换成对应的 vlib_buffer_t。由于上面的宏中的指针偏移关系,将 x 强制转换为 vlib_buffer_t * 之后,可以得到指向 vlib_buffer_t 数据部分的指针

VPP 初始化

初始化里面主要做了四件事
1、load so //加载静态库
2、register node //注册处理节点
3、function init (cmd init,feture init) //注册处理函数

先上个伪代码简单看下流程

int main(){
   
   

	//VPP 程序的入口函数,它初始化全局状态、配置、插件以及其他运行时环境,并最终进入主循环以运行网络处理和其他功能
	vlib_unix_main();
	
		//执行了插件的早期初始化,设置了插件路径、日志等,并准备加载新的插件
		vlib_plugin_early_init();
		
			//加载新插件,并进行相应的管理和处理,确保只加载有效的插件,并进行了排序、覆盖等操作
			vlib_load_new_plugins();
			
				//加载插件并进行一系列的检查,确保插件可以正确加载和初始化
				load_one_plugin();

		//通过协程跳转到thread0执行
		clib_calljmp();
		
			//线程函数,作为独立线程的入口点
			thread0();
			
				//各种初始化操作,调用初始化函数、配置函数、主循环函数、以及一系列的清理操作
				vlib_main();
				
					//调用所有初始化函数
					vlib_call_all_init_functions();
					
						vlib_call_init_exit_functions
					
					//遍历所有的节点注册(node_registrations 链表),并将这些注册的节点全部注册到 VLIB 中
					vlib_register_all_static_nodes();		
						
					//
					 vlib_main_loop (vm);
					 
						//VLIB 主循环或工作线程循环的核心部分。它处理节点的调度和执行,以及一些循环计数和时间间隔的计算
						vlib_main_or_worker_loop (vm, /* is_main */ 1);
						
							//负责管理进程的调度、执行和暂停等。根据进程的状态和标志,它会判断是否继续执行进程,
							//启动定时器等。执行完毕后,会更新性能计数器、事件日志以及进程的统计信息
							dispatch_process();
							
								//责将进程切换到新的堆栈并执行其启动函数
								vlib_process_startup
								
									//进程启动的具体逻辑,包括在进程的堆栈上调用进程函数并处理返回值
									vlib_process_bootstrap
									
										// 这里就调用到了我们注册一个node之后,真正的处理函数
										n = node->function (vm, node, f);
							
}


接下来对上面伪代码中的函数进行详细分析下:
从main函数开始,main函数中其实都是配置和初始化VPP环境的相关东西,大概就是下面这样:

int main(){
   
   
	//创建主堆,使用了之前设置的 main_heap_size 和 main_heap_log2_page_sz
	main_heap = clib_mem_init_with_page_size(main_heap_size, main_heap_log2_page_sz);
	//初始化VLIB库,初始化VPE(VPP Environment)库
	//将一个VLIB主要实例传递给了 vpe_main_init() 函数,以便VPE库能够与VLIB库进行集成
	vlib_main_init();
	vpe_main_init(vlib_get_first_main());
	//进入VLIB的主循环并开始执行网络处理程序。这是整个程序的核心部分,处理数据包、事件和各种网络功能
	return vlib_unix_main(argc, argv);
}

vlib_unix_main函数解析:

int
vlib_unix_main (int argc, char *argv[])
{
   
   
  
	//初始化时间库
	clib_time_init (&vm->clib_time);

	//初始化并启用事件日志,用于记录系统中的事件
	elog_init (vlib_get_elog_main (), vgm->configured_elog_ring_size);
	elog_enable_disable (vlib_get_elog_main (), 1);

	//初始化插件的配置,通过调用插件的配置函数来加载和处理插件特定的配置信息
	if ((e = vlib_plugin_config (vm, &input)))
	{
   
   
	  clib_error_report (e);
	  return 1;
	}
	unformat_free (&input);

	//这里是各种so插件初始化的地方
	i = vlib_plugin_early_init (vm);
	if (i)
	return i;

	//调用注册的配置函数,这些函数在插件中注册,用于配置程序的各种功能
	e = vlib_call_all_config_functions (vm, &input, 1 /* early */ );
	if (e != 0)
	{
   
   
	  clib_error_report (e);
	  return 1;
	}
	unformat_free (&input);

	//加载 ELF 格式文件的符号,用于处理信号处理程序和内存分配追踪
	clib_elf_main_init (vgm->exec_path);

	vec_validate (vlib_thread_stacks, 0);
	vlib_thread_stack_init (0);

	__os_thread_index = 0;
	vm->thread_index = 0;

	//启动主循环,将堆栈切换到主线程的堆栈,然后使用 clib_calljmp 调用主循环
	vlib_process_start_switch_stack (vm, 0);
	i = clib_calljmp (thread0, (uword) vm,
			(void *) (vlib_thread_stacks[0] +
				  VLIB_THREAD_STACK_SIZE));
	return i;
}

这里我们再看看插件是如何被加载的,通过vlib_plugin_early_init


int
vlib_plugin_early_init (vlib_main_t * vm)
{
   
   
	plugin_main_t *pm = &vlib_plugin_main;

	pm->logger =
	vlib_log_register_class_rate_limit ("plugin", "load",
					0x7FFFFFFF /* aka no rate limit */ );

	//如果插件路径未设置,则使用默认的插件路径 vlib_plugin_path		
	if (pm->plugin_path == 0)
	pm->plugin_path = format (0, "%s", vlib_plugin_path);

	//如果有额外的插件路径附加,将其添加到插件路径中
	if (pm->plugin_path_add)
	pm->plugin_path = format (pm->plugin_path, ":%s", pm->plugin_path_add);

	//在插件路径的末尾添加一个 null 字节,以便可以在字符串中找到插件路径的终止
	pm->plugin_path = format (pm->plugin_path, "%c", 0);

	PLUGIN_LOG_DBG ("plugin path %s", pm->plugin_path);

	//创建两个哈希表,用于存储插件的信息和覆盖信息
	pm->plugin_by_name_hash = hash_create_string (0, sizeof (uword));
	pm->plugin_overrides_by_name_hash = hash_create_string (0, sizeof (uword));
	//设置插件主结构体的 vlib_main 字段,以指向 VLIB 主结构体
	pm->vlib_main = vm;

	//加载新的插件
	return vlib_load_new_plugins (pm, 1 /* from_early_init */ );
}

插件的加载就是vlib_load_new_plugins 函数,它做了哪些事呢:加载新插件,并进行相应的管理和处理,确保只加载有效的插件,并进行了排序、覆盖等操作。我们如果自己新定制了plugin插件,也是在这里处理


int
vlib_load_new_plugins (plugin_main_t * pm, int from_early_init)
{
   
   
	DIR *dp;
	struct dirent *entry;
	struct stat statb;
	uword *p;
	plugin_info_t *pi;
	u8 **plugin_path;
	uword *not_loaded_indices = 0;
	int i;

	//根据插件路径字符串生成一个字符串数组,每个元素表示一个路径
	plugin_path = split_plugin_path (pm);

	for (i = 0; i < vec_len (plugin_path); i++)
	{
   
   
	  dp = opendir ((char *) plugin_path[i]);

	  if (dp == 0)
	continue;

	//遍历插件路径数组,打开每个路径对应的目录,并遍历目录中的每个文件项
	while ((entry = readdir (dp)))
	{
   
   
	  u8 *plugin_name;
	  u8 *filename;

	  //如果设置了插件名过滤,将会检查文件名是否满足过滤条件。如果不满足,则跳过当前文件项
	  if (pm->plugin_name_filter)
		{
   
   
		  int j;
		  for (j = 0; j < vec_len (pm->plugin_name_filter); j++)
		if (entry->d_name[j] != pm->plugin_name_filter[j])
		  goto next;
		}

	  //检查文件的扩展名是否是 .so,并且通过 stat 函数检查文件是否可读
	  filename = format (0, "%s/%s%c", plugin_path[i], entry->d_name, 0);
	  char *ext = strrchr ((const char *) filename, '.');
	  /* unreadable */
	  if (!ext || (strcmp (ext, ".so") != 0) ||
		  stat ((char *) filename, &statb) < 0)
		{
   
   
		ignore:
		  vec_free (filename);
		  continue;
		}

	  /* 检查文件是否是一个常规文件,即普通文件,而不是目录或其他类型的文件 */
	  if (!S_ISREG (statb.st_mode))
		goto ignore;

		//如果文件是有效的插件文件,将其信息添加到插件信息数组中,并在哈希表中建立插件名称到插件信息的映射
	  plugin_name = format (0, "%s%c", entry->d_name, 0);
	  p = hash_get_mem (pm->plugin_by_name_hash, plugin_name);
	  if (p == 0)
		{
   
   
		  /* No, add it to the plugin vector */
		  vec_add2 (pm->plugin_info, pi, 1);
		  pi->name = plugin_name;
		  pi->filename = filename;
		  pi->file_info = statb;
		  pi->handle = 0;
		  hash_set_mem (pm->plugin_by_name_hash, plugin_name,
				pi - pm->plugin_info);
		}
	next:
	  ;
	}
	  closedir (dp);
	  vec_free (plugin_path[i]);
	}
	vec_free (plugin_path);


	/*
	根据插件名称对插件信息数组进行排序,确保插件按照名称的字典顺序排列
	*/
	vec_sort_with_function (pm->plugin_info, plugin_name_sort_cmp);

	/*
	遍历插件信息数组,并尝试加载每个插件,如果加载失败,则记录下来
	*/
	for (i = 0; i < vec_len (pm->plugin_info); i++)
	{
   
   
	  pi = vec_elt_at_index (pm->plugin_info, i);

	  if (load_one_plugin (pm, pi, from_early_init))
	{
   
   
	  /* Make a note of any which fail to load */
	  vec_add1 (not_loaded_indices, i);
	}
	}

	/*
	处理未能成功加载的插件,以及通过覆盖列表进行覆盖的插件
	*/
	for (i = 0; i < vec_len (pm->plugin_info); i++)
	{
   
   
	  uword *p;

	  pi = vec_elt_at_index (pm->plugin_info, i);

	  p = hash_get_mem (pm->plugin_overrides_by_name_hash, pi->name);

	  /* Plugin overridden? */
	  if (p)
	{
   
   
	  PLUGIN_LOG_NOTICE ("Plugin '%s' overridden by '%s'", pi->name,
				 p[0])
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

写一封情书

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值