第三章 嵌入式实时LINUX系统RTLINUX的设计与实现
从上一章的分析,我们已经知道Linux是一个通用操作系统,将它应用于嵌入式实时环境有许多缺点和不足。特别是在运行内核线程时,Linux关闭中断,别的问题包括分时的调度,虚拟文件系统的时间不确定性,缺乏高精度的计时器。所以要对现有的Linux进行改造,即要对Linux进行实时化,这一章将介绍RTLinux的结构和如何对RTLinux进行实时化。
3.1 RTLinux的结构
RTLinux使用众所周知的虚拟机技术的简单方案来解决这些相互对立的解决上面提到的问题。增加了一个仿真程序来替换Linux的底层中断程序。一个小的实时内核与Linux内核共享控制处理器。如果来自硬件的属于实时内核的中断将直接被处理,属于Linux内核的中断通过中断仿真程序处理。假如Linux内核中断请求没有被允许,中断模拟程序将在中断队列中标记这次中断的发生。当Linux内核的中断请求被允许时,在中断队列中的中断将被执行。因此,实时内核的操作可以得到机器的立即相应,而且Linux内核不能延迟实时任务的执行。实时任务与运行在Linux内核中的进程之间的通讯通过FIFOs与共享内存的方式进行。使用实时内核中的调度器调度实时任务,调度器的算法和策略可以用户自己定义;而系统也已经实现了RMS和EDF算法。
这样就保留了Linux操作系统所提供的丰富的功能,而且改动它使其作为一个基本内核与实时内核共享控制CPU。实际上,系统可以看作具有双内核的操作系统,实时内核拥有更高优先级别的任务,换句话说,基本内核可以看作实时系统的空闲任务:只是在没有实时处理要求的时候运行。这样实现的RTLinux的详细结构图如图3.1所示。
图3.1 RTLinux详细结构图
3.2 中断模拟
要在标准Linux上增加硬实时能力,首先遇到的一个问题是Linux为了达到同步使用关中断的方式。混杂在一块的关和开中断操作(i486处理器的cli和sti机器指令)造成不可确定的中断分派延迟。Linux内核是一整块大的内核。在提供系统服务各个部分之间没有一个保护的分界线。要改写Linux内核感到非常棘手。造成要限定关中断的时间非常困难,当更新版本发行时,也可能变得不正确。即使我们能处理这些,时间上离我们的要求仍然太长。
在实时Linux中,是通过在Linux内核与中断控制硬件之间增加一个模拟软件,这是与文献[6]相似的技术,但是用于不同的目的。在Linux源代码中所有的cli,sti,和iret(iret:中断返回指令)被替换为相应的宏:S_CLI,S_STI和S_IRET。所有的硬件中断指令都被中断模拟器捕捉。
/* These are macros */
S_CLI: movl $0, SFIF
S_IRET: push %ds
pushl %eax
pushl %edx
movl $KERNEL_DS, %edx
mov %dx, %ds
cli
movl SFREQ, %edx
andl SFMASK, %edx
bsrl %edx, %eax
jz not_found
movl $0, SFIF
sti
jmp SFIDT ( , %eax, 4)
not_fount:
movl $1, SFIF
sti
popl %edx
popl %eax
pop %ds
iret
S_STI: pushfl
pushl $KERNEL_CS
pushl $done_STI
S_IRET
done_STI:
程序3.1 “软”cli,sti和iret
关中断发生时,在模拟器的一个变量重新设定。只要中断发生,模拟器将检查这个变量。假如这个值是已经设定(Linux中断是允许的),Linux的中断处理程序将立即调用。如果Linux中断是不允许的,中断处理程序将不会被调用。一个变量值将设定,并且保存所有挂起的中断的信息。一旦Linux中断允许处理时,所有挂起的中断将被处理。这种中断称之为软中断。
由于Linux不能直接控制中断控制器,Linux的中断不会影响实时中断的处理。
S_CLI、S_STI和S_IRET宏如程序3.1所示。这个代码使用GNU汇编规范。S_CLI宏简单重新设定变量值,保存Linux中断状态。S_STI宏设置正在被处理的中断的栈。S_IRET宏模拟中断返回。S_IRET宏的工作就像硬件iret指令所做的那样允许软中断。
S_IRET宏是三个宏中最有意思的一个。它先保存一些寄存器和初始化指向内核的数据段寄存器。然后存取全局变量。扫描所有挂起的中断而设置的屏蔽位。如果没有发现挂起的中断,设置中断状态变量,一个硬件的中断返回指令被执行。如果发现一个中断,跳转到Linux中断处理程序。中断处理程序返回后,依次跳转到下一个未处理中断的中断处理程序,直到没有中断再挂起为止。
扫描和转到中断处理程序是一个原子操作,否则,在这过程中有一个中断发生扫描将不能发现任何挂起的中断,这个新到的中断的处理程序将会延迟处理,直到下一个S_STI或者S_IRET被执行是才能被处理。
使用链式跳转的方式来代替子程序调用的方式调用Linux中断处理程序,是因为后者不能完全模拟直接的中断处理。Linux中断处理程序检查栈来发现是用户还是内核代码被中断,基于这个做出决定处理。因此,保护中断状态是很重要的。