1.实时系统的概念1.1什么是实时操作系统?各种RTOS(实时操作系统)的应用。其中最具代表性的是国外的μC/OS-III、FreeRTOS、Vxworks等,国内的代表是RT-Thread和LiteOS。图1:常见的实时操作系统。在这众多的RTOS系统中,既有开源的,也有商业的,也有一些是行业专用的。例如,enea公司推出的OSE系统,广泛应用于通信行业早期的基站设备。系统。无论是开源的还是商业的,这类系统都有一个最显着的特点,那就是它们都具有很高的实时性。也正因为这个特点,它们一直集中在嵌入式领域,尤其是工业控制领域,如工业制造控制、导弹飞行器导航、电力设备监控等。历史上许多著名的航空航天设备都使用实时操作系统。比如登陆火星的凤凰号和好奇号火星车,它们使用的操作系统是美国WindRiver公司推出的Vxworks。那么系统的实时性如何呢?Linux系统在嵌入式领域也有广泛的应用。Linux系统支持实时性能吗?1.2Linux实时性、软实时性和硬实时性是指操作系统能够在指定的时间点内完成指定的任务操作。一旦超过这个时间点,将会给整个系统带来不可估量的后果。与此相对的是通用操作系统,它更注重用户体验,偶尔的系统卡顿不会给用户带来灾难性的后果。实时性反映了系统行为控制的精确能力,具体体现在定时器的高精度、及时的中断响应、固定的、可预测的系统行为。Linux系统最初是按照分时系统设计和推出的,历史版本使用的调度算法的目的是为了公平分配和使用各种系统资源,保证CPU被各个进程公平使用,所以早期不支持实时。但是在后来的2.6版本中,加入了内核抢占的功能,提高了它的实时性,在一定程度上具备了软实时能力。软实时是指系统对时间限制不是很严格,在某些情况下系统允许超过限制。比如我们在PC上用鼠标操作的时候,偶尔会出现卡顿的延迟。这种情况除了把我们逼疯,影响用户体验外,不会对我们造成严重影响。大家在使用电脑的时候,通常都会遇到问题。一个例子是鼠标图标转圈,界面没有响应操作。又如视频信号采集,偶尔会丢失几帧数据,不会对视频的最终播放造成严重的画面丢失。但是硬实时不同。它对操作系统的行为有严格的时限要求。超过期限往往会导致灾难性的后果。比如我们日常生活中使用的汽车,都装有安全气囊。在发生剧烈碰撞时,汽车可能会在0.2s内停止,因此要求安全气囊在0.02s内充气弹出。危及生命。另一个例子是导弹防御系统。当敌方导弹来袭时,拦截系统必须做出100%准确的反应,计算弹道进行拦截。稍有延迟就会导致拦截失败。这种后果是不可接受的。这就是硬实时系统和软实时系统的区别。但是,由于Linux系统内核过于庞大,模块较多,内核中影响实时性的因素仍然很多,如大量自旋锁的使用、中断禁止、时钟粒度等,远达不到美国水平的控制精度。距离。但不能断定Linux系统以后不能用于实时控制领域。事实上,Linux内核一直在发展。在历史版本的主线上,仍然有不少科技公司或大牛在努力提高Linux系统的实时性。他们要么在某个版本上发布实时补丁,要么对内核进行某些更改。具体代表有RTLinux、RTAI(Real-TimeApplicationInterface)和Xenomai。RTLinux的全称是AReal-TimeLinux,由新墨西哥矿业技术学院的V.Yodaiken开发。RTLinux采用双核方式,可以说是首创。简单理解就是系统中有两个核心,实时核心和非实时核心。处理底层硬件资源,实时内核绕过非实时内核,来自硬件的中断源完全被实时内核接管,非实时Linux内核运行为实时内核上的低优先级进程。实时内核上的中断和任务按优先顺序进行响应,从而提高实时性能。Xenomai也借鉴了RTLinux的双核方式,也有实时内核和非实时内核之分。但不同的是,Xenomai在底层硬件和两个核心之间增加了一层硬件抽象层ADEOS(AdoptiveDomainEnvironmentforOperatingSystem)。实时内核和非实时内核作为硬件抽象层的两个域存在。Xenomai内核属于实时域,Linux内核属于非实时域。ADEOS拦截系统关键路径的中断,优先响应Xenomai实时域的中断。当没有实时任务和中断需要处理时,就会轮到Linux内核执行。两者的比较如下。图2:RTLinux和Xenomai框架对比由于版权和技术专利等因素,RTLinux不再更新。Xenomai对开发者比较友好,因为它注重可扩展性、可移植性和可维护性。它仍在发布补丁,并且在社区中非常活跃。在工控领域也有很多成功的应用案例。这些基于Linux系统提高实时性能的系统的出现,使得在Linux系统的开发过程中对实时性能的研究有增无减。甚至还有很多人从Linux系统的实时研究入手,深入学习Linux内核的调度机制、中断机制、定时器机制等,逐渐发展成自己的兴趣爱好.从我们作为一个普通技术群体的实力来看,我们可能没有能力像国外的公司或者一些技术组织那样做大的改动,发布实时的Linux。如果我们要提高系统的实时性,有的是出于自己工作的需要,有的则是结合个人爱好研究。那么如果我们想要提高Linux内核的实时性,应该怎么做呢?2.Linux实时性能优化2.1实时优化与时钟精度知其然,知其所以然,了解影响实时性能的因素,才能对其进行很好的优化改造。目前影响Linux内核实时性的因素主要有时钟精度、系统中断、进程调度算法、内核可抢占性等。每一块都可以深入研究并相应地优化。首先是时钟精度。时钟就像系统的脉搏,系统进程的调度和切换是根据时钟的节拍进行的。目前Linux内核支持几种不同的系统节拍,用户可以在编译内核时进行配置。假设当前系统节拍为100Hz,则系统时钟粒度为10ms。如果提高到1000Hz,则时钟粒度为1ms,精度提高10倍。图3:编译内核系统节奏设置上图是CentOS7.6发行版系统默认的系统节奏设置。时钟精度提高最直接的影响就是系统中的调度动作加快,进程响应更加及时,但是时钟中断的频率也加快了,这也增加了系统的开销和压力,因为会频繁响应系统时钟中断。在一些耗CPU的进程中,由于系统频繁的进程切换,会造成CPU资源的浪费,CPU时间也会浪费在进程切换上,因为进程切换和实际调度执行是有时间差的,称为一个进程switchingoverhead,好坏我们还是要根据自己的系统性能来看。2.2中断中断之后。无论是RTOS还是Linux,硬件中断在系统中的响应优先级总是最高的。由于RTOS支持中断优先级,在实际使用中可以根据产品的实际情况,针对不同的外设场景设置不同的优先级,高优先级的中断可以抢占低优先级的中断,使得RTOS的中断响应速度非常快。Linux系统中的中断模块比RTOS系统复杂得多。通常,当Linux系统进入中断时,它会禁用本地CPU的中断。在处理特定的中断时,由于本地CPU中断(NMI类型中断除外)被禁止,当有新的中断到来时,它不得不挂掉。只有处理完当前中断,才打开本地中断,响应新的中断。.如果系统中存在大量不同类型的中断,则难免有些中断会延迟,不能及时响应。这种延迟现象在单核CPU上尤为明显。针对这种情况,要求我们在编写实际的中断处理函数时,尽量不要在中断处理函数中进行复杂的操作,坚持中断处理函数“快进快出”的原则。例如只读硬件寄存器等简单操作就足够了,其余的数据处理操作都在中断的下半部分进行。这就是所谓的“中断线程”。传统上,中断的下部有软中断、tasklet、工作队列,它们的优先级也是从高到低。软中断和tasklet不允许在中断上下文中休眠,它们的优先级高于工作队列。工作队列允许在进程上下文中休眠但是优先级最低,所以在实际的编程开发中,我们需要根据场景有选择性地使用。如果是多核CPU,那么可以根据实际的中断情况,将不同类型的中断迁移绑定到不同的CPU上,避免不同中断之间的干扰。下面以I.MX6DL硬件平台为例介绍中断迁移的使用。首先通过#cat/proc/interrupts命令查看所有系统中断,如下图:图4:绑定中断前的系统中断分布从上图可以看出,目前硬件平台共有4个CPU。其中IMX-uart和imx-i2c的中断集中在CPU0和CPU3。从第一列我们可以知道它们的中断号分别是58和69。接下来,我们可以将所有IMX-uart中断迁移到CPU1,让CPU0只响应imx-i2c中断。迁移可以通过#echo"2">/proc/irq/58/smp_affinity命令完成。注意这里的数字“2”表示CPU编号,从1开始。迁移后的IMX-uart中断如下:图5:绑定中断后的系统中断分布从前后两张图的对比可以看出,在中断迁移前,IMX-uart的中断数在CPU3是1824次,而CPU0上的中断次数是727次。中断迁移后,CPU0和CPU3不再响应IMX-uart中断,CPU1上IMX-uart中断数从15个增加到115个。我曾经在双核硬件平台上遇到串口和SPI需要对接同时响应大量中断。它们相互影响,导致中断响应不及时,导致数据丢失和输出不及时。最后通过绑定中断核解决了。事实上,中断绑定内核并不能完全消除对实时性能的影响,只能将中断对进程的影响降到最低,因为系统中的NMI中断和本地时钟中断无法迁移和禁用。除了中断能够绑定内核之外,应用层进程和线程也可以改变它们与CPU的亲和力。例如,迁移到中断较少的内核,也可以在一定程度上提高进程的实时性。2.3进程调度算法除了上面提到的时钟和中断,还有一个对实时性影响最大的因素就是操作系统的调度算法。Linux系统目前默认使用完全公平调度算法(CFS),根据每个进程的权重来分配运行时间。在默认使用CFS的情况下,我们可以分配更高的优先级和权重,可以看作是通过赋予更高的优先级来获得更好的实时性。Linux内核目前支持多种调度类。每个调度类都是同类型调度策略的集合。目前支持的调度类有:stop、deadline、realtime、CFS、idle。通常,进程可以选择上述调度类,它们的优先级从高到低排序。其中,deadline调度类的可选调度策略为SCHED_DEADLINE,realtime调度类的可选调度策略为SCHED_FIFO、SCHED_RR、CFS。可选的调度类是SCHED_NORMAI、SCHED_BATCH和SCHED_IDLE。在实际开发过程中,如果实时进程对时间要求比较严格,可以选择deadline调度类。其他情况可以参考相应的调度策略。改变调度策略是最直接的优化方法。deadline调度类的用法如下:图6:deadline调度策略编程参考2.4内核的其他限制除了内核本身的调度算法外,Linux内核的调度模块还有其他的限制因素。例如,为了防止某个进程或某个进程组长时间占用CPU时间,Linux内核引入了“运行带宽”的概念,即某个进程或进程组使用的总时间CPU不能超过这个“带宽”阈值,默认值为0.95s。图7:“RunBandwidth”的默认值在实际开发过程中,为了提高我们进程的实时性,进程需要长期占用CPU资源。我们可以禁用这个“运行带宽”。事实上,不同的产品和使用场景会有不同的优化措施。如果设备的CPU核数较多,我们可以整体规划系统的CPU使用率。可以使用大量的中断核和进程线程绑定核来实现CPU的独占。例如,DPDK是Intel公司开发的高性能网络加速组件。在Linux内核中,传统的网络数据包是通过网卡驱动和内核协议栈来收发的。网卡驱动也针对大数据包场景做了很多应对,但本质上,网络包的收发都是在内核中进行的。还是要靠系统给我们实现一个好的软中断机制。DPDK使用轮询而不是中断。它绕过了Linux内核的网络模块(驱动和协议栈),不需要频繁拷贝数据,让用户空间可以直接看到硬件网卡的数据,有了很大的提升。减少了数据传输距离的开销。下图是使用轮询和DPDK独占CPU的例子。图8:DPDK轮询独占CPU又如用于中断收集分配的irqbalance,会根据系统的负载自动进入性能模式和节能模式,将中断平均分配给不同的CPU处理。在特殊情况下,我们需要禁用此功能。还有其他的比如禁止软锁,优化虚拟内存管理等。从上面可以看出,实时优化的方法多种多样,甚至用轮询代替中断。但关键是我们需要了解Linux内核模块的一些运行机制,以及这些机制在实现过程中存在的先天缺陷。只有这样才能针对具体问题做出相应的优化措施。上述方法只是从一个大的方向分析介绍了Linux内核的实时影响因素和相应的优化措施,非常简单。总结起来有以下几点:中断优先级高,要减少中断对进程的影响;进程之间有优先级,合理改变优先级;内核模块的实现机制有限,特殊情况下使用旋转训练;系统节拍,提高计时精度;禁止irqbalance,防止节能或睡眠模式;还有其他更深入的细节我们没有深入分析,比如Linux进程切换耗时、中断响应耗时、内存分配开销以及普通定时器精度和高精度定时器精度等等,这些是都是从细分模块的方向研究和优化的。进程切换涉及到主调度器和周期调度器,这其中必然涉及到定时器。目前linux内核的高精度定时器很难达到us级别,这就决定了调度时间也是有限的。不是那么准确,而且内核中填充了大量的自旋锁,使用自旋锁会关闭CPU中断,影响实时性,所以我们在开发过程中进一步提高了自己的水平要求.3.总结上面列出的优化措施实现起来非常简单,但更重要的是我们要明白为什么要这样做。只有深入剖析Linux内核的机制,真正阅读内核的模块源码,才能实时收获更多或者Linux内核的学习路径。比如,只有阅读内核源码,才能知道tasklet和workqueue的不同应用场景。虽然它们都是“中断的下半部分”之一,但它们在内核中的执行优先级还是有很大区别的。只有读过内核源码才会知道tasklet和hrtimer也是基于softirqs的,而这个softirq也是有优先级的。只有阅读内核源码才能知道系统目前实现了多少个软中断,甚至我们可以实现软中断来提高实时性,虽然Linux内核不建议我们这样做。除了阅读内核的源码外,还需要掌握调试和跟踪内核的工具,如ftrace、trace-cmd、kernelshark、perf等。俗话说,工欲善其事工欲善其事,必先利其器。掌握这些工具的使用,会让我们更高效地优化工作。ftrace是一个非常强大的调试工具。除了常用的函数跟踪器可以让我们轻松知道一个函数的执行时间,其强大的事件机制可以让我们直接在驱动程序或内核中添加跟踪点。输出内核执行过程中的各种数据,方便我们深入了解内核的执行情况。总之,实时优化是一条漫长的路,一路上充满了未知。越深入,就越好奇,越觉得Linux内核神奇。例如,您可能想知道Linux内核中的进程唤醒并实际执行该进程需要多长时间。再比如外部中断,从被触发到真正走到用户注册的中断处理程序,用了多长时间。只有你到设备上去调试,想办法搞清楚这些问题,你才会发现Linux内核有趣的地方,才会越来越有成就感。以上只是我在工作中积累的一些意见。如果读者在面对Linux内核时不知从何下手,个人建议不妨从实时优化方向入手,逐步深入。作者简介:一线码农邓士强,从事通信行业。他目前在一家通信公司担任内核工程师。他喜欢每天研究和学习Linux内核知识。
