周末抽空想了想数据回放的时间戳问题,想到了一个比较有意思的方案,就是使用虚拟时间。所以就尝试采用多进程共享虚拟时间的方式来劫持系统时间戳,尝试满足数据回放过程中的功能代码对系统时间戳需求。

  • 一般而言,像ROS或者DDS的消息数据都是带有时间戳的,比如录制时间戳,消息内部header中保存的数据源时间戳或者数据对应的有效时刻的时间戳等等
  • 对于实际生产环境中的功能逻辑代码,可能会依赖于系统时间戳来进行一些逻辑判断,比如超时判断,定时器;又或者对不同来源(不同topic、不同帧率)的时间戳的消息进行时间戳对齐或者插值等等,这些都是对时间戳的依赖。而如果软件没有对获取时间戳的接口进行封装,那么在采用类似于Pub/Sub的方式对数据回放的时候,就会出现问题,因为数据回放的时间戳是不可控的,可能会导致功能逻辑的错误。
  • 对于单进程的数据回放程序,可以使用消息录制时的时间戳来作为虚拟的仿真系统时间戳,这样的话,原有的代码逻辑就可以直接使用这个时间戳来进行仿真
  • 对于多进程节点,数据通过类似于DDS Pub/Sub的方式进行传输,那么就需要对时间戳进行额外的处理;对系统时间戳进行劫持,然后通过共享内存的方式来共享虚拟时间戳是一个看上去能走通的方案(当然,未必是最优的)

实现的思路非常简单,那就是可以根据消息对应的录制时间戳和消息内部的时间戳(其实一般前者应该就够了),来更新虚拟的时间戳,并将这个虚拟时间戳通过共享内存的方式共享给其他进程。这样的话,其他进程就可以通过读取共享内存的方式来获取虚拟时间戳。

该虚拟时间戳,可以通过劫持libc中的gettimeofdayclock_gettime等函数来实现,这样的话,就可以在不修改原有代码的情况下,来实现对系统时间戳的劫持;这可以通过将动态库指定到LD_PRELOAD的方式来实现。

具体原型代码实现可见 Virtual timestamp。该实现仅为虚拟时间戳多进程更新和共享的原型验证,实际应用中还需要考虑更多的问题,比如定时器回调的处理,进程异常退出后共享内存的释放等等。

其实ROS在设计中是采用 /clock topic 来发布时间戳的(ROS 2 Design: Clock and Time),这个主要受到网络通信延时的影响。共享内存的方式有可能会更快,不过没有考虑多机通信的情况。一般回放数据的时候,都是在一个机器上进行的,所以这个问题应该不大;要是真的有多机通信的需求,也估计还得靠消息传递的方式来实现。

时间戳在回放过程中是个比较重要的问题,这影响到功能逻辑的正确性。例如Timestamps and rosbags: discussing an alternative to clock and use_sim_time这里就谈到了录制消息时间戳乱序的问题,里面提到需要对文件进行后处理,来保证时间戳的顺序性。所以关于回放过程中的时间戳问题,还是要根据具体的应用场景来进行设计。

参考

  1. Virtual timestamp
  2. ROS 2 Design: Clock and Time
  3. Timestamps and rosbags: discussing an alternative to clock and use_sim_time