分类: LINUX
2015-08-17 14:02:12
实时任务负责轮询端口,并将接收到的信息存入一个FIFO的缓冲区,而Linux进程而负责从该缓冲区中读出数据并显示(或保存到文件),在读写该缓冲区,必须禁止中断,所以系统必须提供一种共享数据区,当操作该数据时,中断被禁止。
在NMT RT-Linux中,系统有一些函数用于管理这些数据区(其中的数据被组织成FIFO形式,称为RT-FIFO),如下:
l int RTFifo_Create(unsigned int fifo, int size)创建一个大小为size的RT-FIFO。
l int RTFifo_Destroy(unsigned int fifo)销毁指定的RT-FIFO。
l int RTFifo_Get(unsigned int fifo, char *buf, int count)从FIFO中读取count个字节到buf中。
l int RTFifo_Put(unsigned int fifo, char *buf, int count)向FIFO中写buf中count个字节。
同时FIFO机制提供了一个实现信号灯的很好方法。两态信号灯可以通过创建一个大小为1的FIFO来实现,V操作即为RTFifo_Put(),数据内容无所谓,同时忽略返回的错误。P操作为RTFifo_Get()。计数信号灯可以通过创建大小足够容纳所期望V操作个数的FIFO简单地实现。由此可见,FIFO机制提供了实时应用中任务同步所需的大部分功能。当前的实现在RTOS用户习惯的某些功能上仍有欠缺,比如优先级禁止(防止优先级反转)和任务安全删除。但是仔细设计几乎总能避免这些问题。此外,虽然FIFO操作可以在没有数据(读FIFO)或没有空间(写FIFO)时阻塞,语法却相当复杂,阻塞能力看来不是设计的重点。然而,至少有一个提供FIFO阻塞操作简单语法的努力正在进行,同时还实现了阻塞超时,这是许多嵌入式应用的重要特征。RT-Linux简单、开放的设计允许用户相当容易地实现类似的附加功能。
4.
Linux内核能长时间地屏蔽中断。当然,正是这一点使Linux成为非实时操作系统。解决这个问题有两种途径:一是重新设计内核使之可抢先。但是Linux内核又大又复杂,不便于经常修改。而且当初设计Linux时根本就没打算把它做成一个实时系统。因此,把实时性强加到已存在的代码上是不可行的。于是NMT RT-Linux设计者用另一种途径来使Linux可抢先。他们将中断分为两组:由Linux控制的为一组,另一组由NMT RT-Linux控制。RT-Linux中断象RT-Linux任务一样受到约束,它们不能进行Linux调用。所以它们可以安全地中断Linux内核。另一方面,Linux中断不允许中断内核。因此NMT RT-Linux形成了一个虚拟中断机制,使Linux本身永远不能屏蔽中断。Linux用“cli”和“sti”宏指令来屏蔽和使能中断。在标准Linux里,这些宏只是简单地执行相应的x86/PC处理器指令。NMT RT-Linux修改了这些宏,执行cli时不是屏蔽中断,而是重定向到某些RT-Linux代码。如果这是一个RT-Linux中断,允许它继续;如果是一个Linux中断,则只是设置一个标准位。然后当执行了sti开中断后,那些挂起的Linux中断才继续执行。这样,Linux仍然不能中断它自己,但是NMT RT-Linux可以。RT-Linux很简单,只提供了实现一个实时系统所需的不能再少的功能。但是系统设计者正是得益于这种简单性。大部分应用应该在Linux进程中实现,因为Linux本身稳定可靠,并且为广大用户所熟悉,即使有了麻烦也能找到帮助。实时任务只包括必要的功能来实现实时I/O,以及从Linux进程接收或发送数据。
在中断处理中,NMT RT Linux对Linux内核有如下三处修改:
l cli(Clear Interrupt)被简单地修改成对一个控制“软中断使能”(SIE)的全局变量的清0
l sti(Set Interrupt)被修改成为所有等待中断产生中断模拟
l 低级wrapper指令(保存和恢复中断状态)被修改为使用软返回,而不是使用原来的硬件机制
当一个中断发生时,控制转移到实时处理程序,处理程序首先进行一些必要的处理,然后将中断传给Linux。如果“软中断使能”标志为1,则将控制转移到Linux的处理程序。
同时,需要说明的是,Linux非常容易修改,因为在标准的x86 Linux下,cli()和sti()事实上是一段汇编宏指令(产生x86下的指令cli和sti)。总的说,需要大约2000行新代码,而且需要修改大约几百行Linux的代码。如下为三个宏:
S_IRET保存最少的必要状态,保证内核数据地址可访问。
/*These are macros*/
S_CLI: mov $0, SF1F
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, SF1F
sti
jmp SFIDT (, %eax, 4)
not_found: movl $1, SFIF
sti
popl %edx
popl %eax
pop %ds
iret
S_STI: pushfl
pushl $KERNEL_CS
pushl $done_STI
S_IRET
done_STI:
当发生中断时,中断处理程序首先执行一些实时系统的必要代码,然后把中断传给Linux(如果“软中断使能”置1)。由于大多数I/O驱动都没有为实时系统专门设计,大多数实时驱动中断处理程序仅仅决定只是简单地通知Linux。另一方面,每次时钟中断发生时,给时间节拍变量增1,并且决定是否应该运行一个定时任务,在适当的时候将中断传给Linux。
如果软中断被禁止,直接通过IRET将控制返回,否则控制被返回到S_IRET,这个宏调用与该中断相应的处理程序。
同时,需要特别指出的是,必须在时钟中断速度和事件发生的不同步之间寻找一个折衷点。更高的时钟中断速度意味着可以对任务进行更多的采样,从而降低系统响应时间,但同时用于中断切换的时间也增多了。反之则可能导致事件的反应时间过长,从而影响系统响应时间,尽管这减少了中断的次数(时间)。
首先, 要执行的任务和瞬时限制必须经过鉴定能满足我们的条件;
第二,编写代码,每一个实时任务都要经过测量和调度测试, 以保证在系统运行过程中每个任务都不能超过它所允许的时间最大值。 调度测试由应用于一系列测试的整个任务集组成, 如果能通过第二阶段的测试,就有可能保证没有任务可能会运行超过最大的期限。
如果测试不能通过的话,那么设计就必须重新从头开始: 选择一个快一点的CPU,或者使用其它的算法来实现这些任务。
总结起来,任务有三个时间标识:Pi,Di和Ci。系统的目标是,保证所有的任务在所有的执行过程中都不超过系统允许的时间最大值。 为了保证运行时间,系统必须是可预测的。 说一个系统是一个实时系统和说一个系统是可预测的实际上是一样的。
系统响应语义的正确性是程序员的责任,而瞬时的正确性依赖于操作系统(OS)。
操作系统必须能支持和组织所有任务的执行,处理中断也是它的职责。 操作系统必须提供:
与普通的操作系统不同,实时操作系统的目标是最大程度地减少复杂性。 我们不需要一个能做很多事情的系统,最重要的是它能在规定的时间内可预测地执行我们的任务。
在一个实时系统上,一个正常情况下需要10个单位时间的任务, 由于上下文环境的改变,在最坏的情况下消耗12的单位时间是可取的。 而在其它普通的操作系统上,一个平均情况下需要3个单位时间的程序经常会执行20个单位时间。
如果发现一个实时系统比普通的操作系统“慢”,我们不必感到奇怪。 有时候,为了获得可预测的行为,甚至需要禁止使用Cache, 这会带来一些性能上的丢失。对一个实时系统来说, 处理器Cache所提供的管道线单元和预言跳转算法是最大的敌人。
POSIX是可移植操作系统接口(Portable Operating System Interface)的首字母缩写(可是为什么OS这个缩写后面没有一个X呢?)。 这一标准意在期望获得源代码级的软件可移植性。 换句话说,为一个POSIX兼容的操作系统编写的程序, 应该可以在任何其它的POSIX操作系统(即使是来自另一个厂商)上编译执行。 POSIX标准定义了操作系统应该为应用程序提供的接口:系统调用集。 POSIX是由IEEE (Institute of Electrical and Electronic Engineering) 开发的,并由ANSI (American National Standards Institute)和 ISO (International Standards Organisation)标准化。 很显然POSIX是基于UNIX的,大多数的操作系统(包括Windows NT)都倾向于开发它们的变体版本与POSIX兼容。
POSIX的定义被分为几个工作组:包括计算机厂商、软件公司、 政府部门和计算机设计师。每一个工作组关于操作系统的某一方面。 例如:POSIX.4组是关于实时方面的内容。
POSIX.4的扩展(1993年更名为1003.1b)允许一个操作系统在实时情况下使用。 很明显,这些扩展大部分都是关于时间管理和进程优先级, 也有一些系统调用来协助进行进程间通信。
POSIX扩展被设计用来增强操作系统对资源的管理控制能力。
Linux2.0为实时性实现了许多符合POSIX扩展的系统调用, 但Linux这一方面的内容我们将在以后进行讨论。 它的2.2版本基本上100%与POSIX 1003.1b兼容。
RT-Linux是在新墨西哥矿业及科技学院计算机系由 Victor Yodaiken和Michael Barabanov开发出来的。 它是Michael提交的完成计算机科学硕士论文的一部分。 新最可用的版本是0.6。现在只用在INTEL体系结构的计算机上。
RT-Linux是用一种完全不同的方式解决这一问题的。 有别于修改Linux系统的内核以使其具有可预测性,它直接在处理器(i386) 上建立了一个具有一个调度器的小的核心(与Linux kernel相独立), Linux内核在这一核心上运行,并与其它实时任务分享处理器。 那么Linux与其它任务分享CPU,更精确地说,Linux是后台的任务, 只有没有其它实时任务执行的时候它才会运行。
我猜想读者现在可能困惑了,可能是因为有人想操作系统是一个整体, 怎么可以修改它?
更令人惊奇的是:事实上,如果作为一个模块编译的话, 你可以动态地装入和移除调度器。
与其它操作系统类似,作为同步方式或者为了实现临界区, Linux内核代码通常会关闭中断。如果在Linux关中断期间来了一个时钟中断, 它就会阻塞,这将导致丢失瞬时精确度。RT-Linux使用了一个非常优雅的解决方案: 所有对CLI、STI和IRET(修改中断状态的汇编调用)的调用都用 S_CLI、S_STI和S_IRET来代替和模拟,这样,Linux就永远不能禁止中断调用。
RT-Linux缺省的调度策略是抢占式、固定优先级的调度, 并对Linux任务赋予了较低的优先级。 如果实时任务消耗了所有的CPU时间,那么Linux任务将不能获得任何CPU时间, 看起来就像是停止了一样。
使用RT-Linux我们不仅有了一个实时系统,还有了一个经典的操作系统。 在采样和控制一个物理系统的同时我们还可以上网冲浪。
这一发行版的文件可以在下面获得: ~rtlinux.
为了把一个Linux系统改成RT-Linux, 我们必须把RT-Linux提供的内核补丁应用到内核源代码上并重新编译内核。 下面是编译的方法。我们假设rtlinux-0.6-2.0.33.tgz在目录/usr/src下, 并且它已被解压缩到/usr/src/rtlinux-0.6。 我们还假定所有的内核选项都已经配置(make config)好了。接下来
# cd /usr/src/linux
# patch -p1 <../rtlinux-0.6-2.0.33/kernel_path
# make dep; make clean; make zlilo; make modules; make modules_install
# reboot
新内核跟一个普通的内核看起来没什么差别, 但它已经准备好转换为一个实时系统了。 在/usr/src/rtlinu-0.6-2.0.33/testing下有各种各样的演示程序。
除了发行版在testing目录中的例子,你还可以下载Oleg Subbotin为我们准备的另一个示例程序,它允许我们创建任务的执行记录。 这一示例的一个文件是一个修改了的调度器,它不仅能执行任务调度, 还能够发送关于任务决策的信息。这一信息被收集并存储于一个文件中, 以后可以图形化的显示。结果我们就可以看出各种任务是以什么顺序执行的, 具有高优先权的任务是如何抢占低优先级的任务的。Linux任务没有表现出来。
每一个任务都表现在一个水平轴上。长方形表示每一个任务占用CPU的时间 (因为我们使用一个单处理器的系统,所以在同一时刻只能有一个实例在运行), 在这个例子里,每一项任务的最大允许执行时间与他们的周期相同, 这一周期用一个时间间隔标记(用代表)。在这一间隔内任务必须执行完毕。 上面部分的任务具有较高的优先级,并且能从其它任务(如600位置)抢占处理器,
现在已经有了一个多处理器的RT-Linux版本。为了尽可能地保持系统的可预见性, RT-Linux所能提供的服务被故意地限制的很少, 因为没有必要包含那些对实时性要求不严格的功能。
几周以前开始了一个RT-Linux手册-教程的编写工作。(本文文写于1998年:译者注)
在RT-Linux出现以前,大多数需要实时系统的工程师被迫使用MS-DOS并且建立所有需要的驱动, 或者是以非常惊人的价钱购买一个真正的实时操作系统。 现在开发者们有了一个全功能的操作系统, 他们可以在与将要运行于其上的相同的系统上开发实时的程序。 实际上,我们在上网冲浪的同时再运行几个实时程序也没有问题。
我们这一系列以后的文章中会研究几个实时应用程序的例子, 以及如何编写我们自己的实时程序。
Linux虽然给实时性进程提供可较高的优先级,但是并没有加入时间限制。例如完成的最后期限,应在多长时间内完成和执行周期等。同时,其他大量的非实时进程也可能对实时进程造成阻塞,无法确保实时进程的响应时间。
2.中断处理的缺点
Linux内核对于中断处理采用 Bottom Half Handling方法(详细请自己查谷歌),这种处理方法在处理中断过程中,屏蔽了系统其它中断,而且优先级别高的任务也不能抢占处理,所以linux中断句柄是不可调度的;但在实时系统中,有时期望能在一个可调度整体内处理这些中断句柄,从而能更有效地区分不同实时任务的密度,分配不同的优先级。因此,单纯采用bottom half hadling缩短时间片方法在对实时性能严格要求的场合仍有些不适合。
3.内存管理的缺点
Linux系统采用虚拟内存管理机制,在虚拟内存中,系统会根据当前任务使用的内存情况,把一些任务或用户进程交换出内存,保持在磁盘文件系统页面上,在以后需要时,再将它们调入内存,这种方法提高了内存的使用效率,但调入过程需要花费一定的时间。这种采用时间换空间的方法,在实时应用场景下,往往造成任务的响应时间加长或有限时间内无法执行完毕。
4.时钟精度
Linux中硬件时钟中断的默认时间间隔是10MS,所有的软件时钟都是靠硬件来触发的。linux内核的任务调度也依靠这个时钟,时钟频率直接影响到系统响应速度和上下文切换的系统开销。最小时间片为10MS,决定了linux任务调度能提供最小10MS的调度粒度,对于许多实时系统来说,通常需要做微秒级的响应,这种调度精度很难满足实时系统对系统响应速度的要求。
本文探索了一些支持实时特性的 Linux 架构,并探讨了实时架构的含意是什么。有许多种解决方案赋予 Linux 实时能力,本文将对瘦内核(或微内核)方法、超微内核方法以及资源内核(resource-kernel)方法进行考查。最后,描述了标准 2.6 内核的实时功能,并向您示范如何启用并使用这种功能。
一些例子将演示全部这些内容的含意。图 1 显示的是中断延迟指标。当中断到达时(event),CPU 发现中断并转入中断处理。执行一些工作以确定发生了什么事件,然后执行少量工作分配必需的任务以处理此事件(上下文切换)。中断到达与分发必需任务之间的时间(假设分配的是优先级最高的任务)称为响应时间。对于实时性要求,响应时间应是确定的并应当在已知的最坏情况的时间内完成。
图 1. 中断延迟和响应时间
换句话说,系统面对变化的负载(从最小到最坏的情况)时必须确定性地保证满足时间要求。注意,上述定义并未提到性能,原因是实时性与速度关系不大:它与可预见性有关。例如,使用快速的现代处理器时,Linux 可以提供 20 μ 微秒的典型中断响应,但有时候响应会变得很长。这是一个基本的问题:并不是 Linux 不够快或效率不够高,而是因为它不能提供确定性。
上下文切换除为中断处理提供确定性外,实时处理也需要支持周期性间隔的任务调度。考虑图 2。本图演示了周期性任务调度。大量控制系统要求周期性采样与处理。某个特定任务必须按照固定的周期(p)执行,从而确保系统的稳定性。
图 2. 周期性任务调度
现在您已经对实时性要求有了一些深入了解,让我们查看一些实时 Linux 架构各支持哪个级别的实时性以及如何做到这一点。
瘦内核方法瘦内核(或微内核)方法使用了第二个内核作为硬件与 Linux 内核间的抽象接口(见图 3)。非实时 Linux 内核在后台运行,作为瘦内核的一项低优先级任务托管全部非实时任务。实时任务直接在瘦内核上运行。
图 3. 硬实时的瘦内核方法
瘦内核主要用于(除了托管实时任务外)中断管理。瘦内核截取中断以确保非实时内核无法抢占瘦内核的运行。这允许瘦内核提供硬实时支持。虽然瘦内核方法有自己的优势(硬实时支持与标准 Linux 内核共存),但这种方法也有缺点。实时任务和非实时任务是独立的,这造成了调试困难。而且,非实时任务并未得到 Linu 平台的完全支持(瘦内核执行称为瘦 的一个原因)。
使用这种方法的例子有 RTLinux (现在由 Wind River Systems 专有),实时应用程序接口(RTAI)和 Xenomai。
超微内核方法
这里瘦内核方法依赖于包含任务管理的最小内核,而超微内核法对内核进行更进一步的缩减。通过这种方式,它不像是一个内核而更像是一个硬件抽象层(HAL)。超微内核为运行于更高级别的多个操作系统提供了硬件资源共享(见图 4)。因为超微内核对硬件进行了抽象,因此它可为更高级别的操作系统提供优先权,从而支持实时性。
图 4. 对硬件进行抽象的超微内核法
注意,这种方法和运行多个操作系统的虚拟化方法有一些相似之处。使用这种方法的情况下,超微内核在实时和非实时内核中对硬件进行抽象。这与 hypervisor 从客户(guest)操作系统对裸机进行抽象的方式很相似。更多信息参见 参考资料 。
关于超微内核的示例是操作系统的 Adaptive Domain Environment for Operating Systems (ADEOS)。ADEOS 支持多个并发操作系统同步运行。当发生硬件事件后,ADEOS 对链中的每个操作系统进行查询以确定使用哪一个系统处理事件。
资源内核法
另一个实时架构是资源内核法。这种方法为内核增加一个模块,为各种资源提供预留(reservation)。这种机制保证了对时分复用(time- multiplexed)系统资源的访问(CPU、网络或磁盘带宽)。这些资源拥有多个预留参数,如循环周期、需要的处理时间(也就是完成处理所需的时间),以及截止时间。
资源内核提供了一组应用程序编程接口(API),允许任务请求这些预留资源(见图 5)。然后资源内核可以合并这些请求,使用任务定义的约束定义一个调度,从而提供确定的访问(如果无法提供确定性则返回错误)。通过调度算法,如 Earliest-Deadline-First (EDF),内核可以处理动态的调度负载。
图 5. 实现资源预留的资源内核法
资源内核法实现的一个示例是 CMU 公司的 Linux/RK,它把可移植的资源内核集成到 Linux 中作为一个可加载模块。这种实现演化成商用的 TimeSys Linux/RT 产品。
标准 2.6 内核中的实时
目前探讨的这些方法在架构上都很有趣,但是它们都在内核的外围运行。然而,如果对标准 Linux 内核进行必要的修改使其支持实时性,结果会怎么样呢?
今天,在 2.6 内核中,通过对内核进行简单配置使其完全可抢占(见图 6),您就可以得到软实时功能。在标准 2.6 Linux 内核中,当用户空间的进程执行内核调用时(通过系统调用),它便不能被抢占。这意味着如果低优先级进程进行了系统调用后,高优先级进程必须等到调用结束后才能访问 CPU。新的配置选项 CONFIG_PREEMPT 改变了这一内核行为,在高优先级任务可用的情况下(即使此进程正在进行系统调用),它允许进程被抢占。
图 6 允许抢占的标准 2.6 Linux 内核
但这种配置选项也是一种折衷。虽然此选项实现了软实时性能并且即使在负载条件下也可使操作系统顺利地运行,但这样做也付出了代价。代价就是略微减低了吞吐量以及内核性能,原因是 CONFIG_PREEMPT 选项增加了开销。这种选项对桌面和嵌入式系统而言是有用的,但并不是在任何场景下都有用(例如,服务器)。
新的 O(1) 调度程序在 2.6 内核中另一项有用的配置选项是高精度定时器。这个新选项允许定时器以 1μs 的精度运行(如果底层硬件支持的话),并通过红黑树实现对定时器的高效管理。通过红黑树,可以使用大量的定时器而不会对定时器子系统(O(log n))的性能造成影响。
只需要一点额外的工作,您就可以通过 PREEMPT_RT 补丁实现硬实时。PREEMPT_RT 补丁提供了多项修改,可实现硬实时支持。其中一些修改包括重新实现一些内核锁定原语,从而实现完全可抢占,实现内核互斥的优先级继承,并把中断处理程序转换为内核线程以实现线程可抢占。
结束语
Linux 不仅是一个实验和描述实时算法的理想平台,目前在标准的 2.6 内核中也实现了实时功能。从标准内核中您可以实现软实时功能,再执行一些额外的工作(内核补丁)您就可以构建硬实时应用程序。
本文简要介绍了一些为 Linux 内核提供实时计算的技术。很多早期的尝试使用瘦内核方法把实时任务与标准内核分离。后来,出现了超微内核法,它与如今的虚拟化解决方案中使用的 hypervisor 非常相似。最后,Linux 内核提供了自己的实时方法,包括软实时和硬实时。
虽然本文只是对 Linux 的实时方法进行了简单介绍,但 参考资料 一节中提供了更多的信息,可以从中获得额外的信息和其他有用的实时技术。
参考资料
学习
获得产品和技术