关于Linux转实时低延迟处理运动实时控制的探讨及解决方案

xingyun86 2021-4-23 1982

近日有项目需要极高的实时性对物理硬件进行网络控制,这就需要操作系统能精确到ms甚至us即时发送指令。但是普通Linux的实时性经过测试可以去到1ms(无任何其他实时任务或者中断)。如果稳定的话,使用也是ok的,不过好奇心使然,wiki了解了多种实时性解决方案。最终总结了两种可行且不受任何架构影响的解决方案。

  • 非Linux阵营:VxWorks,RTEMS
    Linux阵营 :RT-linux,Preempt-rt,WindRiver Linux,RTAI,Xenomai。
  • RT-linux:应该是第一批实时性linux,可以提供硬实时,有两个版本,免费版和收费版,收费版最初由 F

    SMLabs(Finite State Machine Labs)公司开发,在2007年卖给了 WindRiver。之后 WindRiver 将其作为自己的产品 WindRiver Linux 的一个实时核心(real-time core)象征性的维护了一段时间,与2011年8月彻底停止更新和维护。RT-linux 的性能应该很好,WindRiver 应该是出于竞争的目的对其进行了收购,现在 RT-linux 已经不再更新,因此对于我们也只能当做了解一段历史了。

    WindRiver Linux:WindRiver 为 Linux 世界提供的一个发行版,具备软实时的能力,应该吸收了一部分 RT-Linux 的核心技术,感觉是 WindRiver 为了延长其产品线,提高行业竞争力和覆盖面而开发的系统,还没进行深入分析,毕竟是软实时。

    Preempt-rt:Ingo Molnar 引领的 Linux kernel 实时 patch,可以改善通用 Linux 的实时性,也是本项目前期工作的重点。

    RTAI:(Real-Time Application Interface)项目应该继承自 RT-linux,后期吸收了 Xenomai 的一部分思想,专注实时性能,应用广泛。

    Xenomai:2001 年 8 月由 Philippe Gerum 发起,其思想是来源于 Karim 的 ADEOS(Adoptive Domain Environment for Operating System)。发布后即被 RTAI 采用,并一度合并为 RTAI/Fusion。后于2005年独立。Xenomai 的实时性能比RTAI略差,因为其完全由 ADEOS 控制中断,而 RTAI 是由其内核对中断进行了截断,非实时的中断才交给 ADEOS,这就减少了一部分实时开销。

  • 双核(如RTLinux,RTAI,Xenomai)实时方案。运作一个real-time核心,然后将修改过的GNU / Linux核心程序码视为该实时核心的空闲任务。

    常用的双内核法实时补丁有 RTLinux/GPL、RTA 和 Xenomai,其中RTLinux/GPL只允许以内核模块的形式提供实时应用;而RTAI和Xenomai支持在具有MMU保护的用户空间中执行实时 程序。

1.RT-PREEMPT kernel内核补丁

PREEMPT_RT是Linux内核的一个实时补丁。得到Linus的高度评价:
Controlling a laser with Linux is crazy, but everyone in this room is
 crazy in his own way. So if you want to use Linux to control an 
industrial welding laser, I have no problem with your using PREEMPT_RT."
 -- Linus Torvalds

适用于所有linux内核。架构amd64/I586/arm/arm64都确认有ok。(debian,其它系列可能需要自行编译)

其实时原理:

Linux kernel在SpinLock、IRQ上下文方面无法抢占,因此高优先级任务被唤醒到得以执行的时间并不能完全确定。同时,Linux kernel本身也不处理优先级反转。RT-Preempt Patch是在Linux社区kernel的基础上,加上相关的补丁,以使得Linux满足硬实时的需求。

RT-Preempt Patch对Linux kernel的主要改造包括:
 Making in-kernel locking-primitives (using spinlocks) preemptible though reimplementation with rtmutexes:
 Critical sections protected by i.e. spinlock_t and rwlock_t are now 
preemptible. The creation of non-preemptible sections (in kernel) is 
still possible with raw_spinlock_t (same APIs like spinlock_t)    
Implementing priority inheritance for in-kernel spinlocks and 
semaphores. For more information on priority inversion and priority 
inheritance please consultIntroduction to Priority Inversion    
Converting interrupt handlers into preemptible kernel threads: The 
RT-Preempt patch treats soft interrupt handlers in kernel thread 
context, which is represented by a task_struct like a common userspace 
process. However it is also possible to register an IRQ in kernel 
context.
    Converting the old Linux timer API into separate 
infrastructures for high resolution kernel timers plus one for timeouts,
 leading to userspace POSIX timers with high resolution.

 可抢占的关键部分
可抢占的中断处理程序
可抢占的“中断禁用”代码序列
内核中的自旋锁和信号量的优先级继承
延期经营
延迟减少措施

在“可调度/线程”上下文中执行所有活动(包括IRQ),IRQ处理程序移到线程中执行。

优先级继承:是让握有自旋锁或信号量的进程可以暂时的提高优先权让他可以尽快做完关键部分释放自旋锁或信号量,高优先的流程才有办法继续执行。

应用开发示例:https://rt.wiki.kernel.org/index.php/HOWTO:_Build_an_RT-application#Application

2.Xenomai双内核

Altenberg 透露了他的 Xenomai 对 RTL 延迟测试的结果。Altenberg说:“有很多论文声称 Xenomai 和 RTAI 的延迟比 PREEMPT.RT 更小。但是我认为大部分时候是 PREEMPT.RT 配置不好的问题。所以我们带来了一个 Xenomai 专家和一个 PREEMPT.RT 专家,让他们配置自己的平台。”

Adeos是一段相当简单的代码,在正确使用时具有非常有趣的属性。Adeos方案的主干是事件管道,因此,它带来了我们在Xenomai中需要的所有关键功能:

    可预测的中断延迟;
    精确的中断虚拟化控制(每域和每中断处理程序注册,每域和每个CPU中断屏蔽);
    事件的统一,优先和面向域的传播方案;
    一种通用且简单的API,可简化客户端代码的可移植性。

Xenomai使用这些功能来寻求最佳的Linux内核实时服务集成。Xenomai的主要模式可在最短的微秒延迟范围内提供真正的实时性能。此外,Xenomai对未来的Linux演进进行了投注,如PREEMPT_RT,以提高内核的整体粒度,以便辅助模式在确定性意义上仍然是实时的,尽管有更高的最坏情况延迟。

改变整个系统架构新增一个调度员与IRQ管理的机制,让处理实时任务流程简化到只剩ipipe-> scheduler就能执行,不会因linux庞大的架构影响到实时任务的处理时间。

Xenomai 实时内核为开发强实时应用提供了丰富的功能,主要包括实时线程调度与管理、用户空间实时任务支持、线程同步服务、时钟服务、中断服务、动态内存申请和实时对象注册服务等。

Xenomai 是一种采用双内核机制的Linux 内核的强实时扩展。由于Linux 内核本身的实现方式和复杂度,使得Linux 本身不能使用于强实时应用。在双内核技术下,存在一个支持强实时的微内核,它与Linux 内核共同运行于硬件平台上,实时内核的优先级高于Linux 内核,它负责处理系统的实时任务,而Linux 则负责处理非实时任务,只有当实时内核不再有实时任务需要处理的时候,Linux 内核才能得到运行的机会。
Xenomai 基于Adeos(Adaptive Domain Environment for Operating System)实现双内核机制


从xenomai3开始支持两种方式构建linux实时系统,分别是cobaltmercury

cobalt :添加一个实时核,双核结构,具有实时内核cobalt、实时驱动模型RTDM、实时应用POSIX接口库libcobalt,基于libcobalt的其他API skins,如Alchemy APIVxWorks® emulatorpSOS® emulator等。

mercury :基于直接修改linux内核源代码的PREEMPT RT,应用空间在glibc之上,添加xenomai API库,如下图所示。在不支持cobalt内核时,可使用该方法运行xenomai应用;


Xenomai 在Adeos 系统中的域优先级高于Linux 域,每当中断到来之后,Adeos先调度Xenomai 对该中断进行处理、执行中断相应的实时任务,只有当Xenomai没有实时任务和中断需要处理的时候,Adeos 才会调度Linux 运行,这就保证了Xenomai的中断响应速度和实时任务不受Linux 的影响,从而提供了实时系统的可确定性。 

下面简要介绍几类常用的Xenomai 原生API: 
a.任务管理 
Xenomai 本身提供的一系列多任务调度机制,主要有以下一些函数: 
intrt_task_create (RT_TASK *task, const char *name, int stksize, int prio, intmode) ; 任务的创建; 
int rt_task_start(RT_TASK *task, void(*entry)(void *cookie), void *cookie) ;  开始任务调度; 
intrt_task_suspend (RT_TASK *task);              挂起任务; 
intrt_task_delete (RT_TASK *task) ;                删除任务; 
intrt_task_set_periodic (RT_TASK *task, RTIME idate, RTIME period) ;设置任务运行周期; 
intrt_task_wait_period (unsigned long *overruns_r) ;挂起任务到下个周期再运行; 
intrt_task_set_priority (RT_TASK *task, int prio);设置任务优先级; 
b.内存堆服务 
intrt_heap_create (RT_HEAP *heap, const char *name, size_t heapsize, int mode) 创建一个内存堆空间或一个共享内存片段; 
intrt_heap_delete (RT_HEAP *heap)  删除一个内存堆空间或一个共享内存片段; 
int rt_heap_bind(RT_HEAP *heap, const char *name, RTIME timeout)  绑定共享内存空间; 
intrt_heap_unbind (RT_HEAP *heap)  接触共享内存空间的绑定; 
c.信息管道服务 
intrt_pipe_create (RT_PIPE *pipe, const char *name, int minor, size_t poolsize) 创建通讯管道; 
intrt_pipe_delete (RT_PIPE *pipe)  删除通讯管道; 
ssize_t rt_pipe_receive  (RT_PIPE  *pipe,  RT_PIPE_MSG **msgp,  RTIME  timeout) 从管道接受一条信息; 
ssize_trt_pipe_send (RT_PIPE *pipe, RT_PIPE_MSG *msg, size_t size, int mode)  向管道发送一条信息; 
Xenomai 在实时内核之上还提供了多组API 模拟多种不同的实时操作系统和编程规范,包括POSIX、VxWorks 和RTAI 等。这使得实时应用系统的开发和移植变得非常方便。
 

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

#include <alchemy/task.h>

RT_TASK hello_task;

// 任务执行的功能函数
void helloWorld(void *arg)
{
  RT_TASK_INFO curtaskinfo;

  printf("Hello World!\n");

  // 询问当前的任务
  rt_task_inquire(NULL,&curtaskinfo);

  // 打印出任务的名字
  printf("Task name : %s \n", curtaskinfo.name);
}

int main(int argc, char* argv[])
{
  char  str[10];

  printf("start task\n");
  sprintf(str,"hello");

  /* Create task
   * Arguments: &task,
   *            name,
   *            stack size (0=default),
   *            priority,
   *            mode (FPU, start suspended, ...)
   */
  rt_task_create(&hello_task, str, 0, 50, 0);

  /*  Start task
   * Arguments: &task,
   *            task function,
   *            function argument
   */
  rt_task_start(&hello_task, &helloWorld, 0);
}



×
打赏作者
最新回复 (0)
只看楼主
全部楼主
返回