近日有项目需要极高的实时性对物理硬件进行网络控制,这就需要操作系统能精确到ms甚至us即时发送指令。但是普通Linux的实时性经过测试可以去到1ms(无任何其他实时任务或者中断)。如果稳定的话,使用也是ok的,不过好奇心使然,wiki了解了多种实时性解决方案。最终总结了两种可行且不受任何架构影响的解决方案。
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实时系统,分别是cobalt 和 mercury。
cobalt :添加一个实时核,双核结构,具有实时内核cobalt、实时驱动模型RTDM、实时应用POSIX接口库libcobalt,基于libcobalt的其他API skins,如Alchemy API、VxWorks® emulator、pSOS® 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);
}