本文中gptp部分理论介绍采用gPTP,自动驾驶时间同步里的“有趣灵魂” - 知乎文章中的描述
时钟源:
1.系统计数器:
CLOCK_MONOTONIC(启动后单调时间)、CLOCK_REALTIME(实时时间)使用的计数器,也可以称为系统计数器,一般linux默认使用的系统时间(systemtime)都是依赖于该计数器
2.PHY自带计数器:
phc(ptp hardware clock),实际为网口的PHY(如88q2112)自带的硬件定时器所对应的时间
下面基于imx8系列芯片的linux kernel driver分析下 ptp hardware clock
PTP hardware clock相关:
eth对应的PHY上的定时器,网络驱动加载时候创建的节点为/dev/ptp
ptp hardware clock驱动代码:
drivers/ptp目录下
ptp_chardev.c ----/dev/ptp设备节点的fileoperations相关接口,主要为相关ioctls
ptp_clock.c ----ptp_clock_register/ptp_clock_settime/ptp_clock_gettime
其中ptp_clock_register为注册函数,重点关注
会在drivers/net/ethernet/freescale/fec_ptp.c中的fec_ptp_init中调用,所以一般eth0对应的就是/dev/ptp0,eth1对应的就是/dev/ptp1,并且该函数最终会为ptp hardware clock创建一个posix clock
note:因为ptp hardware clock是对应的每个外网口上的PHY带的计数器,所以每个网口驱动挂载的时候会加载ptp hardware clock相关驱动
如上所述网络驱动代码fec_ptp.c中除了会调用ptp_clock_register注册ptp hardware clock外,还有几个地方值得关注:
fec_ptp_init函数中会将fec_ptp_adjfreq/fec_ptp_adjtime/fec_ptp_gettime/gec_ptp_settime等PHY时间相关函数接口注册给ptp_clock_info,后续ptp hardware clock的驱动就是通过这些函数来实现设置和获取时间等操作,同时也会给phy的timer做初始化
框图:

gPTP(generalized Precision Time Protocol,广义精确时间同步协议),基于PTP(IEEE 1588v2)协议进行了一系列优化,形成了更具有针对性的时间同步机制,可以实现μs级的同步精度,协议标准为802.1as,协议本身定义在OSI模型的第2层,链路层,所有报文出入端口的时间戳通过硬件来记录
gPTP定义有两种设备类型,Time-aware-end Station和Time-aware Bridge。每种设备都具有本地时钟,本地时钟都是通过晶振的振荡周期进行度量的,设备内部硬件计数器负责对振荡周期进行计数。设备中用来发布时间同步报文的网络端口称为主端口,用来接收时间同步报文的端口称为从端口
(1)Time-aware-end Station,既可以作为主时钟,也可以作为从时钟
(2)Time-aware Bridge,既可以作为主时钟,也可以作为桥接设备,类似交换机。桥接类设备在收到gPTP报文后,会请报文搓个澡,然后再送出去。而报文在桥接设备内搓澡消耗的时间,称为驻留时间。gPTP要求桥接设备必须具有测量驻留时间的能力
下图为一个Taes、TaB的一个方案图:

BMCA(Best Master Clock Algorithm):
gPTP中规定的主时钟动态分配机制为BMCA(Best Master Clock Algorithm,最佳主时钟选择算法)。系统上电唤醒之后,系统所有设备都可以通过发送一条报文(Announce Message)来参与主时钟竞选,报文中含有各自设备的时钟信息。每一个参选设备都会比较自己的时钟信息和其它设备的时钟信息,并判断是否具有优势,如果不具有,则退出竞选,直到综合能力最强的武林盟主诞生
note:车载系统中一般主设备和从设备的关系一般都是确定的,所以大部分时候不需要BMC算法来找Master,一般系统都可以直接指定主设备、从设备
TSN:
TSN的确定性和实时性优势是建立在精确的时间同步基础之上,而TSN中用于实现精确时间同步的协议是IEEE 802.1AS,也就是业界常说的gPTP
gptp的开源代码实现:
avnu-gptp : GitHub - Avnu/gptp
avnu-gptp是avnu开源的gptp协议栈,主要基于c++代码实现,通过pipe来分享时间信息
linux-ptp : https://git.code.sf.net/p/linuxptp/code
liunxptp既支持ptp也支持gptpd,本文重点以该代码实现为主
同步过程:
gPTP定义有两类报文,事件类型报文(包括Sync、Pdelay_Req、Pdelay_Resp三条)和一般类型报文(包括Follow_UP、Pdelay_Resp_Follow_UP二条)。gPTP定义设备工作在网络七层模型中的第二层数据链路层的MAC(Media Acess Control,媒介访问控制)子层。
当设备MAC层接收或发送事件类型报文时,会触发对硬件计数器进行采样,从而获得时钟振荡周期计数值,结合时钟振荡频率及基准时间,可获得此时的时间戳。而一般类型报文仅用来携带信息,不会触发内部硬件计数器的采样操作
图示:

如果没有网络传输延迟或延迟、可以忽略,则从端口将本地时钟值加上时钟偏差(t1-t2的值)就完成时间同步,也就没有后面的碎碎念了。但是对于μs级时间同步精度的gPTP来说,传输延迟显然无法视若不见
gPTP采用P2P(Peer to Peer)的方法来测量传输延迟。在P2P方法中,测量的是相邻设备间的传输延迟,报文不允许跨设备传输,这也就要求gPTP网络内的所有设备都需要支持gPTP功能。同时定义一组独立的报文专门负责传输延迟测量,分别为周期发送的Pdelay_Req、Pdelay_Resp和Pdelay_Resp_Follow_UP

从端口首先发送Pdelay_Req报文,标志传输延迟测量的开始,在报文离开从端口MAC层时,触发从端口记录此时的时间戳t3。主端口MAC层收到Pdelay_Req报文后会记录此时的时间戳t4,随后,主端口通过Pdelay_Resp报文将值t4发送给从端口,同时在Pdelay_Resp报文离开主端口的MAC层时,触发主端口记录此时的时间戳t5,从端口MAC层收到Pdelay_Resp报文后记录此时的时间戳t6。随后,相同的套路,主端口通过Pdelay_Resp_Follow_Up报文将值t5发送给从端口。至此,一次传输延迟测量过程已经结束。在假设路径传输延迟是对称的前提下,可由如下公式计算相邻设备间的传输延迟。

上文的传输延迟测量是基于从端口与主端口的时钟振荡频率一致的前提下得到的。现在我们考虑一下如果主从端口时钟振荡频率不一致的时候,会导致什么灵异事件发生。
假设从端口的时钟振荡频率是25MHz,主端口的时钟振荡频率是100MHz,这个时候参考时钟本身的频率比=4,但主从端口的rate ratio还是为1。这意味着从端口在收到一次振荡的时候,原有时间会加40ns,而主端口则加10ns。 如果这个时候从端口由于温度、老化等原因,振荡频率变为26MHz,从端口收到一次振荡的时候再加40ns,显然是不准确的。
而频率同步就是要解决主从端口晶振精度不准带来的误差,通过测量报文可以发现此时rate ratio不为1,则从端口可以基于这个值调整自己的时基了

频率同步复用传输延迟测量过程的Pdelay_Resp和Pdelay_Resp_Follow_UP报文。通过采用两组答复,最终可以获得t5,t6,t9,t10的值,由下面公式可得主从端口的频率比

PTP构成的网络各节点称为时钟节点,协议定义了如下三类:
OC(ordinary clock)只有一个PTP通信端口,一般指最终设备
BC(boundary clock)具有多个PTP端口的时钟,通过其中一个从端口与主时钟同步,并支持其他从端口与自身同步
TC(transparent clock)可测量PTP报文在其内部传输的时间,并在转发时提供相应的矫正
E2E(End-to-End)通过Delay_Request和Delay_Response报文进行延迟计算的一种方法
P2P(Peer-to-Peer)通过Pdelay_Req和Pdelay_Resp报文进行延迟计算的一种方法
P2P和E2E对比:
E2E中有个非常关键的假设:既双向链路对等,但是这个假设是针对整个网络架构而言的,如果网络较大,中间交换设备较多那这个假设容易出现问题,换言之,如果缩小规模,甚至对单条链路假设对称更为靠谱,这就是E2E算法基本原理。
P2P的算法机制要求网络中每台交换机都运行P2P算法,也就是网络中不能存在普通交换机,因为普通交换机无法识别和相应Pdelay报文,无法测量它与周边设备的链路延迟,将导致全网时钟计算出现偏误。如果系统中已经存在大量已购的普通交换机,采用E2E是更好的选择。
非常关键,一般称之为Servo。其收敛速度以及能达到的精度是关键指标。相比而言,PTP协议栈的实现很简单
linuxptp支持的servo算法如下:
enum servo_type
{
CLOCK_SERVO_PI, ----默认servo算法,类似PI控制器的线性调整算法
CLOCK_SERVO_LINREG, ----使用线性回归的自适应控制器
CLOCK_SERVO_NTPSHM,
CLOCK_SERVO_NULLF,
};
(1)传输延时测量方式
gPTP仅支持P2P的传输延时测量方式,PTP除了支持上文提到的P2P方法,还支持E2E(End-to-End)方法。在E2E方法中,测量的是网络中任意两个支持PTP设备之间的传输延迟,而在这两个设备之间允许存在普通交换机等可以透传PTP报文的设备。这导致P2P和E2E方式在如下方面存在差异。
(a)测量精度:P2P方法中,报文在桥接设备的驻留时间可以被测量,且会和传输延时时间一同发给后面链路上的设备,故测量精度可控且足够高。E2E方法中,报文在普通交换机的驻留时间具有随机性且不可测量,导致测量精度不可控且波动范围大。
(b)架构灵活性:P2P方法中,测量报文不跨设备传输,主时钟变化或新增从时钟,仅对物理上相邻的设备有影响,有利于网络拓展;E2E方法中,无论主时钟变化还是从时钟变化,都需要重新测量整个网络的传输延迟,且在网络比较复杂时,网络开销会比较大,因此网络拓展性较差
(2)时间戳采样方式
gPTP只能工作在MAC子层,PTP除了可以工作在MAC子层,还可以工作在传输层。工作在传输层时,报文要经历协议栈缓存、操作系统调度等过程,这两个过程都会带来传输延时的增加且大小不可控。而工作在MAC子层时,离物理层只有一步之遥,既能减缓协议栈缓存带来延时的不确定性,又能缩短报文传输延时。
工作在MAC子层时,报文要么直接发给物理层要么从物理层收到,因此时间戳可以选择由物理层硬件打或由软件打。通过硬件的方式打,可以消除操作系统调度带来的延时不确定性。PTP工作在MAC子层时,既支持硬件打时间戳,也支持软件打时间戳。而gPTP从延时可控,延时减少的角度考虑,只允许打硬件时间戳。
(3)时钟类型
PTP时钟支持两种时钟类型,One-Step Clock和Two-Step Clock。在One-Step Clock中,事件报文发送时,同时将本身记录的时间戳发送给从端口,如下图左半部分所示。在Two-Step Clock中,事件报文不携带时间戳信息,需要一条专门的一般类型报文来给从端口发送时间戳,如下图右半部分所示。
One-Step Clock虽然可以比Two-Step Clock节省一条报文,但对硬件要求很高,且硬件成本高,不利于网络扩展和应用普及。在两者精度没有区别的前提下,Two-Step Clock类型显然是gPTP的更优选择,这也是gPTP协议里规定的类型

Ethernet Header:
Destination MAC 8bytes 固定值01:80:c2:00:00:0e,链路层广播地址
Source MAC 8bytes 具体设备地址
Type 2bytes 0x88f7为gptp
gPTP报文由3部分组成:
Header详情:

body:取决于gPTP 报文类型messageID
报文头中的messageID标识了gPTP报文的类型,类型取值范围如下图所示:

ethtool -T eth0:

1) 软件时间戳需要包括参数
SOF_TIMESTAMPING_SOFTWARE
SOF_TIMESTAMPING_TX_SOFTWARE
SOF_TIMESTAMPING_RX_SOFTWARE
2) 硬件时间戳需要包括参数
SOF_TIMESTAMPING_RAW_HARDWARE
SOF_TIMESTAMPING_TX_HARDWARE
SOF_TIMESTAMPING_RX_HARDWARE
详情描述见kernel中Documentation/networking/imestamping.txt(该文档中包括很多对于时间类型和获取办法的详细描述),如下:

linuxptp可以编译出来多个bin,我们重点关心如下两个
ptp4l:主要用来计算两个设备之间的误差,频率误差
phc2sys : 主要是把两个时钟进行同步,比如把systime同步到phc时间(ptp hardware clock)
ptp4l代码简述:
1.getopt_long() ----获取配置参数
2.clock_create() ----创建时钟处理,socket创建、初始化等
3.clock_poll() ----实时处理clock过来的相关事件,并结合事件做时间调整等相关处理
由于gptp工作在网络模型第二层,所有通信需要用到以太网链路层的数据收发,所以需要使用socket raw原始数据,ptp则采用常规socket,走组播。
raw socket和常规的udp/tcp socket比较不同,主要操作链路层,不经过网络协议栈,常见的tcpdump/网络流量统计等底层都是走的raw socket,本文挑其中重点函数解析
raw_open函数详解: 创建raw socket相关fd,配置基础特性 
open_socket函数详解:

raw_configure函数:
sk_timestamping_init函数: 
硬件时间戳类型如下:
SOF_TIMESTAMPING_TX_HARDWARE
SOF_TIMESTAMPING_RX_HARDWARE
SOF_TIMESTAMPING_RAW_HARDWARE;
关于时间戳相关选项option的说明: SO_TIMESTAMP/SO_TIMESTAMPNS/SO_TIMESTAMPING

sk_receive函数: 
note:
2.1.1.5 Blocking Read
Reading from the error queue is always a non-blocking operation. To block waiting on a timestamp, use poll or select. poll() will return POLLERR in pollfd.revents if any data is ready on the error queue.
There is no need to pass this flag in pollfd.events. This flag is ignored on request. See also `man 2 poll`.
2.1.2 Receive timestamps
On reception, there is no reason to read from the socket error queue. The SCM_TIMESTAMPING ancillary data is sent along with the packet data on a normal recvmsg(). Since this is not a socket error, it is not accompanied by a message SOL_IP(V6)/IP(V6)_RECVERROR. In this case,the meaning of the three fields in struct scm_timestamping is implicitly defined.
ts[0] holds a software timestamp if set, ts[1] is again deprecated and ts[2] holds a hardware timestamp if set.
我在开发的Rails3网站的一些搜索功能上遇到了一个小问题。我有一个简单的Post模型,如下所示:classPost我正在使用acts_as_taggable_on来更轻松地向我的帖子添加标签。当我有一个标记为“rails”的帖子并执行以下操作时,一切正常:@posts=Post.tagged_with("rails")问题是,我还想搜索帖子的标题。当我有一篇标题为“Helloworld”并标记为“rails”的帖子时,我希望能够通过搜索“hello”或“rails”来找到这篇帖子。因此,我希望标题列的LIKE语句与acts_as_taggable_on提供的tagged_with方法
我有这个代码File.open(file_name,'r'){|file|file.read}但是Rubocop发出警告:Offenses:Style/SymbolProc:Pass&:readasargumenttoopeninsteadofablock.你是怎么做到的? 最佳答案 我刚刚创建了一个名为“t.txt”的文件,其中包含“Hello,World\n”。我们可以按如下方式阅读。File.open('t.txt','r',&:read)#=>"Hello,World\n"顺便说一下,由于第二个参数的默认值是'r',所以这样
如何将自己的字段类型添加到formtastic中?例如,我需要一个自定义的日期时间输入,我想要这样的东西::my_date%>这显然是行不通的,因为formtastic不知道:my_date(只有:boolean、:string、:datetime等等...)但是我怎样才能添加额外的输入类型呢? 最佳答案 您需要添加自定义输入法:classMyCustomFormtasticFormBuilder这非常适合新的HTML5输入类型。你可以这样使用它:MyCustomFormtasticFormBuilderdo|f|%>:my_dat
我有一个Rails应用程序,我正在尝试使用acts_as_list插件设置可排序列表。数据库中的位置字段正在更新,但是在呈现页面时,不考虑顺序。我想我是在寻求帮助。这是我的模型...classQuestionMembership:question_membershipsendclassQuestion:question_membershipsacts_as_listend还有给我列表的草率View代码...>true)%>拖放用于重新排序。数据库中QuestionMembership对象的位置值更新,页面实际上正确显示重新排序。问题是在页面重新加载时,它默认返回到它感觉的任何顺序。我认
所以我正在使用acts_as_taggablegem提供的标签。这些帖子是我正在标记的内容。我怎么能说类似=>的东西(这里是伪代码)ifacollectionofPostshasatagwithacorrespondingStockQuote,displaythestockquote所以现在我有一个acts_as_taggable的Post资源。这是我的帖子索引操作现在的样子:defindex@stock=StockQuote::Stock.quote("symbol")ifparams[:tag]@posts=Post.tagged_with(params[:tag])else@po
我想知道是否有人可以帮助我理解文档中的这一部分:Withthedefinedcontextinmodel,youhavemultiplenewmethodsatdisposaltomanageandviewthetagsinthecontext.Forexample,with:skillcontextthesemethodsareaddedtothemodel:skill_list(andskill_list.add,skill_list.removeskill_list=),skills(plural),skill_counts.我有这个:型号:classProjectControl
我一直在为使用acts_as_list的模型实现一些不错的交互界面,这些界面可以对我的mRails应用程序中的列表进行排序。我有一个排序函数,在每次拖放之后使用sortable_elementscript.aculo.us函数调用并设置每条记录的位置。这是在拖放完成后处理排序的Controller操作示例:defsortparams[:documents].each_with_indexdo|id,index|Document.update_all(['position=?',index+1],['id=?',id])endend现在我正在尝试对嵌套集模型(acts_as_nested
acts_as_taggable_on实现效果很好,但我还需要声明标签别名。我找到了一个声称这样做的插件,acts_as_taggable_with_aliases,但最后一次提交是在2009年并且不在gem存储库中,所以我认为该项目现在已经死了。有什么办法可以实现吗? 最佳答案 也许你可以创建自己的模型来支持这个(以及你想要的任何其他东西)......我认为您可以通过执行以下操作来实现:classTagtrueendclassModelIWantToBeTagged:taggableendmoduleModelTaggingdef
这将是一个非常愚蠢的问题,我只知道这一点,但我还是要问,因为它快把我逼疯了。如何让acts-as-taggable-on起作用?我用geminstallacts-as-taggable-on将它安装为gem因为我似乎永远无法安装插件来工作,但这是另一批问题所有人都可能真的很愚蠢。无论如何,没有问题,它安装正确。我做了rubyscript/generateacts_as_taggable_on_migration和rakedb:migrate,同样没问题。我将acts_as_taggable添加到我想使用标签的模型中,启动了服务器,然后加载了模型的索引,只是为了看看我到目前为止所得到的是
我正在创建一个Rails应用程序,它有一个实现Act_As_Votablegem的User和Post模型.我希望用户能够对帖子进行赞成票和反对票,但也希望通过weighted_score算法对帖子进行排名和排序,该算法考虑了赞成票、反对票的数量和帖子的创建时间。我的weighted_score算法取自Reddit并且描述得更好here.我的帖子模型:classPost0sign=1elsifraw_score我想使用Acts_As_Voteablegem,因为它支持缓存,可以减少硬盘写入次数并节省时间。目前,帖子的weight_score可以即时计算,但不会保存在数据库中,这意味着我无