转自:如何获取网络包的硬件时间戳_飞行的精灵的博客-CSDN博客
在一些应用中我们需要获取网路报文进出MAC的精准的时间戳。相比较于软件时间戳,硬件时间戳排除了系统软件引起的延时和抖动。如下图所示意:
下面我们使用北京飞灵科技有限公司开发的TSync时钟同步开发板来测试 .
查看网卡捕获时间戳的能力
进入Linux命令行后,我们可以使用ethtool -T eth0 来查看对应的MAC捕获时间戳的能力。
root@TSync:~# ethtool -T eth0
Time stamping parameters for eth0:
Capabilities:
hardware-transmit (SOF_TIMESTAMPING_TX_HARDWARE)
software-transmit (SOF_TIMESTAMPING_TX_SOFTWARE)
hardware-receive (SOF_TIMESTAMPING_RX_HARDWARE)
software-receive (SOF_TIMESTAMPING_RX_SOFTWARE)
software-system-clock (SOF_TIMESTAMPING_SOFTWARE)
hardware-raw-clock (SOF_TIMESTAMPING_RAW_HARDWARE)
PTP Hardware Clock: 0
Hardware Transmit Timestamp Modes:
off (HWTSTAMP_TX_OFF)
on (HWTSTAMP_TX_ON)
Hardware Receive Filter Modes:
none (HWTSTAMP_FILTER_NONE)
all (HWTSTAMP_FILTER_ALL)
在Capabilities 字段里, 我们可以知道这个MAC 支持捕获那些时间戳的类型。
SOF_TIMESTAMPING_TX_HARDWARE 支持捕获硬件发送时间戳。
SOF_TIMESTAMPING_RX_HARDWARE 支持捕获硬件接收时间戳。
SOF_TIMESTAMPING_TX_SOFTWARE 支持捕获软件发送时间戳。
SOF_TIMESTAMPING_RX_SOFTWARE 支持捕获软件接收时间戳。
捕获发送时间戳时,可配置为两种模式:
HWTSTAMP_TX_OFF 表示网卡发送的报文都不捕获硬件时间戳。
HWTSTAMP_TX_ON 表示网卡对所有接收到的报文都捕获硬件时间戳。
捕获接收报文的时间戳时,有几种过滤器,可以选择捕获特定数据流的时间戳:
HWTSTAMP_FILTER_NONE: 所有收到的数据流都不捕获时间戳。
HWTSTAMP_FILTER_ALL:所有收到的数据流都捕获时间戳。
除了以上两个过滤器外,一些网卡可能还有以下几种过滤器用于捕获PTP协议的报文时间戳。
ptpv1-l4-sync (HWTSTAMP_FILTER_PTP_V1_L4_SYNC)
ptpv1-l4-delay-req (HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ)
ptpv2-l4-sync (HWTSTAMP_FILTER_PTP_V2_L4_SYNC)
ptpv2-l4-delay-req (HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ)
ptpv2-l2-sync (HWTSTAMP_FILTER_PTP_V2_L2_SYNC)
ptpv2-l2-delay-req (HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ)
ptpv2-event (HWTSTAMP_FILTER_PTP_V2_EVENT)
ptpv2-sync (HWTSTAMP_FILTER_PTP_V2_SYNC)
ptpv2-delay-req (HWTSTAMP_FILTER_PTP_V2_DELAY_REQ)
配置捕获硬件时间戳功能
要捕获硬件时间戳,需要配置MAC上的PHC硬件和配置socket 生成时间戳的类型。
使能PHC时间戳功能
通过SIOCSHWTSTAMP ioctl命令配置硬件发送时间戳捕获模式和 硬件接收时间戳的过滤器。代码如下:
struct ifreq hwtstamp;
struct hwtstamp_config hwconfig;
memset(&hwtstamp, 0, sizeof(hwtstamp));
memset(&hwconfig, 0, sizeof(hwconfig));
hwtstamp.ifr_name = "eth1";
hwtstamp.ifr_data = (void*)&hwconfig;
// 设置网卡捕获所有发送报文的硬件时间戳。
hwconfig.tx_type = HWTSTAMP_TX_ON;
// 设置网卡捕获所有接收到的报文的时间戳。
hwconfig.rx_filter = HWTSTAMP_FILTER_ALL;
ioctl(sock, SIOCSHWTSTAMP, &hwtstamp)
配置socket 捕获时间戳的类型
int so_timestamping_flags = SOF_TIMESTAMPING_TX_HARDWARE | SOF_TIMESTAMPING_RX_HARDWARE;
setsockopt(sock, SOL_SOCKET, SO_TIMESTAMPING,&so_timestamping_flags, sizeof(so_timestamping_flags)) < 0)
获取发送或接收报文的时间戳
在发送完或接收完报文后,时间戳被记录到一个 cmsg_level为SOL_SOCKET, cmsg_type 为SCM_TIMESTAMPING, data为 struct scm_timestamping 的一个control message中。这个cmsg可以通过recvmsg() 接口读取。
对于发送报文的时间戳,是放在socket的error 队列中,使用下面函数取到msg中。
ssize_t recv_len = recvmsg(sock, &msg, MSG_ERRQUEUE);
对于接收到的报文的时间戳,使用下面函数取到msg中。
ssize_t recv_len = recvmsg(sock, &msg, 0);
时间戳信息以struct scm_timestamping结构保存在cmsg消息的data段中。结构里包含三个时间戳:
struct scm_timestamping {
struct timespec ts[3];
};
ts[0]里存放的时software 时间戳,如果使能的话有效,否则为0。
ts[1]里存放的是一个被转化为系统时间的硬件时间戳,这个硬件时间类似一个影子时钟,用来系统时间和MAC上phc 时钟同步。
ts[2]里存放的便是我们想要获取的硬件时间戳。
使用下面代码,即可获提取到我们想要的时间戳。
struct cmsghdr *cmsg = NULL;
struct scm_timestamping hw_ts;
struct timespec ts;
for(cmsg=CMSG_FIRSTHDR(&msg);cmsg!=NULL;cmsg=CMSG_NXTHDR(&msg, cmsg)) {
if(cmsg->cmsg_level==SOL_SOCKET && cmsg->cmsg_type==SO_TIMESTAMPING) {
hw_ts=*((struct scm_timestamping *)CMSG_DATA(cmsg));
fprintf(stdout,"HW: %lu s, %lu ns\n",hw_ts.ts[2].tv_sec,hw_ts.ts[2].tv_nsec);
fprintf(stdout,"ts: %lu s, %lu ns\n",hw_ts.ts[1].tv_sec,hw_ts.ts[1].tv_nsec);
fprintf(stdout,"SW: %lu s, %lu ns\n",hw_ts.ts[0].tv_sec,hw_ts.ts[0].tv_nsec);
memcpy(&ts, &scm_ts.ts[2], sizeof(struct timespec)); // 拷贝捕获的硬件时钟到timespec 结构中。
}
}
代码测试
为了验证获取的时间戳的正确性,我们使用两块飞灵科技的TSync时钟同步开发板作为报文的发送端和接收端,并分别在两个板子上捕获发送和接收的硬件时间戳。
为了使发送端和接收端的时间一致,我们首先让他们分别和GNSS 卫星时间同步。板子上电后,点击下图按钮,打开GNSS同步。
将两个板子分别通过PTP端口连接到路由器。 在串口控制台运行dhcp获取IP地址。分别拷贝stamp_send.c 和 stamp_recv.c 文件到两块板子上,并分别如下编译:
gcc stamp_recv.c -o stamp_recv
gcc stamp_send.c -o stamp_send
在接收端运行 ./stamp_recv eth0
root@TSync:~/stamp_test# ./stamp_recv eth0
source IP: 192.168.1.78
Test started.
Recv pakage: hello world 0
HW: 1639236711 s, 631040873 ns
ts[1]: 0 s, 0 ns
SW: 0 s, 0 ns
Hardwave recv timestamp: 1639236711 s, 631040873 ns
Recv pakage: hello world 1
HW: 1639236712 s, 632989572 ns
ts[1]: 0 s, 0 ns
SW: 0 s, 0 ns
Hardwave recv timestamp: 1639236712 s, 632989572 ns
Recv pakage: hello world 2
HW: 1639236713 s, 635084458 ns
ts[1]: 0 s, 0 ns
SW: 0 s, 0 ns
Hardwave recv timestamp: 1639236713 s, 635084458 ns
Recv pakage: hello world 3
HW: 1639236714 s, 637115333 ns
ts[1]: 0 s, 0 ns
SW: 0 s, 0 ns
Hardwave recv timestamp: 1639236714 s, 637115333 ns
在发送端运行 “./stamp_send eth0 192.168.1.78”,192.168.1.78 为接收端的IP 地址。
root@TSync:~/stamp_test# ./stamp_send eth0 192.168.1.78
source IP: 192.168.1.79
Test started.
Sent packet number (0/10) : hello world 0
HW: 1639236711 s, 631013470 ns
ts[1]: 0 s, 0 ns
SW: 0 s, 0 ns
Hardwave send timestamp: 1639236711 s, 631013470 ns
Sent packet number (1/10) : hello world 1
HW: 1639236712 s, 632962036 ns
ts[1]: 0 s, 0 ns
SW: 0 s, 0 ns
Hardwave send timestamp: 1639236712 s, 632962036 ns
Sent packet number (2/10) : hello world 2
HW: 1639236713 s, 635057007 ns
ts[1]: 0 s, 0 ns
SW: 0 s, 0 ns
Hardwave send timestamp: 1639236713 s, 635057007 ns
Sent packet number (3/10) : hello world 3
HW: 1639236714 s, 637087896 ns
ts[1]: 0 s, 0 ns
SW: 0 s, 0 ns
Hardwave send timestamp: 1639236714 s, 637087896 ns
从发送端和接收端的log可以看到, 报文的接收时间戳和发送时间戳之差,就是路由器的链路延时。
测试源码文件“硬件时间戳使用示例代码.zip”可以从飞灵科技的Wiki上下载。资料下载 - 飞灵科技-文档 :