引言
Linux 作为一种通用的操作系统,它最初的设计是用于桌面系统或者小型服务器。 要将Linux 用于嵌入式系统中,需要对Linux 内核作一些改进,使它能更好地为嵌入式系统服务。 在这些改进中,进程管理是相当重要的一块。
嵌入式系统的硬件设备与普通PC 有较大的差别。 由于要节省成本、降低功耗,嵌入式系统的CPU 可能不带MMU ,使用的存储设备ROM、Flach 、RAM 的容量较小,这些因素决定了需要改变Linux 进程管理,来适应存储系统方面的变化。进程管理中另一个与嵌入式系统密切相关的方面就是实时性了。 尽管不是所有的嵌入式系统都需要具有实时能力,但实时性确实是嵌入式系统最重要的特点之一。 两者的关系如图1 所示。
图1 嵌入式系统与实时性的关系
目前市场上商用的嵌入式操作系统,如VxWork、pSOS 和QNX 等都具有良好的实时性能。因此本文主要着手讨论Linux 实时性方面的改造。
Linux 在实时应用中的不足之处
虽然目前Linux 内核在实时性方面有所增强,但它仍然不是实时操作系统,Linux 内核的设计关注于应用程序的吞吐量连同内核整体设计的完善。 作为提高吞吐量的必然要求,Linux 的调度器试图提供一种“公平分配”策略来保证所有的进程可以均衡地享有CPU 的资源。 普通Linux 内核提供三种调度策略,分别为SCHEDRR、SCHEDFIFO、SCHEDOTHER。 其中第一个策略为实时进程的基于优先级的轮转法, schedfifo 为实时进程的先进先出算法,schedother 为非实时进程的基于优先级的轮转法。 前两种为软实时进程调度策略,后者为普通实时进程调度策略。
普通时分进程调度策略保证了每个进程相对于其静态优先级可以公平地获取CPU 资源。 由于调度器按照优先级驱动的方式,因此软实时进程的优先级要高于普通进程,通常软实时进程的优先级在1000 以上而普通进程的优先级在0~999之间。 由调度器调用的goodness ( ) 函数保证实时进程先于普通进程获得CPU。
函数goodness() 如下:
static inline int goodness ( struct task struct 3 p ,int this
cpu ,struct mm struct 3 this mm)
int weight ;
weight=-1;
if (p->policy &SCHED YIELD) goto out ;
/ 3 非实时进程3 /
if (p=>policy==SCHED OTHER)
{weight =p->counter ;
if ( ! weight) goto out ;
if (p-> mm==this mm !!! p->mm)
weight+=1 ;
weight+=20-p->nice ;
goto out ;
}
/ 3 软实时进程3 /
weight=1000+p->rt priority ;
goto out ;
return weight ;
} -
从上面的goodness() 函数可以看出普通Linux 内核的进程调度算法只是软实时的,并不是硬实时的。 尤其不能作为硬实时应用,主要有以下几个原因:
(1) L INUX 内核的实时进程的调度算法当中,SCHED RR 和SCHED RR 调度策略的主要不同点在于,SCHED RR 进程在一个时间片运行完以后被放到运行队列的最后。 L INUX的每一个进程都有一个调度策略,在进程描述结构task struct 中规定policy 属性。 所有的进程不管采用哪一种调度策略,都放在同一个运行队列中。这样做,就对实时进程的响应产生了影响。
(2) Linux 的内核是非抢占式的( non-preemptive) 。
(3) Linux 的进程调度策略不是完全抢占式的。
(4) Linux 的虚拟内存技术(virtual memory)增加了系统的不确定性。
(5) Linux 采用的时钟中断的精度不高。
(6) Linux 系统的一些额外操作会延迟实时进程的执行。
综上所述,不经处理的Linux 是不适合做实时操作系统的,即便是软实时,在系统负荷重的情况下也不能保证其实时性。 但通过对Linux 内核的改造完全可以提高Linux 的实时性,甚至可以满足实时的要求。
LINUX 实时性改造的实现方法
实现LINUX 的实时性一般有两种方法: 一种是对普通的LINUX 的内核的数据结构、调度函数、中断方式等进行修改使其能够处理实时进程。 另一种是在LINUX 内核之外,进行实时性扩展。 也就是在普通LINUX 的基础之上再设计一个用于专门处理实时进程的内核。 为了提高LINUX 的实时性,并且使之能够在嵌入式系统中更好地满足硬实时系统的要求,采用上述两种方法结合的两种方式来提高系统的实时性。
外部实时性扩展
对LINUX 做外部实时性扩展最主要的思想是在硬件中断与原来的LINUX 内核之间增加一个实时内核。 在原来的LINUX 内核的基础上,增加一个实时内核可以解决LINUX 原有内核的关中断的问题。
对于实时内核来说,它始终不关闭硬件中断,可以接受所有的中断信号。 当中断信号需要实时进程来处理时,实时进程将抢占LINUX 内核,在RTLINUX中把原来的LINUX 内核作为一个普通进程来对待,并且它的优先级是最低的。 如果中断信号需要原来的LINUX 内核来处理,则由实时内核信号传递给LINUX 内核。 同时内核中提供一个标志位用来模拟原来的LINUX 内核的关中断情况。 这个标志字在LINUX 打开中断的时候置1 ,关中断的时候置0。 实时内核在中断到来的时候检查这个标志位,如果是置1 的,那立刻将中断传给LINUX 内核,否则的话,将所有待处理的中断放入一个队列中,一直到LINUX 打开中断时才将它们一起传给内核。
内部实时性改造
对Linux 内部实时性改造有两个方面的工作: (1) 对时钟机制的改造。 (2) 在内核方面的抢占性改造。 改造的目的是为了缩短Linux 内核的响应延时。
(1) 在时钟机制的改造方面,可以通过提高系统时钟的精度来增强系统的实时性,特别是对外部中断的响应。 因为精确的时钟是操作系统进行准确的调度工作的必不可少的条件。 执行调度就要求在特定的时间进行任务切换。 不精确的时钟会导致调度偏差,从而导致无法预计的结果。 所以提高时钟精度,减少调度偏差是非常重要的。在操作系统中,时钟精度不高的原因之一是因为:周期性时钟中断的使用。 操作系统不得不将大量的时间开销用于处理时钟中断。 Linux。 操作系统也是如此。 在Linux 中,它的中断频率被设为100Hz。 即大约每10ms 产生一次定时中断。
(2) 在内核抢占性方面的改造有两种方法:一种是抢占点的方法。 另一种是抢占式内核的设计。所谓抢占就是内核在某个合适的地方调用schedule() 函数来检查是否有高优先级的任务已经处于ready 状态并让这个高优先级任务运行。为了提高系统的实时性,抢占的选择要合理,既不能时间间隔太短,也不能太长,因此,在设置抢占点的时候要测试内核中的运行路径,在较长的运行路径中插入schedule() 。 进行调度,这个地方就是抢占点。
另一种是抢占式内核的设计,即允许处于系统调用状态的用户进程被刚刚唤醒的高优先级进程所抢占。 但是这种抢占方式并不是在内核代码的所有地方都是安全的,如在临界区就不能抢占。
结论
综上所述,不经改进的Linux 操作系统是不适合做实时操作系统的,即便是软实时,在系统负荷重的情况下也不能保证其实时性。 但通过对Linux 的改造可以完全提高Linux 的实时性,甚至可以满足硬实时的要求。