REX是高通开发出来的一个操作系统,起初它是为了在Inter 80186处理器上应用而开发的,到后来才转变成应用在ARM这种微处理器上。他历经了很多版本,代码也越来越多,功能也越来越完善。REX只用不到5k的ROM存储空间,从前REX系统汇编代码和C代码加起来不过一千多行,不过现在已经超过一万五千行了。在功能提升的背后,不但要有高性能硬件的支持,同时要求系统的设计上也要更合理。
作为一个实时的嵌入式操作系统,REX有几个比较显著的特点:简单、高效、抢占式、多任务、实时。
简单,我们大家都能理解,嵌入式操作系统都有这样的共同的特点,它不像大型的操作系统一样提供各种各样的功能,它只须按照手机的基本需要来提供一些相应的功能即可,而且作为一个嵌入式操作系统,必须要求它有很小的功耗,这样才能手机在形状的大小上有合理的控制,也适合手机这种以电池供电的设备的需求。高效的,实时系统不是以每秒钟进行多少次运算来评价效率的,而是以对一个事件的响应时间和多久做完来评价的。 REX 是一个 Real Time 的系统,它要求对事件的反映速度要快,不能说用户按了一个 key ,系统等了几分钟都没有相应。 REX 系统不但响应的快,对事件的处理也快。 REX 系统是多任务的,在我们的感觉上, REX 可以同时执行多个任务,这样也体现了高效的特点,但事实上它和其他的操作系统一样,也是以 CPU 在不同任务中的迅速轮转来实现形式上的多任务。这样就出现了一个问题:多任务在 CPU 之间如何轮转?这样就用到了 REX 的另一个特点:抢占式。 REX 是抢占式的,当一个最高优先级的任务一旦 READY ,它总能得到 CPU 的控制权,也就是说它可以抢占正在运行中的任务的 CPU 。以前的 REX 系统中每一个任务都有不同的优先级,总是能明确的比较出处于就绪态的任务中哪一个会获得 CPU ,但是到后来的 REX 版本中,允许不同的任务有相同的优先级,这就要求有另外一种调度方式,也就是在相同优先级的任务之间以时间片轮转的方式来调度。上文中我们在讨论多任务和任务之间的调度,那么何为任务呢?在嵌入式实时操作系统中,任务就相当于线程。REX中有很多的TASK,我们可以把它们理解成都里运行的功能模块。每个任务都有自己独立的堆栈、运行空间、信号、队列等,它们之间是独立的、不互相干扰的,每个任务都实现一个相对独立的功能。但是每个任务都要求有自己独立的堆栈,这就要求在REX中不能有太多的任务,曾经Web Browser因为占用资源过多而被作为一个任务在REX中运行的,但是后来又把它放入BREW之中。我们要注意,不到万不得已不能新加任务。REX操作系统首先会建立一个idle task和main task,然后用main task来控制其他任务的创建。Main task也就是mc_task,在它被创建后它会首先创建一些非常重要服务如时钟、数据服务,数据库服务、Encoder Driver、RF Driver等等,之后才是定义和创建一些任务,首先它将sleep这个任务定义好,然后就是Dog Task,之后再是其他的任务,对于这些任务的内存拷贝、定义、开始服务的顺序我一直很迷惑,考虑到REX严格而周密的优先级制度,那么这些任务的貌似混乱的不同的定义顺序肯定也有其道理,虽然我至今尚迷惑于此,不过这样的确实现了mc_task的静态创建与mc_task对其他任务的动态创建。REX是通过rex_def_task()这个函数来定义任务的,这个函数先生成一个文间结构到stack,然后建立一个TCB跟这个任务相对应,然后再把task放到task list里。每个task都有自己的TCB,也就是task control block任务控制块,TCB里包含了该任务的堆栈地址,任务入口,任务优先级(priority),任务信号等与TASK相关的信息,REX允许用户直接操作TCB结构。在这些任务被定义和初始化后并不是立即运行的,而是根据优先级的不同而先后执行的,最高优先级的任务在其就绪之后开始运行,因为优先级是根据软硬件的逻辑关系及任务本身的重要性和运行频率来合理的确定下来的,所以这种抢占式的方式在兼顾公平的同时也体现了高效的特点。在程序上这些是利用best task和current task的概念来实现的:
rex_set_best_task( REX_TASK_LIST_FRONT() );
这也就是一个Scheduler的概念,也就是调度。它在REX中是被作为一个function来实现的:rex_sched()。它不能被APP直接调用,在调用rex_sched()前必须设置全局变量:rex_best_task,它的基本算法可以用下面的一段伪代码表示:
当前任务压栈保存状态,等待再次弹出。
任务之间是需要相互通信的,而且任务之间的通信要比APP之间的通信难的多,这也是前文提到的Web Browser从任务变为APP的原因。Task之间的通信是依靠信号量的,也是通过信号量的设置清除和等待促成了任务之间的切换。
典型的,当mc_task创建一个任务的时候,它首先define一个task,初始化这个task需要的数据后mc等待,然后去执行处于ready态的task中优先级最高的,等待该task初始化完毕后准备开始了,再返回给mc一个回馈信号,注意这里的回馈信号是无所谓哪个信号发出了,也就是有一个task准备就绪了就要返回mc,之后task死循环等待信号,这个信号可以是个cmd信号,可以是个timer信号,也可以是一个start信号。Task在收到该信号后,根据信号的不同来处理这些信号。这样就可一通过不同的信号来控制task的运行了。
在rex中,这些信号是被保存在task的TCB中的,信号量就是一个标志位,每一个signal是一个1bit的二进制数,在32位处理器ARM中也就是最多有32个信号量。每个信号量代表不同的意思。任务可以自定义信号量,可以用rex_wait()这个函数来等待某个信号,同时把自己挂起,task也只有通过这种方式挂起,其他task或者中断可以通过rex_get_sigs(), rex_set_sigs(), rex_clr_sigs(), 来对某一个task的信号操作,当Task A set一个信号给Task B的时候,该任务也会存储一些数据在特定的buffer中,set一个信号会引起任务调度,Task B接收到这个信号从而处于就绪态,一旦Task B的优先级足够高而运行后,Task B就可以从buffer中读取数据,这样就实现了任务之间的通信。信号被存在任务自己的TCB中,从而实现了信号传递的目的性和安全性。
REX也提供了定时器的功能。Timer有两种,一种是clock timer,一种是任务自定义的timer。Clk_timer到时后回调用call back函数,会立即执行想要的操作,而自定义的timer不会这样,它只会置一个信号,执行不执行还要看task的优先级。Timer的自定义可通过rex_set_timer来实现,对timer的操作还有:rex_get_timer, rex_resume_timer, rex_cls_timer, rex_pause_timer, rex_timed_wait. 系统每一个时钟滴答都会去检查这个timer有没有到时,REX维持了一个特别的list来存储timer,timer一旦到期,REX就从list中将timer移除,但并不是删掉,只是REX不知道这个timer的存在了。Timer只能被task创建和所有。特别要注意的是:不能在活跃的timer上调用rex_set_timer。程序的循环定时检测就因为timer的存在,timer实现了操作系统对离散时间上的事件的及时响应。
一个timer到了,或者是一个其它信号的设置都可能引起任务的调度,任务的调度中就不得不提到中断的概念。中断顾名思义,就是打断当前的操作,来进行其它操作,操作系统每隔一段时间就要执行一次中断来检测当前出于就绪态的task中优先级最高的一个是不是当前任务,或者是任务自发的产生中断。也就是调用rex_wait。既然中断会破坏当前任务的运行,但一段代码不能被打断的时候就要执行关中断这个操作,这段代码就叫做临界段代码,当执行完这段代码后,也要立即开中断。假如Task a执行关中断后自愿wait了,那么关中断会被存储,而不会影响下一个执行的任务,当Task A再次运行的时候,关中断的状态又被恢复。关于中断有几点要注意的问题:1.在中断中的代码不要操作的太多;2.尽量不要在中断中使用循环,或者等待;3.在中断的情况下不要改动全局变量,这样可能造成被挂起的任务执行错误;4. clk_tick_isr也产生中断。
在操作系统中一定要考虑互斥和共享资源的问题。如果两个人都要过一条河,但是河上只有一条独木桥,如果两个人都同时占用这个桥那两个人都没法过河,这就涉及到了互斥的概念。REX提供了Critical section and Mutual exclusion的概念,特别的我们注意到的就是临界代码的关中断,来实现互斥。其实在任务之间的资源互斥还可以用信号量的方法来解决。这样就出现了一个问题:假如有三个任务,它们的优先级排列顺序如下:Task A>Task B>Task C,Task C先运行,它占用着Task A的资源,这时候因为Task A得不到资源而一直处于suspend状态,而在Task C运行的过程中,Task B就绪了,系统就会在中断的时候挂起Task C而去运行Task B,这样就出现了优先级反转的问题。解决这种问题的办法就是动态改变优先级,也就是把Task C的优先级提升。REX支持优先级的动态改变,但是这是需要深思熟虑后才能做的,因为优先级都是经过严格定义的,一旦改变可能会引起不可预料的后果。优先级的动态定义是通过优先级继承来实现的也就是Task C继承了Task A的优先级。
REX有较强的存储区保护功能,它把一些重要的信息存储在了NV中。在任务的执行中从NV中读取信息来供任务使用。嵌入式操作系统所处的硬件上一定很小,REX把静态变量都存储在一个区,然后是各个任务所需要的空间,之后是动态变量的存储区,存储区的大小应该是实现被告知程序员的,这样程序员就可以合理的安排所用空间的大小了。一般一个任务的堆栈大小也是确定的,所以在任务中不能使用较大的数组或结构体,因为这样会撑爆该任务所在的栈。
if(rex_best_task == rex_curr_task)
return ;
else if(the system is currently servicing interrupts)
{
rex_curr_task = rex_best_task;
}
rex_curr_task = rex_best_task;
rex_start_task( rex_best_task );