版权声明:本文为本文为博主原创文章,转载请注明出处。如有问题,欢迎指正。博客地址:https://www.cnblogs.com/wsg1100/

一、问题起源

何为漂移?举个例子两颗32.768kHz晶振\(C_1\)和\(C_2\),由于制造工艺原因或者使用时温度、辅助元件参数等影响,与他们的实际频率一定不是相同的,与32.768kHz有不同的偏差,假如\(C_1\)实际使用时频率32.766kHz,\(C_2\)实际频率32.770kHz。

假如有那么两个电子手表,使用32.768kHz晶振,每来一个脉冲寄存器计数加1,我们通过这个电路来获取时间,这样计算1s的时间寄存器里应该是32.768kHz(我们认为我们的晶振是没问题的嘛)。好现在用\(C_1\)来计数就会使我们得到的时间比真实1S长\(\frac{1}{2000}=0.0005\)秒,这样下来这个手表会越走越快,即与真实时间的偏移越来越大。同样\(C_2\)得到的时间比真实1S短\(0.0005\)秒,越走越慢。

两个手表在它们的计时周期(这里举例1秒)存在的偏差就是漂移。

X86平台上,linux 4.4.xx之后的版本构建的xenomai,出现linux内核xenomai内核两者时钟存在漂移,打xenomai补丁之后,有两个内核分别有各自的时间子系统,只不过xenomai掌管着底层的硬件timer-event的中断触发设置和处理,linux时间子系统的触发源就退化为xenomai时间子系统管理的软件timer了(linux是xenomai的idle任务嘛,当然要xenomai来提供时钟),本质上它们还是使用同一个硬件timer源。

此问题解决时本人还未阅读xenomai的时间子系统相关源码,所以其中有些解释现在看起来·只见树木不见森林·,懒得改了,关于xenomai的时间子系统后续会有分析文章,敬请关注!!!。

构建xenomai系统后,linux内核与xenomai内核两个时间子系统之间的时间漂移可通过xenomai库编译出的工具clocktest来查看,其中的dirft列就是表示该cpu上两个内核之间的时间漂移。如下:

回到问题本身,xenomai来常用来运行ethercat主站,主站DC模式下同步运行时,出现的现象是主站本地时间永远无法与参考时钟同步,导致每周期主站都需要读回参考时钟进行调整;

下面分析问题:主站由xenomai实时调度,ethercat主站工作过程中使用的是xenomai时间子系统根据底层硬件timer计算得到的时间,ethercat主站在这个时间上去同步参考时钟,增加或减少偏移量。先不管硬件timer与真实时间的偏移,非常小先忽略,这不是重点,两个内核都使用这个硬件timer,现在出现的问题是两个内核对同一硬件timer的度量不一致,才会存在漂移。

由此可以推断出xenomai时间子系统对硬件timer的度量计算有问题,下面开始从一步步挖掘分析。

二、 clocktest工具分析

clocktest工具主要用于测试xenomai 时钟(CLOCK_REALTIMECLOCK_MONOTONICCLOCK_MONOTONIC_RAWCLOCK_HOST_REALTIME、coreclk默认CLOCK_REALTIME),相对于Linux绝对时钟CLOCK_MONOTONIC之间的漂移,clocktest首先为每个CPU创建一个线程cpu_thread,并固定到相应CPU上执行,cpu_thread测试原理为:

  1. 找一个时间点作为测试起始点,此时xenomai时间表示为first_clock,Linux绝对时钟时间表示为first_tod,它们均为一个数,单位纳秒ns。

  2. 让测量任务睡眠,睡眠时间是一个范围的随机数,睡眠范围为:[1000000, 200000)纳秒(这里的睡眠时间至少1000000是因为读取linux时钟的函数(SYS_gettimeofday)精度只能读取到us级,而xenomai读取到的时间为ns级,为了使差距与us对齐,所以至少经过1ms,简而言之计算周期1ms单位的漂移)。

  3. 读取睡眠后的各自时钟的计数值,读取xenomai时钟读取到的值为clock_val,Linux时间计数值为first_tod同样的时间段,xenomai时钟计数为clock_val-first_clock,Linux时间计数值tod_val-first_tod;这个时间段的偏移率为:

    \[\frac{clock\_val-first\_clock}{tod_val-first_tod}
    \]

三、 读linux时钟时间

在clock工具中读取Linux参考时钟时间使用系统调用syscall(SYS_gettimeofday, &tv, NULL);

系统函数do_gettimeofday()读取全局时钟timekeeper的值xtime_sec,然后加上系统上一个tick到此时的纳秒数nsecensece是直接调用timekeeper使用的clocksouse对应的读函数读取clocksouse counter计算得到的,也可看到底层读取到的精度是纳秒级的,只不过在上一个函数将精度丢弃了。

四、 读xenomai时钟时间

在clock工具中读取xenomai时钟时间的函数是static inline uint64_t read_clock(clockid_t clock_id);

read_clock()函数调用系统调用函数clock_gettime(),这是一个POSIX标准函数,在xenomai中kernel\xenomai\posix\clock.c实现如下:

clock_gettime()继续调用__cobalt_clock_gettime()来获取时钟,在colocktest中传入的参数是:CLOCK_REALTIME。进而调用内核函数xnclock_read_realtime (struct xnclock *clock)读取时间,再通过ns2ts函数将读取的到的纳秒转换为需要的timespec结构体中的tv_sectv_nsec

xnclock_read_realtime返回 nkclock的时间加上一个与wallclock的偏移(clock->wallclock_offset), (nkclock是xenomai的时钟源,类型为struct xnclock,当没有使用外部时钟时,时钟使用X86处理器中的TSC时钟(早期X86CPU中TSC与CPU的频率有关,现在的CPU TSC频率一般是固定的),当使用外部时钟作为xenomai的时钟时是另外一回事)。

xnclock_read_monotonic()最终调用xnclock_core_read_monotonic()函数:

xnclock_core_read_monotonic()函数中由xnclock_core_ticks_to_ns()函数将xnclock_core_read_raw()函数返回的TSC CPU tick数转换为纳秒ns返回,这就是读取的xenomai时钟时间。怎样获取CPU的TSC值呢?在X86处理器中有一条指令rdtsc用于读取TSC值 。

到这,整个xenomai时间读取流程完了,就是读取TSC的值,没其他的了,看似没有什么问题。难道真的x86中的TSC不准?注意到读取的TSC数值还需要转换才能得到时间,转换函数xnarch_llmulshft(),涉及到这两个变量tsc_scale,tsc_shift,怀疑是这两个值有问题,继续分析,那这两个值是干嘛用的?

当已知频率F,要将A个cycles数转换成纳秒,具体公式如下:

\[转换后的纳秒数 =\frac{A}{F}*1000000000
\]

这样的转换公式需要除法,绝大部分的CPU都有乘法器,但是有些处理器是不支持除法,虽然我们无法将除法操作的代码编译成一条除法的汇编指令,但是也可以用代码库中的其他运算来取代除法。这样做的坏处就是性能会受影响。把1/F变成浮点数,这样就可以去掉除法了,但是又引入了浮点运算,kernel是不建议使用浮点运算的。解决方案很简单,使用移位操作,具体可以参考clocksource_cyc2ns的操作:

通过TSC的xnclock_core_read_raw()函数获取了tick数目,乘以mult这个因子然后右移shift个bit就可以得到纳秒数。这样的操作虽然性能比较好,但是损失了精度(通过另外验证下面代码算出的值,以这台机器的2700M算出的值,带入2700Mcycle得1000000230ns,有200纳秒左右的偏移),还是那句话,设计是平衡的艺术,看你自己的取舍。

tsc_scale,tsc_shift在哪里计算的呢?

具体计算在xnarch_init_llmulshft()函数中计算:

如何获取最佳的tsc_scaletsc_shift组合?当一个公式中有两个可变量的时候,最好的办法就是固定其中一个,求出另外一个,然后带入约束条件进行检验。我们首先固定shift这个参数。mult这个因子一定是越大越好,mult越大也就是意味着shift越大。当然shift总有一个起始值,我们设定为32bit,因此tsc_shift从31开始搜索,看看是否满足最大时间范围的要求。如果满足,那么就找到最佳的multshift组合,否则要tsc_shift递减,进行下一轮搜索。

先考虑如何计算mult值。根据公式(cycles * mult) >> shift可以得到ns数,由此可以得到计算mult值的公式:

\[mult=\frac{ns<<shift}{cycles}
\]

如果我们设定ns数是10^9纳秒(也就是1秒)的话,cycles数目就是频率值。因此上面的公式可以修改为:

\[mult=((10^9<< freq))
\]

看看上面的公式,再对照代码,一切就很清晰了。

那到这自然也就想到,这个freq值就是TSC对应的CPU的频率值了。那上面的函数是由谁调用计算的?CPU的频率值freq在哪获取的?这些值在xenomai核初始化时计算。

五、xenomai xnclock初始化

在xenomai核启动函数xenomai_init()中,由mach_setup()函数完成xenomai域相关定时器、中断、时钟设置。在mach_setup()函数中首先调用ipipe_select_timers()从全局timers链表中为每一个CPU选择一个具有最高评级的clock_event_device作为该cpu的percpu_timer。而timers是每一个clock_event_device 在register的时候, 由 ipipe_host_timer_register()将该clock_event_device添加到链表timers上的。

当一个CPU找到一个合适的clock_event_device的时候,就回调用install_pcpu_timer()设置该clock_event_device为该CPU的percpu_timer,并配置该ipipe_timer频率与CPU频率的转换因子(参数c2t_integ,c2t_frac, ipipe_timer_set中用到,而ipipe_timer_set常被__xnsched_run调用,推测与任务时间片计算有关,有时间再来分析),同时设置CPU的ipipe_percpu.hrtimer_irq为该timer的中断号。而这里使用的 CPU频率值为__ipipe_hrclock_freq,他的定义如下;

接着mach_setup()中调用ipipe_get_sysinfo(&sysinfo)获取系统的信息,系统online的cpu数,cpu的频率,这个频率也是使用上面cpu_khz的值。另外将0号cpu的hrtimer_irq作为系统hrtimer的中断号,并且设置sys_hrtimer_freq的频率(这个频率是具体percpu_timer的频率lapic-timer或者HPET),同样sys_hrclock_freq也是cpu_khz的值。这些信息在下面的初始化中要用到。

接下来将cobalt_pipeline.timer_freq设置为timerfreq_arg也就是sysinfo中的sys_hrtimer_freq

cobalt_pipeline.clock_freq为sysinfo中的那个cpu_khz得来的sys_hrclock_freq

下面注册两个虚拟中断到ipipe,一个为cobalt_pipeline.apc_virq,链接到root_domain,处理linux挂起恢复。另一个为cobalt_pipeline.escalate_virq,注册域为xnsched_realtime_domain,handler为__xnsched_run_handler,一看这就是与xenomai调度相关的。

下面就是这里主要的初始化xnclock的xnclock_init()函数了。

需要注意的是xnclock_init()函数传的参数是cobalt_pipeline.clock_freq,也就是那个cpu_khz得来cobalt_pipeline.clock_freq

首先第一步做的就是求转换因子tsc_scale, tsc_shift,执行xnclock_update_freq(),到这就是熟悉的xnarch_init_llmulshft(1000000000, freq, &tsc_scale, &tsc_shift),在上面的分析中提到过,下面去看cpu_khz在哪里获取到的。其他的不在这里不分析。

![image-20200702191733579](D:\文档\源码笔记\xenomai blogs\实时内核与linux内核时钟漂移过大原因.assets\image-20200702191733579.png)

六、 TSC init

\arch\x86\kernel\tsc.c

![image-20200702191841659](D:\文档\源码笔记\xenomai blogs\实时内核与linux内核时钟漂移过大原因.assets\image-20200702191841659.png)

这里很明了了,cpu_khtsc_khz是两个值,而xenomai使用cpu_khz去算tsc与纳秒的转换因子,如果cpu_khtsc_khz相等那没有问题,但通过添加调试输出,这两个值是不等的。

![image-20200702191953017](D:\文档\源码笔记\xenomai blogs\实时内核与linux内核时钟漂移过大原因.assets\image-20200702191953017.png)

![image-20200702191959548](D:\文档\源码笔记\xenomai blogs\实时内核与linux内核时钟漂移过大原因.assets\image-20200702191959548.png)

刚好下面的条件判断没有对tsc_khzcpu_khz不相等做处理。

![image-20200702192026940](D:\文档\源码笔记\xenomai blogs\实时内核与linux内核时钟漂移过大原因.assets\image-20200702192026940.png)

这台机器上导致计算tsc_scale, tsc_shift时使用的是2700Mhz,而TSC的频率是2712MHZ,用2700MHZ得来的tsc_scale, tsc_shift去转换2712MHZ产生的cycles当然不对,每秒就会有12M的漂移,也就是每周期漂移\(\frac{12MHZ}{2700MHZ}=0.004444444\)秒,转换为(微秒/秒)就是\(4444.4444(us/s)\).与机器上实际测试相符:

解决办法:

tsc_khz不为0时,直接 cpu_khz=tsc_khz

修改后clocktest测试如下:

附:xenomai内核的一些时钟信息:

$cat /proc/xenomai/clock/coreclok
gravity: irq=100 kernel=1341 user=1341
devices: timer=lapic-deadline, clock=tsc
status: on
setup: 100
ticks: 443638843357 (0067 4aef87dd)

修改后:

$cat /proc/xenomai/clock/coreclok
gravity: irq=99 kernel=1334 user=1334
devices: timer=lapic-deadline, clock=tsc
status: on
setup: 99
ticks: 376931548560 (0057 c2defd90)

lapic-deadline 是上面解析的CPU0 的percpu_timer,deadline表示lapic-timer支持deadline事件触发;

关于xenomai的时间子系统后续会有整理分析文章,敬请关注!!!。

后面一直没有使用4.14及以上的内核,不知道现在还有没这个问题。大家可以看看,再提个issue或者啥的.....

【原创】解BUG-xenomai内核与linux内核时间子系统之间存在漂移的更多相关文章

  1. Android内核和Linux内核的区别

    1.Android系统层面的底层是Linux,并且在中间加上了一个叫做Dalvik的Java虚拟机,从表面层看是Android运行库.每个Android应用都运行在自己的进程上,享有Dalvik虚拟机 ...

  2. 内核操作系统Linux内核变迁杂谈——感知市场的力量

    本篇文章个人在青岛游玩的时候突然想到的...今天就有想写几篇关于内核操作系统的博客,所以回家到以后就奋笔疾书的写出来发表了 Jack:什么是操作系统? 我:你买了一台笔记本,然后把整块硬盘彻底格式化, ...

  3. Linux内核分析——Linux内核学习总结

    马悦+原创作品转载请注明出处+<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 Linux内核学习总结 一 ...

  4. 【内核】linux内核启动流程详细分析

    Linux内核启动流程 arch/arm/kernel/head-armv.S 该文件是内核最先执行的一个文件,包括内核入口ENTRY(stext)到start_kernel间的初始化代码, 主要作用 ...

  5. 【内核】linux内核启动流程详细分析【转】

    转自:http://www.cnblogs.com/lcw/p/3337937.html Linux内核启动流程 arch/arm/kernel/head-armv.S 该文件是内核最先执行的一个文件 ...

  6. 【内核】Linux内核Initrd机制解析,内核更新步骤,grub配置说明

    什么是Initrd initrd的英文含义是 boot loader initialized RAM disk,就是由boot loader初始化的内存盘.在 linux内核启动前, boot loa ...

  7. 《Linux内核--分析Linux内核创建一个新进程的过程 》 20135311傅冬菁

    20135311傅冬菁 分析Linux内核创建一个新进程的过程 一.学习内容 进程控制块——PCB  task_struct数据结构 PCB task_struct中包含: 进程状态.进程打开的文件. ...

  8. /var/log目录下的20个Linux日志文件功能详解 分类: 服务器搭建 linux内核 Raspberry Pi 2015-03-27 19:15 80人阅读 评论(0) 收藏

    如果愿意在Linux环境方面花费些时间,首先就应该知道日志文件的所在位置以及它们包含的内容.在系统运行正常的情况下学习了解这些不同的日志文件有助于你在遇到紧急情况时从容找出问题并加以解决. 以下介绍的 ...

  9. [内核同步]Linux内核同步机制之completion

    转自:http://blog.csdn.net/bullbat/article/details/7401688 内核编程中常见的一种模式是,在当前线程之外初始化某个活动,然后等待该活动的结束.这个活动 ...

随机推荐

  1. C#LeetCode刷题之#566-重塑矩阵( Reshape the Matrix)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3720 访问. 在MATLAB中,有一个非常有用的函数 resha ...

  2. BLE GAP 协议和 GATT 协议

    BLE GAP 协议和 GATT 协议 最近要打算学习 Blufi 协议进行蓝牙配置,其中必然使用 GAP 协议和 GATT 协议,于是进行重新学习一番. BLE 是一个 Bluetooth SIG ...

  3. Jdk1.7下的HashMap源码分析

    本文主要讨论jdk1.7下hashMap的源码实现,其中主要是在扩容时容易出现死循环的问题,以及put元素的整个过程. 1.数组结构 数组+链表 示例图如下: 常量属性 /** * The defau ...

  4. axios 常用的几个方法

    Vue推荐使用axios 发送网络请求:最近重新开始做Vue项目,重新回归一下.从axios的几个方法开始吧. 1. 安装: 既然是Vue项目,我还是选择常用的npm的方式 $ npm install ...

  5. 实验室外的攻防战 UOJ#180 [树状数组]

    实验室外的攻防战 UOJ#180 [树状数组] 题目 时针指向午夜十二点,约定的日子--2月28日终于到来了.随着一声枪响,伏特跳蚤国王率领着他的跳蚤大军们包围了 \(picks\) 博士所在的实验室 ...

  6. Eclipse怎么切换工作空间

    1.进行点击Eclipse编辑代码的窗口界面中,进行点击菜单中file的选项. 2.弹出了下拉菜单中进行选择为“switch workspace”的选项. 3.弹出了下一级菜单中进行选择为other的 ...

  7. vue+leaflet

    1.index.html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...

  8. GPU虚拟机创建时间深度优化

    ​桔妹导读:GPU虚拟机实例创建速度慢是公有云面临的普遍问题,由于通常情况下创建虚拟机属于低频操作而未引起业界的重视,实际生产中还是存在对GPU实例创建时间有苛刻要求的业务场景.本文将介绍滴滴云在解决 ...

  9. antd-vue中table行高亮效果实现

    [方式一]:通过设置customRow达到目的,点击时遍历所有行设置为正常颜色,把当前行设置为特殊颜色(高亮色) HTML: <a-table ref="table" siz ...

  10. ORB-SLAM: A Versatile and Accurate Monocular SLAM System 笔记(二)

    4. 自动地图初始化 地图初始化的目标是两个帧之间相对位姿来三角化一系列的点云(riangulate an initial set of map points),这个操作是独立与场景且不需要人为的干预 ...