VPP学习-VPP初始化流程

news2024/11/19 9:37:41

概念

        VPP作为一个开源的、高性能的用户态网络协议栈,以进程的形式运行于Linux或(类unix)系统下,即VPP实际是一个用户进程,VPP启动后可通过"ps -ef | grep vpp"命令查看。

VPP启动

        用户态进程启动都有一个main函数即程序入口函数,VPP也不例外,VPP main函数位于/src/vpp/net/main.c中,主要做了如下工作:

        1)加载并解析VPP配置文件startup.conf,获取诸如heapsize、plugin_path等配置参数值。

        2)利用配置参数值完成内存堆初始化

        3)vlib_main_init

        4)vpe_main_init

        5)vlib_unix_main

        针对上述过程,分别了解学习

配置文件解析

        VPP配置文件在启动时通过 如下形式加载:

        vpp -c /etc/vpp/startup.conf

         关于配置文件形式、内容及参数含义可参考此前博客:VPP配置文件介绍

        配置文件读取、解析获取配置参数信息。 

堆初始化

        配置文件解析后,完成相关初始化工作,具体如下:

        1)设置堆大小,堆页大小参数,堆大小默认为1G,在main函数入口时设定uword main_heap_size = (1ULL << 30),

        2)设置主线程CPU亲和性

        3)根据此前设置好的堆相关参数,分配并完成堆的初始化

                a)main_heap = clib_mem_init_with_page_size (main_heap_size,
                         main_heap_log2_page_sz)

                        i) clib_mem_init_internal

                                1) clib_mem_main_init初始化VPP巨页大小及numa节点信息

                                2)clib_mem_create_heap_internal (base, size, log2_page_sz,
                     1 /*is_locked */ , "main heap") 分配创建堆,

                                        a)获取有效的页大小信息

                                        b)使得分配内存的大小对于页大小

                                        c)clib_mem_vm_map_internal分配heap内存

                                        d)初始化并返回结构体变量指针clib_mem_heap_t *h.

                b)获取当前numa 节点索引

                c)设置巨页大小

                d)根据此前分配的主堆设置每一个numa节点的main_heap:  clib_mem_set_per_numa_heap (main_heap);

vlib_main_init

        1)vgm->init_functions_called = hash_create (0, /* value bytes */ 0);创建hash表用于记录已被调用的初始化函数;当vpp启动后,可以通过命令行查看有哪些初始化函数,如下所示:

        2)vm = clib_mem_alloc_aligned (sizeof (*vm), CLIB_CACHE_LINE_BYTES); 分配vlib_main_t 结构体变量,用于指示每个线程的mains。

        3)将vm加入一个向量(vector)vgm->vlib_mains

vpe_main_init

        vpe_main_init函数完成插件路径的设置,具体实现如下:

vpe_main_init (vlib_main_t * vm)
{
#if VPP_API_TEST_BUILTIN > 0
  void vat_plugin_hash_create (void);
#endif

  /* 设置CLI客户端命令行提示符 */
  if (CLIB_DEBUG > 0)
    vlib_unix_cli_set_prompt ("DBGvpp# ");
  else
    vlib_unix_cli_set_prompt ("vpp# ");

  /* Turn off network stack components which we don't want */
  vlib_mark_init_function_complete (vm, srp_init);

  /*
   * Create the binary api plugin hashes before loading plugins
   */
#if VPP_API_TEST_BUILTIN > 0
  vat_plugin_hash_create ();
#endif

  /* 设置插件路径 */
  if (!vlib_plugin_path)
    vpp_find_plugin_path ();
}

vlib_unix_main

        在vlib_unix_main函数中

        1)首先通过vlib_plugin_config函数从命令行加载和配置插件。

        2)调用vlib_plugin_early_init函数实现在main函数之前初始化插件。需要注意的是,vlib_plugin_early_init函数是通过调用vlib_load_new_plugins函数来加载插件目录,这个目录可以通过命令行指定,默认的插件路径是 /usr/lib/vpp_plugins;

        vlib_load_new_plugins函数将会调用load_one_plugin函数,load_one_plugin函数会通过dlopen函数加载插件目录下的所有插件(插件以动态库(.so)的形式存在的);

        dlopen每个插件后,VPP会获取函数vlib_plugin_registration的符号地址,每个插件都要求实现该函数,这个函数会传递非常重要的数据,包括插件的版本信息、early_init初始化函数指针、描述信息等

        3)vlib_unix_main函数调用vlib_call_all_config_functions来对命令行的选项进行解析,完成相应的配置。

        4)vlib_thread_stack_init函数初始化线程栈,并将os线程指定为索引0线程。vlib_unix_main最后调用clib_calljmp函数,clib_calljmp函数使用thread0作为回调函数,thread0也是初始化得到的线程;在thread0函数中,函数跳转到src/vlib/main.c中的vlib_main函数。具体实现如下:

static uword
thread0 (uword arg)
{
  vlib_main_t *vm = (vlib_main_t *) arg;
  vlib_global_main_t *vgm = vlib_get_global_main ();
  unformat_input_t input;
  int i;

  vlib_process_finish_switch_stack (vm);

  /* 初始化命令行参数 */
  unformat_init_command_line (&input, (char **) vgm->argv);
  /* 跳转到vlib_main函数处理 */
  i = vlib_main (vm, &input);
  unformat_free (&input);

  return i;
}

vlib_main

        在vlib_main函数中:

        1)通过vlib_register_all_static_nodes注册所有的静态节点

        2)通过vlib_node_main_init函数初始化节点图,

        3)通过vlib_call_all_config_functions配置子系统,

        4) 然后进入vlib_call_all_init_functions,完成所有函数的初始化。

        5)vlib_main函数最后调用vlib_main_loop函数,此函数是thread0的主循环。

        vlib_main具体实现如下:

int vlib_main (vlib_main_t * volatile vm, unformat_input_t * input)
{
  vlib_global_main_t *vgm = vlib_get_global_main ();
  clib_error_t *volatile error;
  vlib_node_main_t *nm = &vm->node_main;

  vm->queue_signal_callback = placeholder_queue_signal_callback;

  /* Reconfigure event log which is enabled very early */
  if (vgm->configured_elog_ring_size &&
      vgm->configured_elog_ring_size != vgm->elog_main.event_ring_size)
    elog_resize (&vgm->elog_main, vgm->configured_elog_ring_size);
  vl_api_set_elog_main (vlib_get_elog_main ());
  (void) vl_api_set_elog_trace_api_messages (1);

  /* Default name. */
  if (!vgm->name)
    vgm->name = "VLIB";

  if ((error = vlib_physmem_init (vm)))
    {
      clib_error_report (error);
      goto done;
    }

  if ((error = vlib_log_init (vm)))
    {
      clib_error_report (error);
      goto done;
    }

  if ((error = vlib_stats_init (vm)))
    {
      clib_error_report (error);
      goto done;
    }

  if ((error = vlib_buffer_main_init (vm)))
    {
      clib_error_report (error);
      goto done;
    }

  if ((error = vlib_thread_init (vm)))
    {
      clib_error_report (error);
      goto done;
    }

  /* 注册所有节点的节点函数变量 */
  vlib_register_all_node_march_variants (vm);

  /* 注册所有静态节点 */
  vlib_register_all_static_nodes (vm);

  /* 设置随机生成器的种子       */
  if (!unformat (input, "seed %wd", &vm->random_seed))
    vm->random_seed = clib_cpu_time_now ();
  clib_random_buffer_init (&vm->random_buffer, vm->random_seed);

  /* Initialize node graph.初始化节点图 */
  if ((error = vlib_node_main_init (vm)))
    {
      /* Arrange for graph hook up error to not be fatal when debugging. */
      if (CLIB_DEBUG > 0)
	clib_error_report (error);
      else
	goto done;
    }

  /* Direct call / weak reference, for vlib standalone use-cases */
  if ((error = vpe_api_init (vm)))
    {
      clib_error_report (error);
      goto done;
    }

  if ((error = vlibmemory_init (vm)))
    {
      clib_error_report (error);
      goto done;
    }

  if ((error = map_api_segment_init (vm)))
    {
      clib_error_report (error);
      goto done;
    }

  /* See unix/main.c; most likely already set up */
  if (vgm->init_functions_called == 0)
    vgm->init_functions_called = hash_create (0, /* value bytes */ 0);
  if ((error = vlib_call_all_init_functions (vm)))
    goto done;

  nm->timing_wheel = clib_mem_alloc_aligned (sizeof (TWT (tw_timer_wheel)),
					     CLIB_CACHE_LINE_BYTES);

  vec_validate (nm->data_from_advancing_timing_wheel, 10);
  vec_set_len (nm->data_from_advancing_timing_wheel, 0);

  /* Create the process timing wheel */
  TW (tw_timer_wheel_init)
  ((TWT (tw_timer_wheel) *) nm->timing_wheel,
   process_expired_timer_cb /* callback */, 10e-6 /* timer period 10us */,
   ~0 /* max expirations per call */);

  vec_validate (vm->pending_rpc_requests, 0);
  vec_set_len (vm->pending_rpc_requests, 0);
  vec_validate (vm->processing_rpc_requests, 0);
  vec_set_len (vm->processing_rpc_requests, 0);

  /* Default params for the buffer allocator fault injector, if configured */
  if (VLIB_BUFFER_ALLOC_FAULT_INJECTOR > 0)
    {
      vm->buffer_alloc_success_seed = 0xdeaddabe;
      vm->buffer_alloc_success_rate = 0.80;
    }

  if ((error = vlib_call_all_config_functions (vm, input, 0 /* is_early */ )))
    goto done;

  /*
   * Use exponential smoothing, with a half-life of 1 second
   * reported_rate(t) = reported_rate(t-1) * K + rate(t)*(1-K)
   *
   * Sample every 20ms, aka 50 samples per second
   * K = exp (-1.0/20.0);
   * K = 0.95
   */
  vm->damping_constant = exp (-1.0 / 20.0);

  /* Sort per-thread init functions before we start threads */
  vlib_sort_init_exit_functions (&vgm->worker_init_function_registrations);

  /* Call all main loop enter functions. */
  {
    clib_error_t *sub_error;
    sub_error = vlib_call_all_main_loop_enter_functions (vm);
    if (sub_error)
      clib_error_report (sub_error);
  }

  switch (clib_setjmp (&vm->main_loop_exit, VLIB_MAIN_LOOP_EXIT_NONE))
    {
    case VLIB_MAIN_LOOP_EXIT_NONE:
      vm->main_loop_exit_set = 1;
      break;

    case VLIB_MAIN_LOOP_EXIT_CLI:
      goto done;

    default:
      error = vm->main_loop_error;
      goto done;
    }

  /* 进入主循环 */
  vlib_main_loop (vm);

done:
  /* Stop worker threads, barrier will not be released */
  vlib_worker_thread_barrier_sync (vm);

  /* Call all exit functions. */
  {
    clib_error_t *sub_error;
    sub_error = vlib_call_all_main_loop_exit_functions (vm);
    if (sub_error)
      clib_error_report (sub_error);
  }

  if (error)
    clib_error_report (error);

  return vm->main_loop_exit_status;
}
  vlib_main_loop

        在src/vlib/main.c函数中定义了两个循环函数,vlib_main_loop与vlib_worker_loop。vlib_main_loop对应的就是thread0线程,即控制平面;而vlib_worker_loop对应的是worker线程,即数据平面。

        两个函数都调用vlib_main_or_worker_loop函数进入循环,这里仅关注vlib_main_loop的循环,如下所示:

static void
vlib_main_loop (vlib_main_t * vm)
{
  /* is_main 为1 表示main_loop */
  vlib_main_or_worker_loop (vm, /* is_main */ 1);
}

        主要涉及以下内容:

        1) 启动所有线程:dispatch_process (vm, nm->processes[i], /* frame */ 0, cpu_time_now);创建相互协作的多线程,VPP主要有三种类型的线程:

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

        EAL线程:处理包的工作。

        Processes:这些都是定期执行、相互协作的多线程,VPP主线程的超时到期之后会执行这些。

        2)在while循环中处理不同类型的节点;

while(1) { 
    //PRE_INPUT是在调用INPUT之前需要处理的node,例如清除网卡发送队列等
    dispatch_node (...,  VLIB_NODE_TYPE_PRE_INPUT, ....); 
     //处理所有INPUT类型的NODE,例如DPDK的node
    dispatch_node (...,  VLIB_NODE_TYPE_INPUT, ....);
    //处理所有INTERNAL类型的NODE,所有业务相关的都是该类型的NODE,例如ACL、ROUTE等的node
    dispatch_pending_node (VLIB_NODE_TYPE_INTERNAL);
    //处理所有PROCESS类型的NODE,对应产生事件的node,恢复到该node的睡眠点执行。例如定时器等
    //仅在主线程中处理该类型的node
    dispatch_suspended_process (VLIB_NODE_TYPE_PROCESS);
}

参考链接

阐述一下VPP初始化流程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1434951.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第7节、双电机直线运动【51单片机+L298N步进电机系列教程】

↑↑↑点击上方【目录】&#xff0c;查看本系列全部文章 摘要&#xff1a;前面章节主要介绍单个电机控制&#xff0c;本节内容介绍两个电机完成Bresenham直线运动 一、Bresenham直线算法介绍 Bresenham直线算法由Jack Elton Bresenham于1962年在IBM开发&#xff0c;最初用于计…

Akamai 如何揪出微软 RPC 服务中的漏洞

近日&#xff0c;Akamai研究人员在微软Windows RPC服务中发现了两个重要漏洞&#xff1a;严重程度分值为4.3的CVE-2022-38034&#xff0c;以及分值为8.8的CVE-2022-38045。这些漏洞可以利用设计上的瑕疵&#xff0c;通过缓存机制绕过MS-RPC安全回调。我们已经确认&#xff0c;所…

Transformer实战-系列教程7:SwinTransformer 算法原理 1

&#x1f6a9;&#x1f6a9;&#x1f6a9;Transformer实战-系列教程总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在Pycharm中进行 本篇文章配套的代码资源已经上传 1、SwinTransformer SwinTransformer 可以看作为一个backbone用来做分类、检测、分割都是非常好…

紫光展锐M6780丨一语即达,“声”临其境

在前面四期&#xff0c;紫光展锐针对M6780的显示技术进行了系列揭秘。虽名为“智能显示芯片”&#xff0c;但M6780的魅力远不止于超高清智能显示&#xff0c;更有智能语音交互功能&#xff0c;助力打造数字世界的交互新体验。 智能语音技术是一种基于人工智能和语音识别技术的创…

【CSS】margin塌陷和margin合并及其解决方案

【CSS】margin塌陷和margin合并及其解决方案 一、解决margin塌陷的问题二、避免外边距margin重叠&#xff08;margin合并&#xff09; 一、解决margin塌陷的问题 问题&#xff1a;当父元素包裹着一个子元素的时候&#xff0c;当给子元素设置margin-top:100px&#xff0c;此时不…

(已解决)vueQQ邮箱注册发送验证码前端设计,如何发送验证码设计倒计时

我们之前已经通过前端测试成功完成qq邮箱动态验证码发送&#xff08;未使用redis&#xff0c;我准备自己了解完后&#xff0c;后期有时间补上&#xff09; 衔接文章&#xff1a; 1&#xff1a; spingboot 后端发送QQ邮箱验证码 2&#xff1a; 这段代码建设图形化界面 <di…

分享springboot框架的一个开源的本地开发部署教程(若依开源项目开发部署过程分享持续更新二开宝藏项目MySQL数据库版)

1首先介绍下若依项目&#xff1a; 若依是一个基于Spring Boot和Spring Cloud技术栈开发的多租户权限管理系统。该开源项目提供了一套完整的权限管理解决方案&#xff0c;包括用户管理、角色管理、菜单管理、部门管理、岗位管理等功能。 若依项目采用前后端分离的架构&#xf…

Nacos1.X源码解读(待完善)

下载源码 1. 克隆git地址到本地 # 下载nacos源码 git clone https://github.com/alibaba/nacos.git 2. 切换分支到1.4.7, maven编译(3.5.1) 3. 找到启动类com.alibaba.nacos.Nacos 4. 启动VM参数设置单机模式, RUN 启动类 -Dnacos.standalonetrue 5. 启动本地服务注册到本…

Spark SQL调优实战

1、新添参数说明 // Driver和Executor内存和CPU资源相关配置 --是否开启executor动态分配&#xff0c;开启时spark.executor.instances不生效 spark.dynamicAllocation.enabledfalse --配置Driver内存 spark.dirver.memory5g --driver最大结果大小&#xff0c;设置为0代…

踩坑了,MySQL数据库生成大量奇怪的大文件

作者&#xff1a;田逸&#xff08;formyz&#xff09; 一大早就收到某个数据库服务器磁盘满的报警信息&#xff0c;其中数据盘使用率超过90%&#xff0c;如下图所示。 这是一台刚上线不久的MySQL从库服务器&#xff0c;数据盘的总容量是300G。先登录系统&#xff0c;查看主从同…

全链游戏的未来趋势与Bridge Champ的创新之路

为了充分探索全链游戏的特点和趋势&#xff0c;以及Bridge Champ如何作为一个创新案例融入这一发展脉络&#xff0c;我们需要深入了解这两者之间的互动和相互影响。全链游戏&#xff0c;或完全基于区块链的游戏&#xff0c;代表了游戏行业的一个重要转型&#xff0c;它们利用区…

【C++】I/O多路转接详解(二)

在上一篇文章【C】I/O多路转接详解&#xff08;一&#xff09; 在出现EPOLL之后&#xff0c;随之而来的是两种事件处理模式的应运而生&#xff1a;Reator 和 Proactor,同步IO模型常用于Reactor模式&#xff0c;异步IO常用于Proactor. 目录 1. 服务器编程框架简介2. IO处理1. R…

【爬虫作业】python爬虫作业——爬取汽车之家

爬取汽车之家期末作业&#xff1a; 代码如下所示&#xff1a; import random import timeimport requests #发送网络请求 import parsel import csv # 1.发送网络请求 headers {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like G…

ELFK日志采 - QuickStart

文章目录 架构选型ELKEFLK ElasticsearchES集群搭建常用命令 Filebeat功能介绍安装步骤Filebeat配置详解filebeat常用命令 Logstash功能介绍安装步骤Input插件Filter插件Grok Filter 插件Mutate Filter 插件常见的插件配置选项&#xff1a;Mutate Filter配置案例&#xff1a; O…

ffmpeg命令生成器

FFmpeg 快速入门&#xff1a;命令行详解、工具、教程、电子书 – 码中人的博客FFmpeg 是一个强大的命令行工具&#xff0c;可以用来处理音频、视频、字幕等多媒体文件。本文介绍了 FFmpeg 的基本用法、一些常用的命令行参数&#xff0c;以及常用的可视化工具。https://blog.mzh…

什么是S参数

S参数是网络参数&#xff0c;定义了反射波和入射波之间的关系&#xff0c;给定频率的S参数矩阵指定端口反射波b的矢量相对于端口入射波a的矢量&#xff0c;如下所示&#xff1a; bS∙a 在此基础上&#xff0c;如下图所示&#xff0c;为一个常见的双端口网络拓扑图&#xff1a;…

GPT-1, GPT-2, GPT-3, GPT-3.5, GPT-4论文内容解读

目录 1 ChatGPT概述1.1 what is chatGPT1.2 How does ChatGPT work1.3 The applications of ChatGPT1.3 The limitations of ChatGPT 2 算法原理2.1 GPT-12.1.1 Unsupervised pre-training2.1.2 Supervised fine-tuning2.1.3 语料2.1.4 分析 2.2 GPT-22.3 GPT-32.4 InstructGPT…

【计算机网络】计算机网络复习资料(期末)

复习要点 一、填空题 1.计算机网络的两个重要基本特点 连通性、共享 2&#xff0e;计算机中的端口号类型 两类端口号 { 服务器端 { 熟知端口号&#xff08;系统端口号&#xff09;数值为0~1023 登记端口号&#xff0c;1024~49151 } 客户端使用的端口号&#xff1a;短…

uniapp vue3怎么调用uni-popup组件的this.$refs.message.open() ?

vue2代码 <!-- 提示信息弹窗 --><uni-popup ref"message" type"message"><uni-popup-message :type"msgType" :message"messageText" :duration"2000"></uni-popup-message></uni-popup>typ…

架构学习(四):scrapy下载中间件实现动态切换User-Agent

scrapy下载中间件实现动态与固定UserAgent 前言关卡&#xff1a;实现动态切换User-Agentscrapy设置User-Agent方式梳理User-Agent生效梳理为何选择在下载中间件中实现自定义User-Agent下载中间件 结束 前言 请求头User-Agent是比较常规的反爬手段&#xff0c;不同站点对其检测…