时间敏感网络是传统以太网的扩展,实现实时能力和确定性通信。终端通常是基于Linux OS 的,它的实时能力是通过PREEMPT-RT 补丁实现的。而设备的实时性网络是通过时间敏感性网络技术实现。到目前为止,大多数Linux OS 的TSN 都是通过特殊的解决方案实现的,那么,是否能够在主流的Linux 中实现TSN 网络呢?时间同步和流量调度都已经能够实现了。但是并没有实现完整的TSN 标准。

      本文介绍如何在标准的Linux 系统中配置TSN ,主要是linuxptp,tc 等工具。

TSN 解决什么问题

TSN 主要解决两个问题:

  • 全网络设备实现时钟同步
  • 实时数据和非实时数据在一个网络中同传,并且保证实时数据具有优先级。

TSN 解决方案

     TSN 硬件支持

TSN 的核心思想是在原有以太网的基础上,增加了硬件消息队列,每个队列安装优先级排队,由硬件消息调度器在全局时钟的控制下控制队列消息的发送开关(Gate)。这样就能够保证实时数据能够以最快的时间发送出去,并且消息的发送是有序的。不会造成冲突。其结构示意图如下:

          其中每个时间片都有一个用户指定的持续时间。在每个时间片期间,用户通过指定与每个队列对应的哪些门(可能单个或多个门)“打开”或“关闭”来配置可以传输的流量优先级/队列

     tsn 消息的软件调度算法

        软件调度的方法是:

    将网络发送分成若干的时间片(time slice)

    应用程序发送的所有消息包都带有一个优先级(PRI),根据优先级映射到一组时间片(time slice)。例如:

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

       在上面的表格中,优先级2 的消息包映射到TC1,优先级3的消息映射到TC2. 时间片的数量和时间是可以设置的。

     最后,将时间片映射到tsn 以太网的硬件队列。同样地,也可以使用软件设置。

    如此一来,不同优先级的消息包能够在不同的时间片映射到硬件队列中。

    VLAN 设置

   消息包的优先等级是如何设定的呢,它是通过VLAN 的PCP 转化而来的。

           TSN基于IEEE 802.1Q[7]的虚拟局域网(virtual local area network,VLAN)和优先级标准构建的。

   TSN 将VLAN 的优先等级信息PCP自动转化为数据包的优先等级(PRI)。

VLAN 帧结构

 因此,TSN 是构建在VLAN 之上的。

  时间同步协议PTP

 显然,要实现数据的周期性时隙传输,必备的条件是网络中的所有设备和交换机需要使用全网统一时钟,这样才能够避免数据帧的发送冲突。实现时分复用(TDM time division multplexer)。在TSN中,使用IEEE1588/PTP 实现全网精确时钟同步。

        Linux 内核具有支持PTP 硬件时钟(PHC)的子系统·,可以使用POSIX 时钟显示该时钟。通过·POSIX 接口读取,设置和控制。PTP 协议不是Linux 内核的一部分,在用户空间,使用linuxptp 实现IEEE 1588 和IEEE 802.1AS-1 的PTP 协议。在TSN 中通过802.1AS 完成同步,它是IEEE1588 的一部分,并且做了某些扩展,例如在Wifi 中的时间同步。TNS 目前计划实现IEEE802.1AS-2020.它在主流Linux 上还没有实现。

        值得一提的是,并非所有的以太网硬件接口都支持IEEE1588。有的设备上linux ptp 是软件实现的。精度大打折扣。

PTP4L 的使用

在linux 中 ,使用Linux PTP 控制IEEE5188/PTP 的时间同步功能。

安装

git clone git://git.code.sf.net/p/linuxptp/code linuxptp
cd linuxptp
sudo make 
sudo make install

inuxptp主要包含2个程序:ptp4l和phc2sys

1.ptp4l 进行时钟同步,实时网卡时钟与远端的时钟同步(比如TSN交换机),支持1588 和 802.1AS 两种协议

2.phc2sys 将网卡上的时钟同步到操作系统,或者反之.

ptp4l -i eth2 -2 –m -s -f gPTP.cfg 
phc2sys -a -r --transportSpecific=1 -m --step_threshold=1000
 
 
# phc2sys还可以写成这样,功能是一样的
phc2sys  -s eth2 -c CLOCK_REALTIME --transportSpecific=1 -m --step_threshold=1000 -w

Linux 中的调度算法

Linux 的应用程序通过socket 接口实现通信。网络协议栈处理数据包。网络协议有TCP/IP,UDP、IP和原始的以太网数据包和硬件控制。网络栈通过调度,类和过滤器等来配置。这些机制已经建立了许多年。由于TSN 是以太网的扩展,因此,TSN 的Linux 实现仍然使用这些机制。TSN直接将它集成到Linux 网络栈中,基本原理如下图所示:

    Linux内核控制网络硬件的访问,保证数据包在正确的时间发送。这就是时间隙管理组件(Time Slot Management component)。然而内核需要正确的配置。由于需求来自于应用程序, 协议栈通过Qdiscs配置。

        Qdisc 对应一个数据包调度器,作为TSN 实现的一部分,各种Qdiscs 开发了出来,并且集成到主流Linux内核中。

数据包调度方法

  TSN 开发了几种数据包调度算法:

  • MQPRIO

      MQPRIO qdisc 是一个简单的队列规则,可以通过优先级以及优先级对应的流量等级,将不同流量映射到硬件队列的区间。连续的流量等级可以1对1的映射到硬件的队列上。

  • ETF (Earliest TxTime First) 

     ETF会在发送时间(txtime)到达之前的一段时间(delta) 缓存数据包,这个时间可以由delta选项控制。

  • ETS

       ETS基于预先设定的周期性门控制列表,动态地为出口队列提供开/关控制的机制。ETS定义了一个时间窗口,是一个时间触发型网络(Time-trigged)。这个窗口在这个机制中是被预先确定的。这个门控制列表被周期性的扫描,并按预先定义的次序为不同的队列开放传输端口。

在Linux 中 ETS 被称为Time Aware Priority Shaper (TAPRIO)。我们·这里主要采用taprio 

Linux 中的配置方法

      使用Linux 原有的tc 命令来设置Qdiscs。

tc 命令

       Linux操作系统中的流量控制器TC(Traffic Control)在2001 年被导入,用于Linux内核的流量控制,主要是通过在输出端口处建立一个队列来实现流量控制。

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 <command 1> <gate mask 1> <interval 1>

               sched-entry <command 2> <gate mask 2> <interval 2>

               sched-entry <command 3> <gate mask 3> <interval 3>

               sched-entry <command N> <gate mask N> <interval N>

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 <command> <gatemask> <interval>

<command> 只支持"S"

<gate mask>  每一位( bit)对应流量等级的开关状态(例如:bit 0 代表流量等级0/TC0)0  表示关闭,1表示打开

<interval>标志当前门控状态持续的时间

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同步到系统时钟):

例子

配置与使用

系统配置

        在本实验中,我们使用一台DELL 的台式机和一台Atom x6000e 处理器为基础的工业电脑Up Squared 6000 。Dell 电脑中扩展了一个i210 网卡,它支持tsn。而Atom x6000e 带有两个千兆网口,并且支持tsn。两台机器通过网线直连。均运行ubuntu OS。

Step 1 VLAN 的配置

加载 8021q模块到内核:

sudo modprobe 8021q

添加VLAN 

sudo ip link add link enp4s0 name enp4s0.1 type vlan id 1 egress-qos-map 2:2 3:3

      egress-qos-map 参数定义了linux 内部的包优先级SO_PRORITY到VLAN PCP 的映射关系。

配置VLAN IP:

sudo ip addr add 10.0.0.1/24 dev enp4s0.10

 激活VLAN:

sudo ifconfig enp1s0.10 up

验证

可以使用 ifconfig 和ping 命令验证设置的VLAN

yao@yao-UPN-EHL01:~$ ping 10.0.0.1
PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.
64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.055 ms
64 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=0.052 ms
64 bytes from 10.0.0.1: icmp_seq=3 ttl=64 time=0.051 ms
64 bytes from 10.0.0.1: icmp_seq=4 ttl=64 time=0.052 ms

注意:在本次实验中,一台设备的VLAN 设为 10,0.0.1  另一台设为 10.0.0.2.

Step 2 LinuxPTP 运行

主设备

sudo ptp4l  -i enp1s0  -m -H

从设备

sudo ptp4l  -i enp4s0  -m -H -s

与系统时钟建立联系

sudo phc2sys -m -s enp4s0 -w

注意:linuxPTP 需要关闭防火墙!

ufw disbale

运行的结果

表明同步效果相当好。抖动12us ,时延21us

Step 3 在Linux 中配置整形和调度算法

tc qdisc replace dev enp4s0 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 \
   clockid CLOCK_TAI

参数:

num_tc=3

时间片数量为3。

流量等级映射到VLAN优先级(PRI)

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

表示优先等级为2 的数据包映射到TC1,而优先等级3 的数据包映射到TC3。

queues 1@0 1@1 1@2

该参数表示映射时间片(TC)到硬件队列 。(注意Intel i210 默认硬件队列为4 有的文章中提到能够使用(ethtool -L  tx 3 来设置硬件队列数量,但是我尝试该条命令不成功) 

  • 流量等级0 <-> 队列1,
  • 流量等级1 <-> 队列2,
  • 流量等级2 <-> 队列3

这样 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)

对应的队列结构。


使用程序测试:

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
 
int main(void)
{
	int ret = -1;
	 
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (-1 == sockfd)
	{
		printf("socket open err.");
		return -1;
	}
 
   
    struct sockaddr_in local_addr = {0};
    local_addr.sin_family = AF_INET;                               
    local_addr.sin_addr.s_addr = inet_addr("10.0.0.1");       
    local_addr.sin_port = htons(6000);                              
    bind(sockfd, (struct sockaddr*)&local_addr, sizeof(local_addr)); 
    int opt = 2; 
    setsockopt(sockfd , SOL_SOCKET , SO_PRIORITY , &opt , sizeof(opt));
   
    struct sockaddr_in sock_addr = {0};	
	sock_addr.sin_family = AF_INET;                         
	sock_addr.sin_port = htons(6000);						 
	sock_addr.sin_addr.s_addr = inet_addr("10.0.0.2");	 
	char sendbuf[]={"hello world, I am a UDP socket."};
    int cnt = 10;
    while(1)
    {
        ret = sendto(sockfd, sendbuf, sizeof(sendbuf)-1, 0, (struct sockaddr*)&sock_addr, sizeof(sock_addr));
	    //printf("ret = %d \n",ret);
        usleep(20000);
    }
 
   
    /*struct sockaddr_in recv_addr;
    socklen_t addrlen = sizeof(recv_addr);
    char recvbuf[1024] = {0};
    while(1)
    {
        ret = recvfrom(sockfd, recvbuf, 1024, 0,(struct sockaddr*)&recv_addr,&addrlen);  
        printf("[recv from %s:%d]%s \n",inet_ntoa(*(struct in_addr*)&recv_addr.sin_addr.s_addr),ntohs(recv_addr.sin_port),recvbuf);
    }*/
    
   
    close(sockfd);
}

在两台机器中,一个运行send另一个运行recieve。程序要略作修改。在wireshark 中可以检测到VLAN 中的内容。

同步情况的测试

  可以编写一个程序产生一个脉冲信号,通过示波器测量。

tsn 的性能,可以使用一个软件发送大量非实时信息,看实时数据是否受到影响。

 

结束语

         本文介绍了在标准的Linux OS 中,配置时间敏感网络的方法。下一步,要结合tsn 交换机做进一步测试。本人也是初学,敬请高人点拨。

 

Logo

获取更多汽车电子技术干货

更多推荐