1 软件中断模拟
由于Linux存在许多关中断的区域,这就会导致中断潜伏期过长,当中断频繁时,会有丢失外部中断的可能性,这是实时系统所不能容忍的。解决这个问题最直接的办法,就是不关中断;这里所谓的不关中断,是对整个系统来说不关中断,但对于Linux本身来说,或者说从Linux的角度来看的话,Linux本身的行为和实际关中断的时候时一样的。这种方法可以用软件中断模式技术来实现。
软件中断模式技术中所指的软件中断,是对于Linux来说的,也就是说,Linux并不直接控制硬件中断,当Linux需要关中断时,它实际上的操作只是将一个软件标识置位,表示从此时开始Linux的执行不想受到中断的干扰,当Linux开中断的时候,实际上的操作是将这个软件标识清除。而实际硬件中断由处于硬件和Linux之间的一个软件层来管理,这个软件层的主要任务就是接收硬件中断,并根据需要调度Linux的中断服务程序;当Linux关中断时(并没有实际禁止硬件中断),软件中断模拟层继续接收硬件中断,并将中断信息记录在日志中,当Linux开中断时,模拟层就根据中断日志调用Linux的中断服务程序;
通过软件中断模拟技术,可以有效的防止外部中断的丢失,但是,我们应该看到,在Linux关中断期间,外部中断还是得不到响应(也就是Linux本身的相应中断服务程序没有执行),所以,这种技术并没有缩短系统的中断潜伏期,也就没有提高系统的中断响应时间,还是无法满足实时任务对快速的中断响应时间的要求。
2 增加内核抢占点
从对Linux的任务响应模型的分析可以看出,调度潜伏期对任务响应时间的影响非常大,在任务响应时间中占了很大一部分(很多文献关于这方面的测试结果都可以证明这一点);所以,Linux实时性优化的一个重要目标就是缩短调度潜伏期的时间。那么,影响Linux调度潜伏期的因素有哪些呢?调度潜伏期的长短与Linux在内核态的一次运行时间长短有关(从进入内核态执行系统服务到退出内核态的时间段),为了减少调度潜伏期的时间,就必须减少Linux连续运行在内核态的时间;最直观的方法就是在执行时间长的内核代码中增加抢占点(也就是增加部分代码主动请求进行调度),将执行时间长的内核代码划分成若干段执行时间段的代码,在各个代码段之间可以进行进程调度,从而达到缩短调度潜伏期的目的。
已经有资料显示,增加内核抢占点是最有效的缩短调度潜伏期的方法;但是,尽管这种方法的原理很简单,实现起来却是相当的繁琐和费时。一方面,要找出内核中哪些部分的执行时间较长本身就不是一件容易的事,而且同样的代码在不同的系统状态下执行时间的长短也不一样,这就需要进行动态的分析找出内核中耗时的代码段;另一方面,随着内核的不断发展,这种方法在不同版本内核之间的移植性也比较差,而且还要对新增加的内核代码进行分析。
3 利用SMP技术实现可抢占内核
由于Linux内核原来的设计是不可抢占的,也就是说,内核代码大部分没有考虑函数的重入(一个系统服务并发的被两个进程执行)和数据的互斥访问的问题,在这种情况下要实现内核的可抢占相当困难。但是,随着后来Linux对SMP(对称多处理器)支持的成熟,出现了利用SMP技术实现可抢占内核的方法。实现可抢占内核,它的目标和增加内核抢占点类似,实际上也是为了缩短调度潜伏期。
要实现可抢占内核,最主要的工作就是要找出哪些代码可能被进程并发调用,并对相关的临界区进行保护,保持数据的一致性。而对SMP的支持,考虑的也是类似的问题,因为在多处理器系统中可以有多个进程同时运行并访问同样的临界资源。所以,可以利用Linux内核对SMP的支持,实现内核的可抢占。在可抢占内核中,当有高优先级的进程可以运行时,系统会立刻进行进程调度;当然,在某些情况下内核抢占也是不允许的,这主要包括以下几种情形:
1)当系统正在处理中断服务程序的时候,不允许进行抢占;中断服务程序是与进程无关的,异步的,它并不属于某个进程,没有进程属性(也就是说不存在进程上下文环境),所以并不满足抢占的条件(没有被抢占的对象)。
2)当系统正在处理下半部中断(Bottom Half)的时候,不允许进行抢占;这里的原因和第一种情况一样。
3)当系统运行的时候持有自旋锁、写锁或读锁的时候,不允许进行抢占;这些锁用于支持SMP,在SMP系统中,这些锁用来保护临界区,控制不同处理器上运行进程对临界区的互斥访问,也就是说,当内核持有这些锁的时候,它不希望被其它进程所抢占。
4)当内核在运行进程调度程序时,不允许进行抢占;这个是显而易见的,运行进程调度程序实际上本身就是抢占的一个过程,而且此时也没有任何进程的上下文环境。
所以,利用SMP技术实现可抢占内核要完成的主要工作包括修改中断返回代码、修改SMP的自旋锁机制、修改调度程序。
根据资料显示,虽然利用SMP技术实现可抢占内核对调度潜伏期的改善不如增加内核抢占点方法,但由于它易于维护、可移植性好,现在已经被加入了标准的Linux2.6版本。
4 实现细粒度定时器
Linux本身的定时器精度比较粗糙,一般为100HZ,也就是说,定时器的中断周期为10ms;但在实时系统中,对定时器的精度有更高的要求,例如,实时系统中的某个实时任务的运行周期必须小于2ms,在这种情况下,普通的Linux是无法满足要求的,因为当定时器频率为100HZ的时候,定时器中断每 10ms才运行一次,才进行一次进程调度,所有周期任务的运行周期至少为10ms。
实现细粒度定时器的一种方法就是提高定时器的频率,这种方法最直接也最容易实现,但由于定时器的频率提高了,定时器的中断服务程序的运行频率也提高了,系统花在任务处理上的时间就会减少,从而降低系统的性能。所以,这种方法有一定的局限性,必须找到一个降低系统性能和提高时钟精度的折衷点。
实现细粒度定时器的另一种方法就是使定时器运行在单触发模式(one-shot mode)。这与周期模式(periodic mode)运行的定时器不同,周期模式运行的定时器,只要对它进行一次初始化操作,以后定时器就会周期的产生中断,不再需要额外的对定时器进行编程操作;而当定时器运行于单触发模式下时,每当定时器产生一次中断后就不再运行,系统再根据当前任务对时间的要求计算出定时器下一次应该产生中断的时间间隔,然后再对定时器进行编程,使它能在系统要求的将来某一时刻产生中断。在单触发模式下,定时器的定时精度能达到微秒级。不过需要注意的是,由于每次中断后都要计算下一次中断的时间,而且还要对定时器进行编程,这两个操作会降低系统的性能。
具体在实时系统中使用哪种定时器方案,一般需要根据具体的应用而定,并不存在一种适用于所有实时应用的定时器方案。
5 优化进程调度
从前述对Linux任务响应模型的分析可知,任务响应时间的长短还与进程调度时间的长短有关,当减少进程调度时间时,也可以有效的加快任务的响应速度。目前,对进程调度的优化主要使用将实时进程和普通进程分开来调度的策略,这可以通过分级调度来实现,通过这样的方法,可以使实时进程的调度时间缩短,从而提高实时任务的响应速度。
6 实时双内核机制
前述的几种Linux内核的实时优化机制虽然可以明显的提高任务的响应速度,增强Linux的实时性,但都无法满足硬实时的要求。由于Linux内核本身的实现方式和复杂度,使得Linux本身始终不能适用于硬实时应用;但随着Linux的应用越来越广泛,对Linux满足硬实时应用的要求越来越强烈;在这种环境下,实时双内核机制开创了Linux支持硬实时应用的先河。在这种技术下,存在一个支持硬实时的微内核,它与Linux内核共同运行于硬件平台上,实时内核的优先级高于Linux内核,它负责处理系统的实时任务,而Linux则负责处理非实时任务,只有当实时内核不再有实时任务需要处理的时候,Linux内核才能得到运行的机会。
实时双内核机制把一个系统看作由两部分组成:实时部分和非实时部分,实时部分由实时微内核处理,非实时部分由Linux处理,它们之间可以通过管道或共享内存的方式进行通信。这种机制虽然在建立实时任务的时候有一套不同于Linux本身的编程接口,但由于它可以与Linux进行通信,从而可以将后续的大量处理工作交给Linux来完成。这样,既保证了实时任务的硬实时要求,也保留了Linux本身的应用编程环境,可以充分的利用Linux下丰富的应用。