MQPRIO qdisc 是一个简单的队列规则,可以通过优先级以及优先级对应的流量等级,将不同流量映射到硬件队列的区间。连续的流量等级可以1对1的映射到硬件的队列上。
使用方法如下:
tc qdisc ... dev dev ( parent classid | root) [ handle major: ] mqprio [ num_tc tcs ] [ map P0 P1 P2... ] [ queues count1@offset1 count2@offset2 ... ] [ hw 1|0 ] [ mode dcb|channel] ] [ shaper dcb| [ bw_rlimit min_rate min_rate1 min_rate2 ... max_rate max_rate1 max_rate2 ... ]]
num_tc:使用的流量等级,最大支持16个等级
map: 将VLAN优先级 (VLAN PRI) 0 -15映射到特定的流量等级
queues: 为每个流量等级提供队列的数量和范围。每个流量等级的队列范围不能重叠且必须连续
hw: 设置为1支持硬件卸载,设置为0配置为仅在软件使用用户指定的值
举个例子:(来源open62541 pub/sub TSN)
sudo tc qdisc add dev
[num_tc 3] 定义3个流量等级
[map 2 2 1 0 2 2 2 2 2 2 2 2 2 2 2 2]
PRI |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
TC |
2 |
2 |
1 |
0 |
2 |
2 |
2 |
2 |
2 |
2 |
2 |
2 |
2 |
2 |
2 |
2 |
如果VLAN PRI=3 就映射到了TC=0
[queues 1@0 1@1 2@2]
i210网卡有4个收发队列 1..4
TC=0:队列1
TC=1:队列 2
TC=2:队列3和队列4
The ETF (Earliest TxTime First) qdisc 允许应用控制数据包从流量控制层到网络设备层的时刻。如果网络接口支持并且配置了offload,ETF也会控制数据包离开网络控制器(就是控制数据包从网卡发出的时刻)
ETF会在发送时间(txtime)到达之前的一段时间(delta) 缓存数据包,这个时间可以由delta选项控制
ETF需要安装在其他的qdisc下,例如mqprio
ETF依赖于SO_TXTIME socket选项和 SCM_TXTIME CMSG在每一个数据包的control message。具体的做法可以参考Open62541中的ua_pubsub_ethernet.c代码中959行的sendWithTxTime函数
static UA_StatusCode
sendWithTxTime(UA_PubSubChannel *channel, UA_ExtensionObject *transportSettings, void *bufSend, size_t lenBuf) {
/* Send the data packet with the tx time */
char dataPacket[CMSG_SPACE(sizeof(UA_UInt64))] = {0};
/* Structure for messages sent and received */
struct msghdr message;
/* Structure for scattering or gathering of input/output */
struct iovec inputOutputVec;
ssize_t msgCount;
UA_PubSubChannelDataEthernet *channelDataEthernet =
(UA_PubSubChannelDataEthernet *) channel->handle;
/* Structure for socket internet address */
struct sockaddr_ll socketAddress = { 0 };
socketAddress.sll_family = AF_PACKET;
socketAddress.sll_ifindex = channelDataEthernet->ifindex;
socketAddress.sll_protocol = htons(ETHERTYPE_UADP);
inputOutputVec.iov_base = bufSend;
inputOutputVec.iov_len = lenBuf;
memset(&message, 0, sizeof(message));
/* Provide message name / optional address */
message.msg_name = &socketAddress;
/* Provide message address size in bytes */
message.msg_namelen = sizeof(socketAddress);
/* Provide array of input/output buffers */
message.msg_iov = &inputOutputVec;
/* Provide the number of elements in the array */
message.msg_iovlen = 1;
/* Get ethernet ETF transport settings */
UA_EthernetWriterGroupTransportDataType *ethernettransportSettings;
ethernettransportSettings = (UA_EthernetWriterGroupTransportDataType *)transportSettings->content.decoded.data;
/*
* We specify the transmission time in the CMSG.
*/
/* Provide the necessary data */
message.msg_control = dataPacket;
/* Provide the size of necessary bytes */
message.msg_controllen = sizeof(dataPacket);
/* Structure for storing the necessary data */
struct cmsghdr* controlMsg;
/* Control message created for tx time */
controlMsg = CMSG_FIRSTHDR(&message);
controlMsg->cmsg_level = SOL_SOCKET;
controlMsg->cmsg_type = SCM_TXTIME;
controlMsg->cmsg_len = CMSG_LEN(sizeof(UA_UInt64));
if(ethernettransportSettings && (ethernettransportSettings->transmission_time != 0))
*((UA_UInt64 *) CMSG_DATA(controlMsg)) = ethernettransportSettings->transmission_time;
msgCount = sendmsg(channel->sockfd, &message, 0);
if ((msgCount < 1) && (msgCount != (UA_Int32)lenBuf)) {
return UA_STATUSCODE_BADINTERNALERROR;
}
return UA_STATUSCODE_GOOD;
}
举个例子:一个带txtime的数据包如果发给ETF,ETF会缓存这个数据包直到 (txtime-delta) , 然后将数据包丢给网络设备。在(txtime-delta)到达之前,ETF会丢掉任何数据包,因此保证最先到达的TxTime出队
使用方法如下:
tc qdisc ... dev dev parent classid [ handle major: ] etf clockid clockid [ delta delta_nsecs ] [ deadline_mode ] [ offload ]
clockid:指定qdisc使用的内部时钟, 用于测量时间和调度时间
delta:在入队或出队一个数据包之后,qdisc会根据下次的txtime - delta调度下一次的唤醒时间。这个参数一般作为系统调度延时的经验值。
offload:当offload 被设置,etf 会试着将网络接口配置为基于事件的发送仲裁(需要硬件支持)。这个特征经常被称作"Launch Time" or "Time-Based Scheduling"
举个例子:(来源open62541 pub/sub TSN)
sudo tc qdisc add dev eno1 parent $MQPRIO_NUM:1 etf offload clockid CLOCK_TAI delta 150000
[$MQPRIO_NUM:1] 配置队列1为ETF(对应TC=0)
[clockid CLOCK_TAI] 参考时钟:CLOCK_TAI,
[offload] 使能"Launch Time"
[delta 150000] 在下次txtime 之前150us调度数据包
3. ETS
IEEE 802.1Qbv/D2.2 Enhancements for Scheduled Traffic (EST), 早期被称作Time Aware Shaper (TAS), 是对802.1Q中定义传输选择算法的增强,已经正式作为802.1Q-2018中的一部分。由于工业控制大部分需要保证周期数据的的确定性,因此该协议对于TSN在工业控制中的应用比较重要。
ETS基于预先设定的周期性门控制列表,动态地为出口队列提供开/关控制的机制。ETS定义了一个时间窗口,是一个时间触发型网络(Time-trigged)。这个窗口在这个机制中是被预先确定的。这个门控制列表被周期性的扫描,并按预先定义的次序为不同的队列开放传输端口。如下图所示:出口硬件有8个队列,每个都有唯一的传输选择算法。传输由门控制列表(gate control list, GCL)控制。对于给定队列门控制有两个状态 :打开和关闭
In Linux, EST/TAS 可以通过配置 taprio 实现. Time Aware Priority Shaper (TAPRIO), 是 qdisc 实现IEEE 802.1Q-2018 Section 8.6.9的简单版本,可以配置一系列门控状态,每个门控状态关联到不同的流量等级(TC)
TAPRIO的配置指令如下:
tc qdisc ... dev dev parent classid [ handle major: ] taprio
num_tc tcs
map P0 P1 P2 ... queues count1@offset1 count2@offset2
...
base-time base-time clockid clockid
sched-entry
sched-entry
sched-entry
sched-entry
num_tc:使用的流量等级,最大支持16个等级
map: 将优先级 (VLAN PRI) 0 -15映射到特定的流量等级
queues: 为每个流量等级提供队列的数量和范围。每个流量等级的队列范围不能重叠且必须连续
base-time:
指定调度的起始时刻,单位是纳秒,参考时钟由clockid指定。如果'base-time'是过去的时间,那么调度开始时间为base-time + (N * cycle-time),其中N比现在时刻大的最小整数。"cycle-time" 所有调度项目的时间总和,也就是调度周期
一般默认设置为0(因为时钟是同步的,所以在所有设备上设置一个相同的值即可)
clockid:指定qdisc参考的内部时钟
sched-entry:
门控制的调度项目,sched-entry
flags:
0x01: txtime支持模式,qdisc通过发送时间戳判断数据包的发送周期
0x02: full-offload模式, GCL直接传递给网卡,并且交给网卡执行,在这个模式下不需要设置clockid,默认网卡参考的时钟是/dev/ptpN (N 可以通过如下指令获取 ethtool -T eth0 | grep 'PTP Hardware Clock' ) (如果设备仅作为bridge,只需要通过ptp4l同步/dev/ptpN即可,并不需要phc2sys将/dev/ptpN同步到系统时钟)
举例来说:(TI AM64X)
#Setup interface and queue configuration
ip link set dev eth0 down
ethtool -L eth0 tx 3
#disable rrobin
ethtool --set-priv-flags eth0 p0-rx-ptype-rrobin off
#bring up eth0 interface
ip link set dev eth0 up
#Setup EST schedule with 3 Gates (Q0-Q2). For description of Command parameters, see manual page for taprio.
#TC0 <-> Q0, TC1 <-> Q1, and TC2 <-> Q2
tc qdisc replace dev eth0 parent root handle 100 taprio \
num_tc 3 \
map 0 0 1 2 0 0 0 0 0 0 0 0 0 0 0 0 \
queues 1@0 1@1 1@2 \
base-time 0000 \
sched-entry S 4 125000 \
sched-entry S 2 125000 \
sched-entry S 1 250000 \
flags 2
首先设置网卡3个发送队列: ethtool -L eth0 tx 3
定义3个流量等级TC: num_tc 3
将流量等级映射到VLAN优先级:
PRI |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
TC |
0 |
0 |
1 |
2 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
映射流量等级到硬件队列 TC0 <-> 队列1, TC1 <-> 队列2, and TC2 <-> 队列3 :
queues 1@0 1@1 1@2
这样 PRI=2的数据包会通过队列2发送,这样 PRI=3的数据包会通过队列3发送
每个调度周期,首先打开TC2(bit = 2,b100)对应的队列125000纳秒;再打开TC1 (bit = 1,b010)对应的队列125000纳秒, 再打开TC0 (bit = 0,b001)对应的队列250000纳秒
网卡硬件offload: flags 2 (因此不需要clockid)
这样实现的时许如下图所示: