不可剥夺型内核:要求每个任务主动放弃cpu。各个任务彼此合作共享一个cpu,异步事件还是由中断服务来处理的。中断服务可使一个高优先级的任务由挂起变为就绪状态,但是中断服务后,使用权就回到了被中断了的那个任务,也就是回到了原来的任务了,直到该任务主动放弃cpu的使用权,此时高优先级的任务才能或得cpu使用权。
其优点:1:响应中断快;2:几乎不需要使用信号量来保护共享数据,当前运行着的任务占用着cpu,而不必担心被别的任务占了,但是某些时候还是要用信号量的,比如说在处理共享的io设备时,仍然要用互斥型信号量。缺点:响应时间,高优先级的任务已经进入就绪状态,但是还是不能运行,因为此时cpu依然被原来的任务占有,要等到原来的程序结束或者其让出cpu使用权,这之间的时间不能确定,也许很长时间。所以不可剥夺型内核的任务级响应时间是无法确定的,这也就导致最高优先级或得cpu的使用权完全取决于程序何时释放cpu了。因此不可剥夺型内核是不能用在商业软件中的。
可剥夺型内核:系统响应很重要,当最高优先级的任务一旦就绪总能得到cpu的使用权,也就是说当一个运行着的任务使得一个比他优先级高的任务进入就绪状态时,当前的cpu的使用权就被这个高优先级任务抢去了,当前任务就被挂起了,或者是中断服务子程序使一个高优先级的任务进入就绪状态,中断完成时,被中断的任务就被挂起,优先级高的任务开始运行。
尽管是可剥夺型内核,不同的可剥夺型内核还有一个在剥夺一个进程处理器使用杈时的强硬程度的区别。
实时操作系统的内核必须是可剥夺型。为此,系统中的每个进程都必须有一个表示其紧急程度的优先级别,以使调度器可根据等待进程的优先级别来决定是否要剥夺当前 进程的处理器使用权。其实,这个优先级别只表示了进程有可能获得处理器使用权的大小,至于调度器在剥夺时的强硬程度是与操作系统启动调度器的时机相关的。
可以想到,随时的、全然不顾的剥夺一定是最强硬的剥夺。那么在哪个时机可以这样做呢?系统时钟中断!在系统中所有进程都具有各自唯一优先级别的基础上,在系统实时时钟中断服务中启动调度器型调度,可使调度器一旦发现待运行进程中有高于当前进程优先级别的进程存在,就会马上剥夺当前进程的处理器使用权。当然,考虑到诸多问题,这样做是不妥当的。
另一个方法是,在系统中的每个进程各自具有一个唯一优先级别的前提下,把调度的时机 选在进程调用系统服务及用户中断服务的末尾,即放在系统服务程序模块和用户中断服务程序的尾部。这样就可在不破坏系统服务的原子性前提下,实现了较为强硬的可剥夺型内核。
使内核的剥夺行为更为柔和的一种方法是,按进程任务的紧急程度把系统中的进程分为若干个组,每个组中的进程都具有同一优先级别,并且把组中的进程按先进先出的方式组成一个队列,再给每一个进程都分配一个占用处理器的时间片。系统在进程把分配给自己的时间片用完之前启动调度器,调度器按照优先级别选择进程队列,在确定了进程队列之后,在队列中按先来先服务的原则再来确定获得处理器使用权进程。显然,这种调度方法虽然也是剥夺型的调度,但是它允许进程把自己的时间片运行完,所以相对来说,这种剥夺行为显得柔和得多。
上面的叙述只是为了说明一个概念,同是剥夺型内核,但是在调度方法不同时,它们的实时性也是不一样的。因此,在设计和选用实时操作系统时,要根据实际应用的情况综合考虑。
例如,对于硬实时应用来说,操作系统内核的剥夺就要强硬一些;反之,就要柔和一些。
必须说明的是,在以进程的优先级别为基础的进程调度中,在具备条件的情况下会产生一种低优先级别的进程先于高优先级别的进程占用处理器的现象,即优先级反转。这种现象是实时系统不能容忍的。
uc-OS是一款可剥夺的、软实时内核。其可剥夺性和实时性方案如下:
ucOS-II的做法:在OSTickISR()函数中通过调用OSIntExit()函数,OSIntExit()函数中调用中断级别调度函数 OSIntCtxSw()进行一次任务调度。另外OSTickISR()函数调用OSTimeTick()函数让通过延时服务 挂起的任务能够及时进入就绪状态。
ucOS-III改进做法:同样在OSTickISR()中能够到调用OSIntCtxSw()函数,进行中断级别的任务调度,但是不同的是, OSTickISR()函数并不调用OSTimeTick()函数,而是给TickTask (void *pdata)函数发送消息。这样做的原因是:OSTimeTick()函数处理内容较多,导致系统在中断中时间过长,影响系统实 时性,通过发送消息的方式,让OSTimeTick()函数在中断外执行。另外,需要注意TickTask这一任务优先级必须设为最高(当系统中有极其重要的任务且不容打断时,优先级可略高于TickTask), 这样下退出中断进行任务调度时,才能够执行该任务,改变系统时间。
以上认识源于与一位与我同样走在嵌入式操作系统学习之路上的朋友的一个问题,经过两人不断交流探讨,终有这点认识,或许进步不大,但意义明显。坚持交流,不轻言放弃是一个年轻人也是一个嵌入式研发人员必备的能力。
在实时系统的设计中,经常会出现多个任务调用同一个函数的情况。如果这个函数不幸被设计成为不可重入的函数的话,那么不同任务调用这个函数时可能修改其他任务调用这个函数的数据,从而导致不可预料的后果。那么什么是可重入函数呢?所谓可重入是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会 出错。不可重入函数在实时系统设计中被视为不安全函数。
满足下列条件的函数多数是不可重入的:
(1)函数体内使用了静态的数据结构;
(2)函数体内调用了malloc()或者free()函数;
(3)函数体内调用了标准I/O函数。
如何写出可重入的函数?在函数体内不访问那些全局变量,不使用静态局部变量,坚持只使用缺省态(auto)局部变量,写出的函数就将是可重入的。如果必须访问全局变量,记住利用互斥信号量来保护全局变量。或者调用该函数前关中断,调用后再开中断。
可重入函数可以被一个以上的任务调用,而不必担心数据被破坏。可重入函数任何时候都可以被中断,一段时间以后又可以运行,而相应的数据不会丢失。可重入函数或者只使用局部变量,即保存在CPU寄存器中或堆栈中;或者使用全局变量,则要对全局变量予以保护。
说法2:
一个可重入的函数简单来说,就是:可以被中断的函数。就是说,你可以在这个函数执行的任何时候中断他的运行,在任务调度下去执行另外一段代码而不会出现什么错误。而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等等,所以他如果被中断的话,可能出现问题,所以这类函数是 不能运行在多任务环境下的。
基本上下面的函数是不可重入的:
(1)函数体内使用了静态的数据结构;
(2)函数体内调用了malloc()或者free()函数;
(3)函数体内调用了标准I/O函数。
把一个不可重入函数变成可重入的唯一方法是用可重入规则来重写他。其实很简单,只要遵守了几条很容易理解的规则,那么写出来的函数就是可重入的。
第一,不要使用全局变量。因为别的代码很可能覆盖这些变量值。
第二,在和硬件发生交互的时候,切记执行类似disinterrupt()之类的操作,就是关闭硬件中断。完成交互记得打开中断,在有些系列上,这叫做“进入/退出核心”或者用OS_ENTER_KERNAL/OS_EXIT_KERNAL来描述。这属于临界区保护。
第三,不能调用任何不可重入的函数。
第四,谨慎使用堆栈。最好先在使用前先OS_ENTER_KERNAL。
还有一些规则,都是很好理解的,总之,时刻记住一句话:保证中断是安全的!