王 娜, 李彦峰,2, 孙菲艳, 汪 辰
(1 金陵科技学院 南京软件研究院, 南京 211169; 2 中国科学院 软件研究所, 北京 100190)
Linux操作系统以其开源、可移植的优势,以及稳定和支持多种处理器结构的特点受到诸多嵌入式产品开发者的青睐,成为嵌入式领域发展最快的操作系统。作为一个通用的操作系统,Linux本身也对实时性做了一定的考虑,但是仍有很多不足。
目前针对Linux的实时化改造,业界已推出多种方案。其中的实时抢占补丁(PREEMPT_RT patch)即因其简洁设计以及与内核主线的一致性,获得工业控制领域的广泛关注,并且PREEMPT_RT补丁(以下简称PREEMPT_RT)对Linux的实时化改造正在不断地被吸收融入到Linux主线中,成为Linux实时化改造的主流设计理念。
中断管理的高效、可靠是系统设计的关键部分之一,如果设计不良则可能造成不必要的硬件等待,使得整个任务处理出现延滞。PREEMPT_RT引入了中断线程化的思路,改善由于中断处理导致的内核延迟的问题。目前很多研究实验均已表明中断线程化可有助于减少内核的抢占延迟,从而整体提升实时任务的处理性能。但中断线程化对于中断延时的影响,现有研究结论却并不一致。一部分研究推证出中断线程化可以降低中断延时[1],另外有些研究则指出,中断线程化会导致中断延时的增加[2]。综上论述可知,本文将基于PREEMPT_RT对中断线程化的实现进行分析,并据此研发提出了一种中断延迟时间的测试方法,接下来又基于TI AM3358处理器的开发板进行测试验证,得出了中断线程化对中断延迟时间影响的结论,同时还设计提出了实时中断和非实时中断的概念,对基于PREEMPT_RT内核的驱动开发具有较强的指导意义。
处理硬件中断是造成内核响应延迟的一个重要原因。分析原因可知,硬件处理某一个中断的过程中,其它中断是被阻塞的。传统Linux内核处理中断的过程,从系统检测到一个中断开始,一直到中断的处理程序运行结束,处理中断的CPU一直处于关中断的状态, 整个过程中断和任务均无法得到响应,造成中断响应时间、任务调度时间不确定。
针对这一问题,Linux系统引入了中断上半部、底半部的概念。当运行于中断底半部程序时,中断被使能,这在一定程度上就减少了系统关中断的时间,但就效果改观而言却极为有限。首先,系统处理底半部中断时,仍然不支持高优先级任务的调度,增加了任务调度时间的不确定性。其次,底半部的设计思想语意含糊,在系统中常常作为一种改进建议,而不是强制规范。事实上,一般用户很难清晰地界定哪部分是中断服务的上半部分,哪部分是底半部分,因此用户完全可能将其绕过,就使得中断编程的随意性增大,难以达到满意的综合设计效率[2]。通过以上分析可知,系统运行中断的过程中,中断响应和任务调度的时间均存在较大的不确定性,受到中断任务本身工作量的干扰,运行时间无法得到保证,因而难以满足实时性的要求。
PREEMPT_RT延续了中断上半部、底半部的设计思路,提出了中断线程化的概念,将中断事件集合中的绝大部分置于内核线程中进行处理。通过唤醒线程使中断得以执行,其结果是增强了内核的可抢占性,使得实时任务能够获得及时处理。
中断线程化的核心思想就是将中断任务尽可能地放到内核线程中去完成,原则上,可以将中断处理的全部工作均投放至线程中去,但也有例外,譬如对于共享中断线的一些处理,就必须定制在硬中断上下文中实现,研究中将这部分的处理称作快速检测处理程序,而将中断过程中除此之外的处理称作中断主处理程序。整个中断处理花费的时间,将主要消耗在中断主处理程序上,所以中断主处理程序的运行时间决定了系统关中断的时间。研究推得伪代码可参见表1。由表1可知,中断线程化前的代码结构中,快速检测处理程序和中断主处理程序都是在硬件中断上下文中被执行的,这个过程中系统处于关中断状态。中断线程化之后,中断主处理程序执行被放入到内核线程中去处理,硬件中断上下文中原定执行中断主处理程序的部分现在却只需完成唤醒中断线程这一个动作,大大减少了系统的关中断时间。
表1中断线程化前后中断处理伪代码
Tab.1Interruptprocessingpseudo-codebeforeandafterthreadedinterrupt
中断线程化前中断线程化后on each IRQ reacheddohard_cli();IRQ_handler(); 快速检测处理程序; 中断主处理程序;hard_sti();doneon each IRQ reacheddohard_cli();IRQ_handler(); 快速检测处理程序; 唤醒中断线程;hard_sti();done……irq_thread: 中断主处理程序;
中断线程在内核中的默认设置是实时优先级为50,调度策略为SCHED_FIFO的实时内核线程。这样一来,在中断线程的运行过程中,如果有优先级大于50的实时进程进来,中断线程的处理被挂起,系统转而去执行实时任务,从而保证了实时任务的及时运行,增加了系统的确定性。
对于被线程化的中断,系统接收到中断后,并不是直接进入中断服务函数,而是通过设置调度标志告知系统,当中断线程被实际调度,此时才能真正开始执行中断处理函数。另外,中断线程的运行还有可能会被更高优先级的实时任务打断。相应地,对于被线程化的中断来说,自身的优先级就被降低了。因此,中断线程化实质上是降低了中断自身的响应速度,减少了系统的关中断时间,在提高系统可抢占性的同时,也一并优化了系统的实时性能。
通过原理分析可以看出,中断线程化使得中断自身的优先级被降低,考虑到有些中断的响应必须及时(例如:系统的时钟中断),Linux抢占内核对中断线程化也给出了灵活处理,增加了IRQF_NODALY标志位,用户在申请某一个中断时,如果设置了此标志位,则此中断将被禁止中断线程化。这样的设计相当于给中断赋予了优先级,将系统的中断分为实时中断和非实时中断。具体来说,实时中断可以通过设置IRQF_NODALY实现,运行在关中断状态的中断上下文; 非实时中断则可做到中断线程化,运行在开中断状态,并且可以被抢占的进程上下文。
基于中断线程化的原理分析可知,从系统整体性能来看,中断线程化减小了系统关中断时间,增加了内核的可抢占性,从而降低了系统的抢占延迟时间,但是对于被线程化的中断本身,其处理优先级却被降低,于无形中就增加了中断的延迟处理时间。目前,对于Linux内核的抢占延迟时间的测量,已经可以见到较为成熟的测试工具。诸如,rttest包中的cyclictest用于测试系统抢占延迟时间即已得到广泛应用,但是,对于中断延迟时间的测试,还没有一个可以供研究者直接调取的通用测试程序。在此情况下,本文即研发提出了一种简单方便的方法用来测试中断延迟时间。对此可做探讨论述如下。
中断延迟时间是指,从中断被触发开始到中断处理函数的第一条指令执行所经历的时间。本文提出了一种测试中断延时的方法,选取系统中的一个定时器硬件,通过计算定时器中断的响应延迟,获取系统的中断延时时间。如图1所示,设置定时器的间隔时间为period,在ts时刻启动定时器,那么定时器中断理论上应该在ts+period时刻触发,事实上,由于中断延迟的存在,实际的中断执行时间要迟于理论计算时间,假设在th时刻,中断处理程序的第一条指令被执行,那么中断延迟时间ti就可以通过计算得来。计算公式的数学表述如下:
ti=th-ts-period
(1)
图1 测试原理
首先选取一个定时器硬件,本文选用TI开发板的Dmtimer3硬件作为定时器的中断触发源。然后设置此定时器每隔100 us触发一个中断,设置中断到时处理函数。在定时器启动时获取中断触发时间ts,定时器中断到来的中断处理函数的第一句代码中获取中断到来时间th,两者的差值即为中断延时时间,此后重新使能定时器,开始下一轮中断,循环测试。测试程序主要代码可详见如下。
//定时器使能函数irq_enalbe
omap_dm_timer_start(timer_ptr);
//获取定时器启动时间
do_gettimeofday(&ts);
……
//定时器中断处理函数
do_gettimeofday(&th);
//计算本次中断响应延迟时间
diff=timeval_sub(th,ti)-period;
//重新使能定时器,开始下一轮的测试
irq_enable()
实验基于TI AM3358处理器开发板设计展开,此开发板使用ARM Cortex-A8核心,主频为800 MHz,内存采用512 M的DDR3内存。实验使用的非实时测试环境内核原始版本为linux-3.2.0,抢占内核版本在原始版本基础上打上PREEMPT-3.2.0-rt10补丁。
为了更加直观地探察中断线程化对系统的影响以及对中断自身的影响,使用本文推介的中断延迟测试方法,设计构建了2类实验。内容阐释如下。
(1)对系统影响实验:设计一个中断负载,分别在Linux内核和PREEMPT_RT内核上运行负载,统计此时的中断延时时间,分析中断线程化对系统关中断时间的影响。
(2)对中断自身影响:分别在Linux原生内核和PREEMPT_RT内核下创建定时器中断,测试中断延迟时间,分析中断线程化对中断自身的影响。
实验基本原理可描述为:为了验证中断线程化对系统关中断时间的影响,需要在系统中人为创建中断,因此开发了一个负载程序。负载被设置为一个内核模块,该内核模块每隔100 us触发一次中断,一次中断处理的时间大概在50 us左右,当此负载存在的情况下,新增定时器中断,计算定时器中断的延迟时间,通过这一指标就可以验证系统的关中断时间。
分别在非实时Linux内核和实时抢占内核中进行实验,将各自运行20 000次,并将实验结果使用二维点阵图绘制出来,运行结果如图2所示。
结果显示,加中断负载后,非实时内核的中断最小延时48 us,最大延时63 us,平均延时54.9 us。实时抢占式内核的中断最小延时6 us,最大延时35 us,平均延时10.9 us,在中断延时时间上较非实时内核要占据明显优势,进而证实了中断线程化减少了关中断的时间,从而可以改善系统的中断延时时间,增加内核的整体可抢占性。
(a) 非实时内核中断延时时间
(b) 实时内核中断延时时间
实验基本原理可描述为:分别在Linux内核和PREEMPT_RT内核创建定时器中断,计算中断延时时间,验证中断线程化对中断自身的影响。
实验将在Linux内核和PREEMPT_RT内核的2种内核环境下分别运行20 000次,获取各次的中断延时时间,并将测试结果使用二维点阵图绘制出来,结果对比如图3所示。
结果显示,经过20 000次测试,非实时Linux内核的中断延时最小时间为2 us,最大值13 us,平均值5.5 us,标准方差0.5。实时抢占内核中断延时最小时间6 us,最大值28 us,平均值12.6 us,标准方差1.3。实时抢占Linux内核的中断延时时间大于普通非实时内核的测试结果。
(a) 非实时内核中断延时时间
(b) 实时内核中断延时时间
使用实时抢占内核的系统,在某些使用场景下,如果对中断延时时间有着严格的要求(比如必须在20 us内响应),中断线程化将无法做到,那么就可以在申请中断时将其设置为实时中断,此时就不再会被线程化,而是运行在中断上下文中。实时抢占内核中的实时中断的中断延时时间测试结果如图4所示。
图4 实时抢占内核中实时中断中断延时
结果显示,实时抢占内核中若将中断设置为实时中断,中断延时时间可以控制在15 us以内,平均中断延时5.5 us,与非实时Linux内核的测试结果相当。
本文探讨了Linux抢占内核中中断线程化的实现原理,从原理上分析了中断线程化对系统整体性能的影响,并提出了一种中断延时时间测试的方法,将该方法在TI AM3358处理器的平台运行实现,实验结果表明,Linux抢占内核中断线程化可以减少系统关中断时间,从而提高中断处理效率。
Linux抢占内核对中断的处理更加灵活,用户可以根据自身需要将中断设置为实时中断或非实时中断。其中,实时中断未被线程化,在中断上下文中处理,处理的优先级高,及时性强,但缺点是增加系统关中断时间,而在此期间将无法处理其它的任何中断或任务。非实时中断指被线程化的中断,在进程上下文处理,依赖调度器的调度,处理的优先级相对较低,及时性差,优点是减少系统关中断时间,可与系统中的其它任务共同竞争资源,其公平性则更加突出。
在研究中,本文进一步设计了实验仿真来证明上述理论,从而为使用PREEMPT_RT内核的开发人员设计内核驱动提供了重要的参考依据。