我们在讨论linux的实时的时候,有时候会拿RTOS来做对比,根据最近对RTOS的理解,总结出来关于为什么linux实时不如RTOS的几个点,分享之
在rtems中我们看的中断在特殊情况如内核态是支持中断嵌套的,但是linux是不允许中断嵌套。
也就是说,在Linux中,如何一个中断触发来,那么必须等到同CPU的上一个中断处理完成。
这时候,中断是不确定的
linux支持中断上下文(hardirq和softirq),我们知道softirq的优先级是高于kthread这类线程任务的(tasklet,timer,netrx等)
如果某个外设驱动的softirq一直占用某个core,那么整个core上的thread很可能都得不到运行
我处理can的报文接收(netrx)的时候就遇到过这类情况,它直接导致来一个cpu core不工作。
那么这也就是另一个不确定性,也就是softirq是外设驱动代码,由客户自己编写的,如果softirq执行时间不确定,那么中断就是不确定的 。
例如某个cpu core上,有一个普通任务正在spin lock,那么core上的任务必须等spin unlock后,才能得以调度。
那么此时其任务就是不确定的。因为谁也没办法知道要等多久才能unlock。这样高优先级就不知道什么时候才能得以运行了。
在linux中内存是使用的时候才分配的,高优先级任务在使用内存的时候,内存分配过程只能保证你分配到,但是不能保证你在确定时间内申请到。 甚至可能没有内存导致oom出现。
基于上面提到的,PREEMPT_RT主要解决如下三个问题
对于中断风暴,做法是在线程内disable irq,线程化完成之后,自动enable irq
而softirq线程化,做法是全部将其添加到ksoftirqd(tasklet,timer,netrx等)执行
spinlock的做法就是将raw_spin_lock 替换成 rt_mutex (可睡眠的,支持优先级继承的mutex,避免优先级翻转)
根据上面就知道
所以可以得出结论,添加PREEMPT_RT的内核,它可以保证某个高优先级任务的一定的实时性,但其负面作用会导致其他所有常规任务性能低下。 所以只适合于定制的特殊场景。
根据上面提到的,使用PREEMPT-RT的内核,一定程度上保持了高优先级任务的实时性,那么基于此,我们还需要做哪些事情来定制操作系统,满足这个实时任务的确定性要求呢?
内核其实提供了EDF调度,也就是SCHED_DEADLINE。我们让这个实时任务在这个特殊的调度类中。
chrt工具动态设置进程的调度政策,如chrt --deadline / --fifo
当然pthread_setschedparam也可以设置调度器种类,如SCHED_DEADLINE/SCHED_FIFO传参
通过bootargs传参隔离某个cpu,然后通过taskset将实时任务绑定到这个cpu上跑
如果要高实时,建议直接汇编使用寄存器调用arm64的高精度定时器来封装特定代码。
对于多线程的实时任务,pthread线程创建前建议设置优先级继承,避免优先级翻转。 也就是pthread_muterattr_setprotocol中传参PTHREAD_PRIO_INHERIT。 其默认是NONE。
默认从内核申请的内存都可能被交换,那么申请的内存调用mlock锁住即可,这样这块内存不会被换出。同时,带mlock标志位的内存会在申请的时候,自动触发缺页异常,分配物理页面。相当于lazy机制的逃逸了。
malloc_trim(-1),这里-1就是trim的最大值,也就是gc不再帮你管理释放内存了,内存不会返回到操作系统中,如果内存通过mmap获得,那么就不会munmap了
堆和栈都是内存,对于堆好办,我们直接mlock即可,对于栈,这是操作系统分配的,我们设置了最大栈大小之后,例如是8M,如果是线程,那么可以设置pthread_attr_setstacksize
在所有函数调用之前,调用一个函数,然后通过局部变量申请一个8M,或者你设置的栈大小的空间,将其占住,然后进行memset一次调用。此时栈会通过缺页异常申请内存,后续就不会在缺页异常申请了。 文字表现力不强,如下是代码
void stack_prefetch() { const size_t size = 8 * 1024 * 1024 - 4096; char buffer[size]; memset(buffer, 0, sizeof(buffer)); }
跑在多核的程序,需要利用好cache一致性,例如局部性原理,但是这里要说的是避免数据伪共享,所以多线程访问同一个结构体时,最好cacheline对齐
__attribute__((aligned(cachesize)))
其他就是性能优化相关的了,通过性能优化可以保证任务的低时延。
本文初衷是介绍一下Linux实时内核,并说明Linux实时并不能硬实时的原因,但是为了表述清楚,也补充了使用PREEMPT-RT补丁的内核,和实时应用程序的编写建议,这些建议纯粹属于经验分享性质,不代表权威信息。
总之,PREEMPT-RT是基于linux内核上,为了保证某个实时任务的确定性做的优化,它需要配合该实时任务的很多其他措施一起使用,否则体现不了实时的确定性。 也就是说我们讨论实时任务的时候,应该讨论整个链路的实时性,而不是某一个方面的实时确定性,那没有任何意义
并且,PREEMPT-RT是牺牲整体系统性能为代价而提供的,使用时需要认清利弊。
PREEMPT-RT做不到完全的硬实时确定性,所以如果面临产品级的开发,推荐使用Linux + RTOS 双OS的策略,RTOS本身关注确定性任务,Linux本身关注复杂计算。