首页 > 产品中心 > 自有产品

x86 Linux 下完成 10us 差错的高精度延时 软件开发

作者:欧宝体育电竞官网入口 信息来源:欧宝体育app入口 发布日期:2021-08-19 03:38:55 点击次数: 20

  在 Linux 下完成高精度延时,网上所能找到的大部分办法只能完成 50us 左右的延时精度。

  在 Linux 下完成高精度延时,网上所能找到的大部分办法只能完成 50us 左右的延时精度。今日让咱们来看下嘉友创信息科技的董文会是怎么处理这个问题的,将延时精度提升到 10us。

  最近在开发一个项目,需求用到高精度的延时机制,规划需求是 1000us 周期下,差错不能超过 1%(10us)。

  由于项目硬件计划是用英特尔的 x86 处理器,了解 Linux 硬件的人都知道这个很难完成。其时评价计划时分有些草率,直接采用了 “PREEMPT_RT 补丁+内核 hrtimer+信号告知” 的方法来评价。其时验证的成果也很满足,所以兴冲冲的告知领导说计划可行,殊不知自己挖了一个巨大的坑

  ◈ 信号告知只能告知到进程,而现在已知的计划无法做到被告知的进程中无其他线程。这样高频的信号发过来,其他线程基本上都会被干掉。(补偿阐明:这儿特指的是内核驱动告知到使用层,在用户层中是有专门的函数能够告知不同线程的。并且这个问题经过研讨,能够经过设置线程的 sigmask 来处理,可是仍旧无法改动计划行不通的定论)

  ◈ 这也是首要原因,项目中需求用的 Ethercat 的同步周期尽管能够在程序开始时固定,可是实践运转时的运转周期是需求动态调整的,调整规模在 5us 以内。这样一来,动态调整 hrtimer 的开支就变得无法疏忽了,换句话说,咱们需求的是一个延时机制,而不是定时器。

  已然信号方法不行,那只能经过其他手法来剖析。总结下来我大致进行了如下的测验:

  测验过usleep、nanosleep、clock_nanosleep、cond_timedwait、select等,终究确认用clock_nanosleep,选它的原因并不是由于它支撑 ns 等级的精度。由于经过测验发现,上述几个调用在周期小于 10000us 的状况下,精度都差不多,差错首要都来自于上下文切换的开支。选它的首要原因是由于它支撑TIME_ABSTIME选项,即支撑肯定时刻。这儿举个简略的比方,解释一下为什么要用肯定时刻:

  假定上面这个循环,咱们意图是让do_post的履行以 1s 的周期履行一次,可是实践上,不行能是肯定的 1s,由于sleep()只能延时相对时刻,而现在这个循环的实践周期是do_work的开支 +sleep(1)的时刻。所以这种开支放在咱们需求的场景中,就变得无法忽视了。而用clock_nanosleep的优点便是一方面它能够挑选时钟源,其次便是它支撑肯定时刻唤醒,这样我在每次do_work之前都设置一下clock_nanosleep下一次唤醒时的肯定时刻,那么clock_nanosleep实践履行的时刻其实就会减去do_work的开支,适当所以闹钟的概念。

  将重要使命的线程改成实时线程,调度战略改成 FIFO,优先级设到最高,削减被抢占的或许性。

  对使用下一切的线程进行规划,依据负载状况将几个负载比较重的使命线程别离绑定到不同的 CPU 核上,这样削减切换 CPU 带来的开支。

  由于许多使命都存在sleep调用,我用strace指令剖析了整个体系中使用sleep调用的份额,高达 98%,这种高频次休眠+唤醒带来的开支势必是不行疏忽的。所以我将main循环中的sleep改成了循环等候信号量的方法,由于 pthread 库中信号量的等候使用了futex,它使得唤醒线程的开支会小许多。其他当地的sleep也尽或许的优化掉。这个作用其实比较显着,能差不多削减 20us 的差错。

  图片是用 Python 对抓包东西的数据进行剖析生成的,参考性不必质疑。纵轴代表实践这个周期所消耗的时刻。能够发现很有意思的现象:

  2. 差错不是正态分布,而是频频呈现在 30us 左右的当地

  3. 每次发生较大的差错时,下个周期必定会呈现一次反向的差错,并且起伏大致相同(这点从图上看不出来,经过其他手法剖析的)。

  简略描绘一下便是假定这个周期的履行时刻是 980us,那下个周期的履行时刻必定会在 1020us 左右。

  第 1 点和第 2 点能够经过上面的 4 条优化办法消除,第 3 点没有找到十分有用的手法,我的了解或许内核对这种差错是知晓的并且有意在补偿,假如有知道相关背面原理的大神欢迎共享一下。

  针对这个第三点古怪的现象我也测验做了手动的干涉,比方设置一个阈值,当实践程序履行的差错大于这个阈值时,我就在设置下一个周期的唤醒时刻时,手动减去这个差错,可是运转作用却大跌眼镜,更差了

  在测验了 200 屡次参数调整,被这个问题卡了一个多礼拜之后,偶尔发现了一篇戴尔的技能文档《Controlling Processor C-State Usage in Linux》,遭到这篇文章的启示,总算处理了这个难题。

  经过软件中止 CPU 内部主时钟;总线接口单元和 APIC 依然坚持全速运转

  经过软件中止 CPU 内部主时钟并下降 CPU 电压;总线接口单元和 APIC 依然坚持全速运转

  经过硬件中止 CPU 内部主时钟;总线接口单元和 APIC 依然坚持全速运转

  经过硬件中止 CPU 内部主时钟并下降 CPU 电压;总线接口单元和 APIC 依然坚持全速运转

  当程序运转的时分,CPU 是在 C0 状况,可是一旦操作体系进入休眠,CPU 就会用 Halt 指令切换到 C1 或许 C1E 形式,这个形式下操作体系假如进行唤醒,那么上下文切换的开支就会变大!

  这个选项按道理 BIOS 是能够关掉的,可是坑的当地就在于版别相对较新的 Linux 内核版别,默许是敞开这个状况的,并且是无视 BIOS 设置的!这就很坑了!

  针对性查找之后,发现网上也有网友测验,2.6 版别的内核不会默许敞开这个,可是 3.2 版别的内核就会敞开,并且比照测验发现,这两个版别内核在相同硬件的状况下,上下文切换开支能够相差 10 倍,前者是 4us,后者是 40-60us。

  能够经过向/dev/cpu_dma_latency这个文件中写值,来调整 C1/C1E 形式下上下文切换的开支。我挑选写0直接封闭。当然你也能够挑选写一个数值,这个数值就代表上下文切换的开支,单位是 us。比方你写1,那么便是设置开支为 1us。当然这个值是有规模的,这个规模在/sys/devices/system/cpu/cpuX/cpuidle/stateY/latency文件中能够查到,X 代表详细哪个核,Y 代表对应的 idle_state。

【关闭】 【打印】