eCos内核概览(1)实时内核
eCos的核心是一个功能全面的,灵活的,可配置的实时内核。这个内核提供了多线程支持,多种调度器的选择,一组丰富的同步原语,内存分配原语和线程管理函数。
在这个内核中,可以改变或替换其中的某些部分(比如调度器)而不会影响内核本身的别的模块。
下列是这个内核的一些特征:
可以选择内存分配算法
可以选择调度算法
一组丰富的同步原语
定时器,计数器和alarms
中断处理
exception处理
cache控制
线程支持
内核支持用GDB进行多线程调试
trace buffers
infrastructure and instrumentation
eCos内核概览(2)调度器
调度器是内核的核心。它定义了线程运行的方式,提供了线程同步的机制。它也控制中断是如何影响线程执行的。没有一个调度器可以覆盖所有可能的系统配置。我们需要几种调度策略来满足不同的需要。这里提供了三种调度器。
位图调度器
位图中每一位表示一个可运行的线程,而每个线程有一个独一无二的优先级,系统允许的线程数是有上限的。
多级队列调度器
可以在相同优先级线程之间按时间片轮换,支持优先级继承。
lottery调度器
目前在任何时候系统只支持一种调度器。将来系统会允许多种调度器共存,但是这将会隐藏在现有的调度器API后。
为了能够安全调度,我们需要一种在并发访问中保护调度器数据结构的机制,传统的方法是在这个临界区禁止中断。不幸的是,这增加了中断的最大dispatch延迟,在任何实时系统中都应当避免这种情形的发生。
eCos采用的机制是保持一个计数器,Scheduler::sched_lock。如果它的值不为0,就防止了重新调度。当前的中断通过调用Scheduler::lock()得到这个锁,它对这个计数器加1避免进一步的调度。函数Scheduler::unlock()对这个计数器减1,如果它返回0,允许继续调度。
为了在中断存在的情况下这种机制能够很好地工作,需要ISR推迟执行任何引起调度(scheduler-oriented)的操作,直到这个锁将要为0。为此我们把ISR的工作分割成两部分。并把第二部分,DSR,写进队列,直到调度器认为运行它们是安全的。(细节见第三章的中断和exception handler)
在单处理器上,Scheduler::lock()仅仅是对Scheduler::sched_lock加1。既然这个锁严格地被嵌套,Scheduler::lock()不需要一个读-修改-写周期。当前线程正在运行这个事实意味着这个锁还没有被别的线程获得,因此它总是可获得的。
eCos内核概览(3)线程同步
为了允许线程协调和竟争资源,提供同步和通讯机制是必要的。传统的同步机制是互斥/条件变量和信号量。eCos内核不但支持这些机制,它还提供了在实时系统中普遍用到的其它同步和通讯机制,例如event flags和消息队列。
在任何实时系统中必须处理的一个问题是优先级倒转(priority inversion)问题。它出现在一个高优先级线程(错误地)被一个低优先级线程阻止了继续执行。一般的例子是:一个高优先级线程等待一个互斥量,而这个互斥量正被一个低优先级线程所拥有,如果这个低优先级线程被一个中等优先级的线程剥夺了运行,那么就发生了优先级倒转,既然这个高优先级线程被一个不相关的较低优先级线程阻止了继续执行。
这里有好几种方法可以解决这个问题。最简单的是运用一个优先级ceiling protocal。所有获得这个互斥量的线程把它们的优先级提升到一个事先规定好的值。它有如下不利因素:它需要事先知道使用这个互斥量线程的最高优先级;如果这个事先规定的值太高,它相当于一个全局锁禁止了所有的调度。
一个更好的解决方案是使用优先级继承协议,拥有互斥量的线程的优先级被提升到与正在等待这个互斥量的最高优先级线程的优先级相等。这个技术不需要预先知道准备使用这个互斥量线程们的优先级。当一个更高优先级线程处于等待时,只有拥有互斥量的线程的优先级被提升。这种方法减少了对别的线程进行调度的影响。但是它的不利之处在于:每次同步调用的开销增加了,因为每次都得遵守这个继承协议。
第三种方法是:认识到糟糕地选择了相对的线程优先级,因此这个发生优先级倒转的系统本身是有缺陷的。在这种情况下,当发生优先级倒转时,内核要有能力检测到,并产生一个exception来调试这个系统。
eCos提供了一种相对简单的实现方式。它只在多级队列调度器中有效,它同时不能完全正确地处理嵌套的互斥量的极端例子。但是,它不但速度快而且是确定性的。如果不需要互斥优先级倒转,可以disable它,这将减少代码大小和数据空间。
eCos内核概览(4)例外(exceptions)
一个exceptions是一个同步事件,它由一个线程在执行时产生。exception包括硬件引起的机器exception(比如除零,内存出错和非法指令)和软件引起的机器exception(比如deadline overrun)。标准c++ exception机制代价太昂贵了而不能在这儿使用。
处理exception最简单和最灵活的方式是调用一个函数。这个函数需要赖以工作的上下文,因此需要访问一些工作数据。这个函数至少也需要exception号和一些可选参数。
exception handler接受一个数据参数,这个参数是一个用handler和上下文信息指针注册的值。它也接受一个exception号和一个错误码,exception号用来确认哪个exception产生了,而错误码则包含处理这个exception时需要的额外信息(比如一个内存出错地址)。从这个函数返回使得线程继续执行。
根据配置选项,exception handler可以是全局的也可以是每个线程一个(per-thread),或者两种情况都有。如果exception handler是每个线程一个,必须把exception handler附带(attach)在每个线程上。
eCos内核概览(5)中断
中断是由外设引起的异步事件。在任何时候它们都有可能发生。它们并不与当前正在运行的线程有一丝联系。
中断处理是RTOS设计中最复杂的一部分,主要由于it is the least well defined。如何对中断向量命名,如何把中断递交给软件和如何屏蔽中断都是高度与特定CPU结构(有时候与特定板)相关的。
让我们考虑一下中断向量这个问题。这儿是硬件支持主要的差异:Intel和680X0支持把中断导引(vectored)到它们自己的向量上,而大多数RISC只有一个向量。第一种情况,可以直接把ISR附带在这个向量上;第二种情况,必须确定实际是哪个外设产生中断,然后导引到正确的ISR。如果这儿有一个外部中断控制器,将有可能对它进行查询并硬件实现本来是由软件实现的一些操作。如果没有外部中断控制器,就必须依次调用ISRs对外设进行测试来决定产生中断的外设。既然两个外设可以同时产生中断,那么每次中断产生时,就必须调用所有的ISRs。
屏蔽中断也有同样的问题。大多数处理器在状态寄存器上有一个简单的中断屏蔽位。而680X0有七级屏蔽。可以对任何带有中断控制器的板进行编程提供相同的多级屏蔽。必须保持中断屏蔽机制简单和高效,同时只需要CPU结构支持。操作一个板上的中断控制器代价可能太高。然而,个别的设备驱动程序可能需要访问中断控制器上个别的屏蔽位,因此必须提供这种支持。
eCos内核概览(6)计数器、时钟、ALARM和定时器
如果硬件提供一个周期性时钟或定时器,它们将会用来驱动与定时有关的系统 features。很多CPU结构现在都有内嵌的定时寄存器,它们可以提供一个周期性中断。应当用它们来驱动这些features。要不然就必须使用一个外部的定时器/时钟芯片。
我们区分计数器,时钟,alarm和定时器:一个计数器保持一个递增的counter,它由一些ticks源驱动。一个时钟是由一个有规律的ticks源驱动的计数器。时钟有一个相关联的精度。一个缺省系统时钟由上面提到的周期性中断驱动,tracks real-time。别的中断源可能驱动另外一些计数器,它们以不同精度 track real-time。一些计数器可以被非周期的事件驱动,因此与实时没有一点联系。
一个Alarm附带在一个计数器上,基于counter的值,提供了一种产生single-shot或周期性事件的机制。一个定时器是一个附带在时钟上的简单Alarm。
系统(包括内核)以ticks单元表示时间。这儿有特定时钟的时间单元和通常是定时器中断的周期。只有在必需的情况下,通过库函数完成由ticks到传统时间和数据单元的转换。
需要用64位表示当前tick count。这需要编译器支持64位整数或汇编码。第五章的时钟,计数器和alarm描述影响时钟,计数器和alarm的时钟API。 |