VPP /软件架构
Software Architecture
fd.io vpp
实现是第三代矢量数据包处理实现。请注意,Apache-2许可证
专门授予非专有的专利许可证。
为了提高性能,vpp数据平面由转发节点的有向图组成,该转发图每次调用处理多个数据包。这种模式可实现多种微处理器优化:流水线和预取以覆盖相关的读取延迟,固有的I缓存阶段行为,矢量指令。除了硬件输入和硬件输出节点之外,整个转发图都是可移植的代码。
根据当前的情况,我们经常启动多个工作线程,这些工作线程使用相同的转发图副本处理来自多个队列的进入哈希数据包
VPP软件分层如下图:
vpp数据平面包括四个不同的层:
VPP基础结构层
,其中包含核心库源代码。该层执行存储功能,与向量和环配合使用,在哈希表中执行键查找,并与用于调度图形节点的计时器一起使用。向量处理库
。vlib层还处理各种应用程序管理功能:缓冲区,内存和图形节点管理,维护和导出计数器,线程管理,数据包跟踪。Vlib实现了调试CLI(命令行界面)。VPP的网络接口
(第2层,第3层和第4层)配合使用,执行会话和流量管理,并与设备和数据控制平面配合使用。与VPP基础结构层关联的文件位于./src/vppinfra
文件夹中。
VPPinfra
是基本c库服务的集合,足以建立直接在裸机上运行的独立程序。它还提供高性能的动态数组,哈希,位图,高精度的实时时钟支持,细粒度的事件记录和数据结构序列化。
关于vppinfra的一个合理的评论/合理的警告:您不能总是仅通过名称来从普通函数中的内联函数中告诉宏。宏通常用于避免函数调用,并引起(有意的)副作用。
详细介绍请参见:VPPINFRA , wiki VLIB
Vppinfra向量是用户定义的“标头”随处可见的动态调整大小的数组。许多vpppinfra数据结构(例如哈希,堆,池)是具有各种不同标头的向量。
The memory layout looks like this:
User header (optional, uword aligned)
Alignment padding (if needed)
Vector length in elements
User's pointer -> Vector element 0
Vector element 1
...
Vector element N-1
如上所示,向量API处理指向向量第0个元素的指针。空指针是长度为零的有效向量。
为避免破坏内存分配器,通常在保留内存分配的同时将向量的长度重置为零。通过vec_reset_length(v)
宏将向量长度字段设置为零。[使用宏!NULL指针很聪明。]
通常,用户头不存在。用户标头允许将其他数据结构构建在vppinfra向量之上。用户可以通过vec * _aligned宏指定数据元素的对齐方式。
向量元素可以是任何C类型,例如(int,double,struct bar)
。对于在矢量之上构建的数据类型(例如,堆,池等)也是如此。许多宏具有支持向量数据对齐的_a
变体和支持非零长度向量头的_h
变体。_ha
变体都支持。
标头和/或与对齐相关的宏变体用法不一致会导致延迟,混乱的故障。
标准编程错误:存储指向向量ith元素的指针,然后展开向量。向量扩展了3/2,因此此类代码似乎可以工作一段时间。正确的代码几乎总是记住向量索引,向量索引在重新分配中是不变的。
在典型的应用程序映像中,一个提供了一组全局函数,这些函数旨在从gdb调用。这里有一些例子:
Vppinfra位图是动态的,是使用vppinfra矢量API构建的。非常适合各种工作。
Vppinfra池结合了矢量和位图,可以快速分配和释放具有独立生存期的固定大小的数据结构。池非常适合分配每个会话的结构。
Vppinfra提供了几种哈希样式。涉及数据包分类/会话查找的数据平面问题经常使用... / src / vppinfra / bihash_template
。[ch]有界索引可扩展哈希。这些模板被实例化多次,以有效地服务于不同的固定键大小。
Biashhes是线程安全的。不需要读锁定。简单的自旋锁可确保一次仅一个线程写入一个条目。
... / src / vppinfra / hash
。[ch]中的原始vppinfra哈希实现易于使用,并且经常用于需要精确字符串匹配的控制平面代码中。
无论哪种情况,几乎总是在哈希表中查找键,以获得相关向量或池中的索引。这些API非常简单,但是在使用不受管理的任意大小的密钥变体时,一定要小心。Hash_set_mem(哈希表,key_pointer,值)存储key_pointer。将向量元素的地址作为第二个参数传递给hash_set_mem通常是一个严重的错误。最好在文本段中存储常量字符串地址。
Vppinfra格式大致等效于printf。
格式具有一些值得一提的属性。Format的第一个参数是一个(u8 *)向量,在该向量后附加了当前format操作的结果。链接调用非常简单:
u8 * result;
result = format (0, "junk = %d, ", junk);
result = format (result, "more junk = %d\n", more_junk);
如前所述,NULL指针是完全正确的0长度向量。格式返回(u8 *)向量,而不是C字符串。如果要打印(u8 *)矢量,请使用“%v”格式字符串。如果您需要一个(u8 *)向量,它也是一个适当的C字符串,则可以使用以下两种方案之一:
vec_add1 (result, 0)
or
result = format (result, "%c" , 0);
请记住,如果合适,请对vec_free()结果进行处理。注意不要通过格式化未初始化的u8 *。
格式通过“%U”格式规范实现了一种特别方便的用户格式方案。例如:
u8 * format_junk (u8 * s, va_list *va)
{
junk = va_arg (va, u32);
s = format (s, "%s", junk);
return s;
}
result = format (0, "junk = %U, format_junk, "This is some junk");
如果需要,format_junk()可以调用其他用户格式的函数。程序员负责参数类型检查。如果va_arg(va,
宏与调用者的现实想法不符,通常会导致用户格式函数崩溃。
Vppinfra unformat与scanf隐约相关,但更为笼统。
典型的用例涉及从C字符串或(u8 *)向量初始化unformat_input_t
,然后通过unformat()
进行解析,如下所示:
unformat_input_t input;
unformat_init_string (&input, "" );
/* or */
unformat_init_vector (&input, <u8-vector>);
然后循环解析单个元素:
while (unformat_check_input (&input) != UNFORMAT_END_OF_INPUT)
{
if (unformat (&input, "value1 %d", &value1))
;/* unformat sets value1 */
else if (unformat (&input, "value2 %d", &value2)
;/* unformat sets value2 */
else
return clib_error_return (0, "unknown input '%U'", format_unformat_error,
input);
}
与格式化一样,取消格式化通过“%U
”用户取消格式化功能方案实现了用户取消格式化功能。
vpp数据平面中的许多函数的返回值类型为clib_error_t *
。Clib_error_t是任意字符串,带有一些元数据[致命,警告],并且易于宣布。返回NULL clib_error_t *
表示“没问题,没有错误”。
Clib_warning(
是添加调试输出的便捷方法。clib警告优先于function:line info以明确定位消息源。Clib_unix_warning()添加了perror()
样式的Linux系统调用信息。在生产映像中,clib_warnings会生成syslog
条目。
Vppinfra序列化支持使程序员可以轻松地序列化和反序列化复杂的数据结构。
基本的原始序列化/反序列化函数使用网络字节顺序,因此在小端字节序的主机上序列化和在大端字节序的主机上反序列化没有结构性问题。
vppinfra事件记录器提供了非常轻量级(不到100ns),带有时间戳的精确事件记录服务。参见... / src / vppinfra / {elog.c,elog.h}
序列化支持使其易于保存,并最终组合了一组事件日志。在通过本地LAN运行NTP的分布式系统中,我们发现从多个系统元素收集的事件日志可以与不小于50us的时间不确定性结合使用。
典型的事件定义和日志记录调用如下所示:
ELOG_TYPE_DECLARE (e) =
{
.format = "tx-msg: stream %d local seq %d attempt %d",
.format_args = "i4i4i4",
};
struct {
u32 stream_id, local_sequence, retry_count; } * ed;
ed = ELOG_DATA (m->elog_main, e);
ed->stream_id = stream_id;
ed->local_sequence = local_sequence;
ed->retry_count = retry_count;
ELOG_DATA宏返回一个指向20个字节的任意事件数据的指针,该数据将按照format_args的说明进行格式化(脱机,而不是在运行时)。除了明显的整数格式外,CLIB事件记录器还提供了一些有趣的功能。“ t4”格式漂亮地打印枚举值:
ELOG_TYPE_DECLARE (e) =
{
.format = "get_or_create: %s",
.format_args = "t4",
.n_enum_strings = 2,
.enum_strings = {
"old", "new", },
};
“ t
”格式说明符表示相应的数据是事件的枚举字符串集中的索引,如先前的事件类型定义中所示。
“ T
”格式说明符表示相应的数据是事件日志的字符串堆中的索引。这使程序员可以发出任意格式的字符串。人们通常将此功能与哈希表结合使用,以防止事件日志字符串堆任意增大。
注意每个日志条目数据字段限制为20个八位字节,事件日志格式化程序支持这些数据类型的任意组合。如:“format”字段可能包含以下的一个或多个实例:
vpp引擎事件日志是线程安全的,并且由所有线程共享。注意不要序列化计算。尽管事件记录器的速度尽可能快,但它不适用于硬数据平面代码中的每个数据包。它最适合捕获罕见事件-上下链接事件,特定的控制面板事件等。
vpp引擎具有几个调试CLI命令,用于处理其事件日志:
vpp# event-logger clear
vpp# event-logger save # for security, writes into /tmp/.
# must not contain '.' or '/' characters
vpp# show event-logger [all] [] # display the event log
# by default, the last 250 entries
事件日志默认为128K条目。命令行参数“ … vlib {elog-events
”配置事件日志的大小。
如上所述,vpp引擎事件日志是线程安全的并且是共享的。为避免混淆工作线程记录的事件的不出现,请确保编码&vlib_global_main.elog_main-而不是&vm-> elog_main。后一种形式在主线程中是正确的,但几乎可以肯定会在辅助线程中产生不好的结果。
g2图形事件查看器可以直接或通过c2cpel
工具显示序列化的vppinfra事件日志。请参阅g2 Wiki页面。
G2图形事件查看器可以直接或通过c2cpel工具显示序列化的vppinfra事件日志。G2是一个细粒度的事件日志查看器。它具有高度的可扩展性,支持O(1e7事件)和O(1e3离散显示“轨道”)。G2显示由vppinfra“ elog。[ch]”记录器组件生成的二进制数据,并且还支持CPEL文件格式,如本节所述。
G2馆:此链接描述了如何构建G2
文件$ < HOMEDIR > /。g2包含显示首选项,可以覆盖它们。只需取消注释以下所示的节之一,或根据需要进行实验。
/*
* Property / parameter settings for G2
*
* Setting for a 1024x768 display:
* event_selector_lines=20
* drawbox_height=800
* drawbox_width=600
*
* new mac w/ no monitor:
* event_selector_lines=20
* drawbox_height=1200
* drawbox_width=700
*
* 1600x1200:
* drawbox_width=1200
* drawbox_height=1000
* event_selector_lines=25
*
* for making screenshots on a Macbook Pro
* drawbox_width=1200
* drawbox_height=600
* event_selector_lines=20
*/
这是带注释的G2查看器屏幕快照,对应于BGP前缀下载期间的活动。此数据在Cisco IOS-XR系统捕获了:
查看器具有两个主滚动条:水平轴滚动条可在时间上移动主绘图区域;水平滚动条可在时间上移动主绘图区域。垂直轴更改可见的过程迹线集。放大/缩小运算符更改时间刻度。
事件选择器PolyCheckMenu更改显示的事件集。使用这些工具以及一些耐心,您可以了解给定的事件日志。
G2具有三个相当复杂的鼠标手势界面,值得详细说明。首先,在显示事件上单击鼠标左键会弹出每个事件的详细信息框。
鼠标左键单击事件详细信息框将其关闭。要缩放到显示区域,请按住鼠标左键,然后向右或向左拖动直到出现缩放栅栏对:
缩放操作完成后,显示如下:
点击任意图将以全分辨率显示它们,右键点击将在新标签页中打开图,
要使用时间标尺,请按住鼠标右键;向右或向左拖动,直到标尺测量感兴趣的区域。如果时间轴刻度很粗,则事件框的时间宽度可能很大,因此在使用时间标尺时,请在每个事件框中使用“参考点”。
更改事件选择器设置可明显控制显示的点集。在这里,我们抑制所有事件,除了“此线程现在正在CPU上运行”:
请注意,先前显示的事件详细信息框由于重新选择事件代码而被抑制,当重新选择事件代码时,该事件详细信息框将重新出现。在上面的示例中,“ THREAD / THREADY pid:491720 tid:12”详细信息框以这种方式出现。
g2主窗口左下角的三个按钮控制快照环。快照只是保存的视图:将查看器调整为“有趣的”配置,然后按“快照”按钮将快照添加到环中。
单击“下一步”还原下一个可用快照。本德尔键删除当前快照。
请参阅下面的热键部分,以访问快速简便的方法来保存和恢复快照环。最终,我们可能会添加安全/便携式/受支持的机制,以从CPEL和vppinfra事件日志文件中保存/恢复快照环。
事件跟踪按发生的最后一个选定事件对跟踪轴进行排序。例如,如果选择一个事件,表示“ CPU上正在运行线程”,则显示的前N个跟踪将是要运行的前M个线程(N <= M;一个线程可能运行不止一次。此功能解决了导致分析的问题通过绘图区域的有限大小。
在标准(NoChaseEvent)模式下,看起来只有BGP线程5和9是活动的:
按下ChaseEvent按钮后,我们看到了另一幅图片:
序列
将鼠标下方的轨道移动到轨道集的末尾,从而有效地将其掩埋。序列
将鼠标下的轨道移到轨道集的开头。后者的功能可能不完全正确–我认为我们最终可能会提供“撤消”堆栈来提供精确的线程挖掘。
摘要模式通过将事件呈现为短的垂直线段而不是编号的框来使屏幕混乱。事件详细信息显示不受影响。G2以摘要模式启动,并充分缩小以显示轨迹中的所有事件。给定大量事件,摘要模式可将初始屏幕绘制时间减少到可容忍的值。充分放大后,键入“e
”-进入事件模式,以启用装箱的数字事件显示。
G2支持以下热键操作,根据该功能的原始作者,据说(大约在1996年)类似Quake:
略,点击查看