TNA架构手册翻译
参考了手册《Extern_P416 Intel® Tofino™ Native Architecture – Public Version》
能够使用的Externs(部分新功能可能因为没有文档化所以表格没有):
externs的直接性与间接性:
- Direct的extern见上表带有Direct前缀的Extern type;所有的direct extern与单个P4 表直接相连,表成为该extern的owner;它的性质是每一个direct extern都至少有一个方法能够更新其表条目,比如count() for a DirectCounter;我的理解是direct extern与表直接相关,具体作用类似于表的entery多了一个特定的域(比如用来计数);另外只能由这些extern的owner表来调用对应的方法。
- 所有的indirect extern在编译之前就会确定大小N,可以理解为一个N个条目的数组;它的性质是每一个indirect extern都有至少一个方法来更新它的条目,比如execute(index) for a DirectMeter. P4程序可以通过这些方法来访问indirect extern,这些方法可以被一个table的action调用或者在P4的control的apply block里面被调用。
- 综上我个人的理解是,直接的extern就是一个表的属性,当然只能通过这个表调用;间接的extern是开了一个新的表,可以在其他地方被调用。
Externs使用的包的长度:
- Ingress中,长度包括从端口的metadata或者resubmit header之后的第一个字节到以太网帧Frame Check Sequence末尾的所有字节;因此对于从外部以太网接口接受的数据包,数据包长度始终至少为64字节,也就是最小以太网帧的长度
- Egress中,长度包括了ingress deparser里面的所有字节(或者是egress deparser,如果是egress-to-egress mirror的情况)。它不包括egress intrinsic metadata.
- 需要注意的是 当前阶段的P4操作对于长度没有影响
Action profile
Action profile的作用如下图所示:
该extern的主要效果是开一个动作表,给原来的table只需要该动作表的key即可;
例子如下:
Action selector
这是另外一种机制来把表的条目和表条目外部定义的action相关联的extern。它比action profile更强大因为提供了动态选择动作规范的功能(实际上又加了一层间接表)
接口如下:
checksum
略,这块比较工程
Counters
Counter是用来保存数据包计数 或/和 字节计数的机制;Counter的值只能被控制面的软件读;如果想要让counters能被P4程序读,那么应该另外用Register实现;
TNA支持直接/间接的counter;
Counter以及DirectCounter包含以下格式:
- A packet count
- A byte count
- Both a packet count and a byte count
其中byte count会按照上述的”包的长度”减去参数adjust_byte_count的值递增;
Indirect Counter:
Direct Counter:
另外还有default action与Direct Counter的讨论,(我感觉不是很有用如果用到再对手册)
Digest
Digest是一种把消息Digest从数据平面传到控制平面的机制
另外的传递消息给控制平面的方法是通过发包到CPU 以太网口或者CPU PCIe口;发送数据包到这些端口需要发送大部分通用的包头,可能还需要payload;每一个都作为单独的信息由控制平面进行接受和处理;
Digest相对于往CPU端口发的好处:
- Digest信息比最小的Ethernet数据包小
- Data可以被自动灵活的打包成Digest信息,不需要再以太网数据包定义头格式
- TNA能够用硬件把多个digest信息合成一个大的messages,减少了控制面接受这些信息的时间;
- Tofino硬件有办法消除重复的摘要(digest)信息(重复指摘要信息的字段值与最近的摘要信息一样)这进一步降低了控制面软件必须处理的消息速率;
摘要信息可以包含数据平面的输入到ingress deparser的任意值;
创建digest实例的时候,程序员需要指定保存的值的类型,一般是P4 struct 类型;然后硬件就会把这些摘要内容进行序列化,保证控制平面能够区分不同摘要实例的信息;
一个TNA程序能够在ingress deparser里面初始化最多8个不同的摘要实例。每一个摘要信息可以包含至多47 bytes的数据;每次数据包执行ingress的时候最多创建一个摘要信息;
默认情况下不会产生digest;为了生成一个digest需要在digest_type字段里面设置0-7这样的数值,用该数值来选择要在摘要中发的值(考虑到最多初始化8个摘要实例,这个其实是在选哪一种);
digest_type在ingress开始执行的时候初始化。默认是无效的。如果ingress代码的某处把它进行了分配后续又要撤销的化,再digest_type字段使用invalidate函数即可;
Digest是通过调用pack方法创建的;参数是digest里要包含的值。参数的类型必须与构造digest实例的时候给定的类型相同;pack方法必须包含在if语句中,并且带有条件(digest_type == constant).
案例代码:
digest_type具有字段有效性(field validity),如果是invalid,那么条件语句的结果会都是false;
Hash
可以对于header或者metadata的字段的任何集合计算这些值的确定性哈希函数;hash的输出可以用于任意的表达式,比如作为counter,meter以及register的索引;
另外可以作为ActionSelector的输出,用于实现ECMP LAG功能等;
接口如下:
支持的预定义的哈希函数:
另外用户可以用CRCPolynomial定义任意的CRC多项式,下表可用作哈希参数的参考:
上表中,如果Reversed=Y,那么要把CRCPolynomial的 reversed argument置为1,否则置为0;
CRCPolynomial支持的参数:
- poly:要使用的多项式;对于幂N的多项式必须设置位N,也就是说字符串的位宽必须是N+1
- init:CRC shift register的初始值,默认是0
- reversed:指定要hash的数据为是否要用相反的顺序发送到执行CRC的shift register里面,true表示反着发,false表示按照正常顺序发
- xor:在最后CRC计算的时候用于XOR的值,默认是0x0
- msb:指示使用hash输出的最高有效位
- extend:指示重复哈希输出宽度,知道达到所需要的位宽度
当使用CRCPolynomial的时候,HashAlgorithm_t的参数应该是HashAlgorithm_t.CUSTOM.
案例:
Meter
Meter提供了一个机制用于测量或者检测最近到来的数据包序列的速度何时高于或者低于配置的平均速率(或者是两个设置的速率,对于两个rate meters的情况)
当包访问meter的时候会被赋予黄色,绿色或者红色;
-————————————————————————-
它根据的文档是RFC2698,该文档描述了trTCM 双速率三色标记;以下是该文档
如果数据包超过峰值速率PIR会被标记为红色,否则标记为黄色或者绿色,取决于是否超过承诺信息速率CIR。
主要流程就是Meter会对每一个包测速把结果反映在Marker里面
Meter可以以两种模式运行,在色盲Color-blind模式中,Meter保证数据包流在meter之前是不被染色的;在Color-Aware模式则表示数据包流在之前被预染色过;
需要设置的参数:
- 峰值信息速率 (PIR) 及其关联的峰值突发大小 (PBS) 和承诺信息速率 (CIR) 及其关联的承诺突发大小 (CBS)。
PIR 和 CIR 以每秒 IP 数据包的字节数来衡量;PIR必须大于CIR
PBS和CBS以字节为单位,需要大于0,建议配置为大于等于流中最大可能的IP数据包的大小
Meter过程:
Meter与Meter的模式以及两个速率为PIR,CIR的令牌桶P,C有关;这两个桶的最大的大小是PBS和CBS;
P,C在最初是满的也就是Tp(0)=PBS,Tc(0)=CBS,在之后Tp美标递增PIR次,Tc美标递增CIR次直到他们满了
当一个大小为B的数据包在时间t到达的时候,trTCM如果是Color-blind会执行以下逻辑:
- If Tp(t)-B < 0, the packet is red, else
- if Tc(t)-B < 0, the packet is yellow and Tp is decremented by B, else
- the packet is green and both Tp and Tc are decremented by B.
如果是Color-aware模式:
- If the packet has been precolored as red or if Tp(t)-B < 0, the packet is red, else
- if the packet has been precolored as yellow or if Tc(t)-B < 0, the packet is yellow and Tp is decremented by B, else
- the packet is green and both Tp and Tc are decremented by B.
结果会存在DS dield里面;对于AF PHB,颜色可以编码为数据包的丢弃优先级
-—————————————————————————–
在TNA实现中Meter和Direct Meter包含了“color aware” and “color blind” meters.主要区别是调用哪一种execute方法;
Meter的类型主要表示要测量包的字节数还是包的数量:
这里的包长度同样指的是上述定义的包长度减去参数adjust_byte_count的值
Meter的color有红黄绿三种
注意到虽然黄色有两种编码,但是TNA不会做区分;
用户自定义颜色(要用的时候看手册吧)
Indirect Meter接口:
Direct Meter接口:
Mirror
mirror是一种创建包的备份以及把备份发送到指定目的地的机制。TNA支持两种mirror:ingress-to-egress和egress-to-egress;
- ingress-to-egress mirror
包目的地的决策是在ingress control里面做的;Mirror extern在ingress deparser里面提供了emit方法;Mirror会对于一个数据包刚刚抵达ingress parser的内容(在ingress以及ingress deparser做修改前;不会包含)做复制,Mirror extern会预置指定的mirror包头,然后把这个包发给Traffic Mangager;
TM会使用一个mirror会话识别器(mirror session identifier,由emit的第一个参数指定)来查找mirror session table,这个表里面包含了一些元数据表明这个mirror的包应该发送到哪里;mirror sessions都是被控制面设置的;mirrored的包会在后去egress pipeline里面进行处理;
- egress-to-egress mirror
与前面相似;mirror 包这个决定是在egress 里面做的,会在egress deparser里面调用emit;和前面的主要区别是这里mirror的内容是经过egress control以及egress deparser所有修改完成之后的内容;其余一致;
当代码启动mirror操作的时候可以指定1023个mirror sessions之一;(不要使用保留的mirror session 0)
例子:如果 要支持一个p4程序发送一个数据包复制到CPU以太网口,P4程序以及控制面开发者约定用27号mirror session来实现,那么控制面软件会设置mirror session27的ucast_gress_port参数设置到CPU的以太网端口号;接着P4程序会处理这个包,使用mirror在27号session上面发送;这会让TM读27号上面的设置信息,找到等于CPU以太网号的ucast_gress_port;TM将镜像数据包排队等待 CPU 以太网端口。请注意,P4 程序无法更改镜像会话属性
TM可以最多将32字节的用户自定义包头预置到mirror数据包;Tofino支持多达8个可能的用户定义包头,每一个mirror数据包可以使用8个用户定义的包头之一;
TNA的mirror可以选择mirror session以及自定义的头;这块都是emit的参数里面设置的;前者在session_id里面,后者在mirror_type里面;
为了在ingress里面mirror一个包,需要把mirror_type的值设置成1-7(来选择自定义包头);初始化是0,0表示不适用mirror;如果在P4的某一个地方把mirror_type设置1-7的一个,然后又不想mirror了,把该值再次设置成0即可;
为了在egress里面mirror一个包,需要把mirror_type设置成0-7;在egress里面,mirror_type是用field validity的,在一开始的时候是invalid的;因此如果在某一个地方设置了mirror_type之后又不想mirror,设置mirror_type的field为invalid即可;
mirror提供了两种emit方法,前者会创建一个不含用户定义包头的包,后者会根据hdr参数胜场含有用户自定义包头的mirror包
另外emit方法需要包含在条件是(mirror_type == constant)形式的if语句里面;
if的语句数量最多是8个;如果mirror_type是invalid的,那么这个if语句的condition是false也不会创建mirror包
ParserCounter
可以用于提取header stacks或者具有可变长度的包头;Tofino有一个8位带符号的计数器,可以用临时的值或者包头字段初始化;使用set初始化计数器的时候可以对于header字段执行有限数量的操作比如masking和circular rotation
Random
Random可以提供均匀分布的伪随机数的生成;如果想要实现不是均匀的随机数,可以先生成均匀分布的随机数,然后对结果进行表查找 和/或 数学运算来达到想要的分布
Register
寄存器是stateful memories,它的值可以在P4程序的控制下在数据包转发期间进行读取和写入
Register也有indirect和direct两种;
构造Register的接口:
I表示indirect register的index的类型;
T表示每一个条目的类型;条目的类型必须是bit<8>, int<8>, bit<16>,int<16>, bit<32>, or int<32> values, or may be pairs of one of those types
任何拥有两种相同类型字段的结构会被识别为一对;另外条目包含单个值得配型bit<1>
RegisterAction可以用来访问Register,RegisterAction包含了apply可以用来读和更新Register里面得一个条目;对于一个Register条目可以定义四个不同的RegisterAction,但是对于一个包对于给定的Register只能执行一个RegisterAction
RegisterAction的apply方法需要定义一个或者两个参数;第一个inout参数表示正在读取和更新的Register条目的值,第二个可选的out是table action中调用execute方法将返回的值;
RegisterAction由外部的table actions通过调用execute方法触发,这会让Stateful ALU读 特定的Register storage,运行RegisterAction代码然后把修改的值写回到相同的Register条目中;以下是最简案例:
Stateful ALU中有两个比较的ALU和四个数学/逻辑ALU 用于计算;比较的结果可以用于调节其他ALU的输出;对于给定的RegisterAction,所有的ALU都会按照相同的字节大小运行,可以是8,16,32比特;ALU的输入可以来自memory word (either half if it is a pair)或者来自either of two PHV registers accessed by the stateful ALU或者来自至多4个常数;
(All Register Actions sharing a single Register also share these resources, so the compiler will attempt to efficiently pack and reuse values used by Register Actions in the same stateful ALU.)四个ALU会被组织成两对;编译器会把apply方法里面的操作调度到相应的ALU执行指定的计算;stateful ALU的可选输出必须是stateful ALU作为输入的值的副本或者写入内存的副本;
案例:通过在内存存储之前的时间戳来度量数据包间的时间戳来查找超过某个burst大小的bursts;
上述代码体现了写RegisterAction的基本原则:
- 在修改内存里面的值之前始终对于它涉及的内存中的值进行测试
- 在代码的任何给定路径上对于每一个register 条目只进行一次修改
- 对于要输出的flag,在方法的顶端设置为0,当合适的时候设置为1;(见burst的修改)
如果RegisterAction 条目的类型是bit<1>,那么大多数stateful ALU的程序性是禁止的;不允许比较以及PHV读,允许的操作只能是把这块内存bit设置成0或者1以及返回先前的bit value;一般用于更新ActionSelector或者不同的bloom filter;
MathUnit Extern用来访问Tofino stateful ALU中的math unit;可以直接调用也可以进行一定的编程:
MathUnit可以在RegisterAction里面被调用:
对于一个给定的Register只能有一个MathUnit;如果有多个RegisterAction共享一个Register那么他们只能用一个MathUnit;
Resubmit
包重传是一种在一个数据包上面重复ingress处理的过程;
比如说MPLS协议,在MPLS头后面不知道是IPV4头还是以太网,因此可以先过一遍parser来确定是哪一个,用resubmit header打个标然后用resubmit回传到ingress前端再处理;
所有的非resubmitted包(包括来自front panel port,CPU,packet generator,recirculated的包)在ingress parser的时候的ingress intrinsic metadata里面的resubmit_flag都是0;所有resubmitted数据包的resubmit_flag都是1;
自定义的头至多64bit(实践中,Tofino2里面支持128bit),会覆盖原来包的端口元数据;除了resubmit_flag字段以及端口元数据包头的部分之外,resubmit的包其余的部分和之前的数据包一样;另外一个数据包只能至多被resubmit一次,因为resubmit操作遇到resubmit_flag=1的数据包的时候会让它被丢掉;
TNA支持最多8个不同的用户定义的resubmit header;需要在ingress control里面给resubmit_type赋值,在ingress deparser里面来选择;
为了resubmit 一个包,需要在0-7里面指定值到resubmit_type里面,resubmit_type同样拥有field validity,初始化是invalid的;如果代码某一处制定了resubmit_type后来又不想resubmit了,在resubmit_type上使用invalidate函数即可;
Resubmit extern只会在ingress deparser里面初始化;它也提供了两个emit方法;
第一个是直接resubmit;第二个可以指定自定义类型的header;
样例代码如下,要注意emit方法只能包裹在条件为resubmit_type==constant的if语句里面;
如果包被resubmitted了那么就不会发给traffic manager;在上述代码里面的emit会被调用,但是pkt的值会被丢掉;
resubmit_type相关的if语句最多有8条。如果resubmit_type是invalid的那么语句的条件会返回false;