大家好,欢迎来到IT知识分享网。
实时操作系统概念
实时系统的特点是,如果逻辑和时序出现偏差将会引起严重后果的系统。有两种类型的实时系统:软实时系统和硬实时系统。在软实时系统中系统的宗旨是使各个任务运行得越快越好,并不要求限定某一任务必须在多长时间内完成。
在硬实时系统中,各任务不仅要执行无误而且要做到准时。大多数实时系统是二者的结合。实时系统的应用涵盖广泛的领域,而多数实时系统又是嵌入式的。这意味着计算机建在系统内部,用户看不到有个计算机在系统里面。以下是一些嵌入式系统的例子:
1、前后台系统(Foreground/Background System)
2、代码的临界段
3、资源
4、共享资源
5、多任务
6、任务
7、任务切换(Context Switch or Task Switch)
8、内核(Kernel)
单片机一般不能运行实时内核,因为单片机的RAM很有限。通过提供必不可缺少的系统服务,诸如信号量管理,邮箱、消息队列、延时等,实时内核使得CPU的利用更为有效。一旦读者用实时内核做过系统设计,将决不再想返回到前后台系统。
9、调度(Scheduler)
10、不可剥夺型内核(Non-Preemptive Kernel)
不可剥夺型内核的另一个优点是,几乎不需要使用信号量保护共享数据。运行着的任务占有CPU,而不必担心被别的任务抢占。但这也不是绝对的,在某种情况下,信号量还是用得着的。处理共享I/O设备时仍需要使用互斥型信号量。例如,在打印机的使用上,仍需要满足互斥条件。图2.4示意不可剥夺型内核的运行情况,任务在运行过程之中,[L2.4(1)]中断来了,如果此时中断是开着的,CPU由中断向量[F2.4(2)]进入中断服务子程序,中断服务子程序做事件处理[F2.4(3)],使一个有更高级的任务进入就绪态。中断服务完成以后,中断返回指令[F2.4(4)], 使CPU回到原来被中断的任务,接着执行该任务的代码[F2.4(5)]直到该任务完成,调用一个内核服务函数以释放CPU控制权,由内核将控制权交给那个优先级更高的、并已进入就绪态的任务[F2.4(6)],这个优先级更高的任务才开始处理中断服务程序标识的事件[F2.4(7)]。
总之,不可剥夺型内核允许每个任务运行,直到该任务自愿放弃CPU的控制权。中断可以打入运行着的任务。中断服务完成以后将CPU控制权还给被中断了的任务。任务级响应时间要大大好于前后系统,但仍是不可知的,商业软件几乎没有不可剥夺型内核。
11、可剥夺型内核
使用可剥夺型内核时,应用程序不应直接使用不可重入型函数。调用不可重入型函数时,要满足互斥条件,这一点可以用互斥型信号量来实现。如果调用不可重入型函数时,低优先级的任务CPU的使用权被高优先级任务剥夺,不可重入型函数中的数据有可能被破坏。综上所述,可剥夺型内核总是让就绪态的高优先级的任务先运行,中断服务程序可以抢占CPU,到中断服务完成时,内核让此时优先级最高的任务运行(不一定是那个被中断了的任务)。任务级系统响应时间得到了最优化,且是可知的。μC/OS-Ⅱ属于可剥夺型内核。
12、可重入性(Reentrancy)
void strcpy(char *dest, char *src)
{
while (*dest++ = *src++) {
;
}
*dest = NUL;
}
不可重入型函数的例子如程序2.2所示。Swap()是一个简单函数,它使函数的两个形式变量的值互换。为便于讨论,假定使用的是可剥夺型内核,中断是开着的,Temp定义为整数全程变量。
程序清单 2.2 不可重入型函数
void swap(int *x, int *y)
{
Temp = *x;
*x = *y;
*y = Temp;
}
使用以下技术之一即可使Swap()函数具有可重入性:
把Temp定义为局部变量
调用Swap()函数之前关中断,调动后再开中断
用信号量禁止该函数在使用过程中被再次调用
如果中断发生在Swap()函数调用之前或调用之后,两个任务中的X,Y值都会是正确的。
13、时间片轮番调度法(ucos不支持)
当前任务已无事可做
当前任务在时间片还没结束时已经完成了。
目前,μC/OS-Ⅱ不支持时间片轮番调度法。应用程序中各任务的优先级必须互不相同。
14、任务优先级
15、静态优先级
16、动态优先级
17、优先级反转
在这种情况下,任务1优先级实际上降到了任务3 的优先级水平。因为任务1要等,直等到任务3释放占有的那个共享资源。由于任务2剥夺任务3的CPU使用权,使任务1的状况更加恶化,任务2使任务1增加了额外的延迟时间。任务1和任务2的优先级发生了反转。
纠正的方法可以是,在任务3使用共享资源时,提升任务3的优先级。任务完成时予以恢复。任务3的优先级必须升至最高,高于允许使用该资源的任何任务。多任务内核应允许动态改变任务的优先级以避免发生优先级反转现象。然而改变任务的优先级是很花时间的。如果任务3并没有先被任务1剥夺CPU使用权,又被任务2抢走了CPU使用权,花很多时间在共享资源使用前提升任务3的优先级,然后又在资源使用后花时间恢复任务3的优先级,则无形中浪费了很多CPU时间。真正需要的是,为防止发生优先级反转,内核能自动变换任务的优先级,这叫做优先级继承(Priority inheritance)但μC/OS-Ⅱ不支持优先级继承,一些商业内核有优先级继承功能。
18、任务优先级分配
一项有意思的技术可称之为单调执行率调度法RMS(Rate Monotonic Scheduling),用于分配任务优先级。这种方法基于哪个任务执行的次数最频繁,执行最频繁的任务优先级最高。
RMS做了一系列假设:
所有任务都是周期性的
任务间不需要同步,没有共享资源,没有任务间数据交换等问题
CPU必须总是执行那个优先级最高且处于就绪态的任务。换句话说,要使用可剥夺型调度法。
给出一系列n值表示系统中的不同任务数,要使所有的任务满足硬实时条件,必须使不等式[2.1]成立,这就是RMS定理:
表2.1基于任务到CPU最高允许使用率.
任务数 n(21/n – 1)
1 1.000
2 0.828
3 0.779
4 0.756
5 0.743
. .
. .
. .
∞ 0.693
19、互斥条件
关中断
使用测试并置位指令
禁止做任务切换
利用信号量
19.1、关中断和开中断
程序清单2.3 关中断和开中断
Disable interrupts; /*关中断*/
Access the resource (read/write from/to variables); /*读/写变量*/
Reenable interrupts; /*重新允许中断*/
μC/OS-Ⅱ在处理内部变量和数据结构时就是使用的这种手段,即使不是全部,也是绝大部分。实际上μC/OS-Ⅱ提供两个宏调用,允许用户在应用程序的C代码中关中断然后再开中断:OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()[参见8.03.02 OS_ENTER_CRITICAL()和OS_EXIT_CRITICALL()],这两个宏调用的使用法见程序2.4
void Function (void)
{
OS_ENTER_CRITICAL();
.
. /*在这里处理共享数据*/
.
OS_EXIT_CRITICAL();
}
如果使用某种实时内核,一般地说,关中断的最长时间不超过内核本身的关中断时间,就不会影响系统中断延迟。当然得知道内核里中断关了多久。凡好的实时内核,厂商都提供这方面的数据。总而言之,要想出售实时内核,时间特性最重要。
19.2、测试并置位
程序清单2.5 利用测试并置位处理共享资源
Disable interrupts; 关中断
if (‘Access Variable’ is 0) { 如果资源不可用,标志为0
Set variable to 1; 置资源不可用,标志为1
Reenable interrupts; 重开中断
Access the resource; 处理该资源
Disable interrupts; 关中断
Set the ‘Access Variable’ back to 0; 清资源不可使用,标志为0
Reenable interrupts; 重新开中断
} else { 否则
Reenable interrupts; 开中断
/* You don’t have access to the resource, try back later; */
/* 资源不可使用,以后再试; */
}
19.3、禁止,然后允许任务切换
程序清单2.6 用给任务切换上锁,然后开锁的方法实现数据共享.
void Function (void)
{
OSSchedLock();
.
. /* You can access shared data in here (interrupts are recognized) */
. /*在这里处理共享数据(中断是开着的)*/
OSSchedUnlock();
}
19.4、信号量(Semaphores)
控制共享资源的使用权(满足互斥条件)
标志某事件的发生
使两个任务的行为同步
(译者注:信号与信号量在英文中都叫做Semaphore,并不加以区分,而说它有两种类型,二进制型(binary)和计数器型(counting)。本书中的二进制型信号量实际上是只取两个值0和1的信号量。实际上 这个信号量只有一位,这种信号量翻译为信号更为贴切。而二进制信号量通常指若干位的组合。而本书中解释为事件标志的置位与清除(
见2.21))。
信号像是一把钥匙,任务要运行下去,得先拿到这把钥匙。如果信号已被别的任务占用,该任务只得被挂起,直到信号被当前使用者释放。换句话说,申请信号的任务是在说:“把钥匙给我,如果谁正在用着,我只好等!”信号是只有两个值的变量,信号量是计数式的。只取两个值的信号是只有两个值0和1的量,因此也称之为信号量。计数式信号量的值可以是0到255或0到65535,或0到,取决于信号量规约机制使用的是8位、16位还是32位。到底是几位,实际上是取决于用的哪种内核。根据信号量的值,内核跟踪那些等待信号量的任务。
一般地说,对信号量只能实施三种操作:初始化(INITIALIZE),也可称作建立(CREATE);等信号(WAIT)也可称作挂起(PEND);给信号(SIGNAL)或发信号(POST)。信号量初始化时要给信号量赋初值,等待信号量的任务表(Waiting list)应清为空。
想要得到信号量的任务执行等待(WAIT)操作。如果该信号量有效(即信号量值大于0),则信号量值减1,任务得以继续运行。如果信号量的值为0,等待信号量的任务就被列入等待信号量任务表。多数内核允许用户定义等待超时,如果等待时间超过了某一设定值时,该信号量还是无效,则等待信号量的任务进入就绪态准备运行,并返回出错代码(指出发生了等待超时错误)。
任务以发信号操作(SIGNAL)释放信号量。如果没有任务在等待信号量,信号量的值仅仅是简单地加1。如果有任务在等待该信号量,那么就会有一个任务进入就绪态,信号量的值也就不加1。于是钥匙给了等待信号量的诸任务中的一个任务。至于给了那个任务,要看内核是如何调度的。收到信号量的任务可能是以下两者之一。
等待信号量任务中优先级最高的,或者是
最早开始等待信号量的那个任务,即按先进先出的原则(First In First Out ,FIFO)
有的内核有选择项,允许用户在信号量初始化时选定上述两种方法中的一种。但μC/OS-Ⅱ只支持优先级法。如果进入就绪态的任务比当前运行的任务优先级高(假设,是当前任务释放的信号量激活了比自己优先级高的任务)。则内核做任务切换(假设,使用的是可剥夺型内核),高优先级的任务开始运行。当前任务被挂起。直到又变成就绪态中优先级最高任务。
程序清单2.7示意在μC/OS-Ⅱ中如何用信号量处理共享数据。要与同一共享数据打交道的任务调用等待信号量函数OSSemPend()。处理完共享数据以后再调用释放信号量函数OSSemPost()。这两个函数将在以后的章节中描述。要注意的是,在使用信号量之前,一定要对该信号量做初始化。作为互斥条件,信号量初始化为1。使用信号量处理共享数据不增加中断延迟时间,如果中断服务程序或当前任务激活了一个高优先级的任务,高优先级的任务立即开始执行。
程序清单2.7 通过获得信号量处理共享数据
OS_EVENT *SharedDataSem;
void Function (void)
{
INT8U err;
OSSemPend(SharedDataSem, 0, &err);
.
. /* You can access shared data in here (interrupts are recognized) */
. /*共享数据的处理在此进行,(中断是开着的)*/
OSSemPost(SharedDataSem);
}
当诸任务共享输入输出设备时,信号量特别有用。可以想象,如果允许两个任务同时给打印机送数据时会出现什么现象。打印机会打出相互交叉的两个任务的数据。例如任务1要打印“I am Task!”,而任务2要打印“I am Task2!”可能打印出来的结果是:“I Ia amm T Tasask k1!2!”
在这种情况下,使用信号量并给信号量赋初值1(用二进制信号量)。规则很简单,要想使用打印机的任务,先要得到该资源的信号量。图2.10两个任务竞争得到排它性打印机使用权,图中信号量用一把钥匙表示,想使用打印机先要得到这把钥匙。
调用向串行口发送命令的函数CommSendCmd(),该函数有三个形式参数:Cmd指向送出的ASCII码字符串命令。Response指向外设回应的字符串。timeout指设定的时间间隔。如果超过这段时间外设还不响应,则返回超时错误信息。函数的示意代码如程序清单2.8所示。
程序清单 2.8 隐含的信号量。
INT8U CommSendCmd(char *cmd, char *response, INT16U timeout)
{
Acquire port’s semaphore;
Send command to device;
Wait for response (with timeout);
if (timed out) {
Release semaphore;
return (error code);
} else {
Release semaphore;
return (no error);
}
}
要向外设发送命令的任务得调用上述函数。设信号量初值为1,表示允许使用。初始化是在通讯口驱动程序的初始化部分完成的。第一个调用CommSendCmd()函数的任务申请并得到了信号量,开始向外设发送命令并等待响应。而另一个任务也要送命令,此时外设正“忙”,则第二个任务被挂起,直到该信号量重新被释放。第二个任务看起来同调用了一个普通函数一样,只不过这个函数在没有完成其相应功能时不返回。当第一个任务释放了那个信号量,第二个任务得到了该信号量,第二个任务才能使用RS-232口。
程序清单 2.9 用信号量管理缓冲区。
BUF *BufReq(void)
{
BUF *ptr;
Acquire a semaphore;
Disable interrupts;
ptr = BufFreeList;
BufFreeList = ptr->BufNext;
Enable interrupts;
return (ptr);
}
void BufRel(BUF *ptr)
{
Disable interrupts;
ptr->BufNext = BufFreeList;
BufFreeList = ptr;
Enable interrupts;
Release semaphore;
}
信号量常被用过了头。处理简单的共享变量也使用信号量则是多余的。请求和释放信号量的过程是要花相当的时间的。有时这种额外的负荷是不必要的。用户可能只需要关中断、开中断来处理简单共享变量,以提高效率。(参见2.18.0.1 关中断和开中断)。假如两个任务共享一个32位的整数变量,一个任务给这个变量加1,另一个任务给这个变量清0。如果注意到不管哪种操作,对微处理器来说,只花极短的时间,就不会使用信号量来满足互斥条件了。每个任务只需操作这个任务前关中断,之后再开中断就可以了。然而,如果这个变量是浮点数,而相应微处理器又没有硬件的浮点协处理器,浮点运算的时间相当长,关中断时间长了会影响中断延迟时间,这种情况下就有必要使用信号量了。
20、死锁(或抱死)(Deadlock (or Deadly Embrace))
先得到全部需要的资源再做下一步的工作
用同样的顺序去申请多个资源
释放资源时使用相反的顺序
内核大多允许用户在申请信号量时定义等待超时,以此化解死锁。当等待时间超过了某一确定值,信号量还是无效状态,就会返回某种形式的出现超时错误的代码,这个出错代码告知该任务,不是得到了资源使用权,而是系统错误。死锁一般发生在大型多任务系统中,在嵌入式系统中不易出现。
21、同步
发信号给等待事件发生的任务中优先级最高的任务,或者
发信号给最先开始等待事件发生的那个任务
根据不同的应用,发信号以标识事件发生的中断服务或任务也可以是多个。
两个任务可以用两个信号量同步它们的行为。如图2.14所示。这叫做双向同步(bilateral rendezvous)。双向同步同单向同步类似,只是两个任务要相互同步。
例如则程序清单2.10中,运行到某一处的第一个任务发信号给第二个任务[L22.10(1)],然后等待信号返回[L2.10(2)]。同样,当第二个任务运行到某一处时发信号给第一个任务[2.10(3)]等待返回信号[L2.10(4)]。至此,两个任务实现了互相同步。在任务与中断服务之间不能使用双向同步,因为在中断服务中不可能等一个信号量。
Task1()
{
for (;;) {
Perform operation;
Signal task #2; (1)
Wait for signal from task #2; (2)
Continue operation;
}
}
Task2()
{
for (;;) {
Perform operation;
Signal task #1; (3)
Wait for signal from task #1; (4)
Continue operation;
}
}
22、事件标志(Event Flags)
内核支持事件标志,提供事件标志置位、事件标志清零和等待事件标志等服务。事件标志可以是独立型或组合型。μC/OS-Ⅱ目前不支持事件标志.
23、任务间的通讯(Intertask Communication)
用全程变量时,必须保证每个任务或中断服务程序独享该变量。中断服务中保证独享的唯一办法是关中断。如果两个任务共享某变量,各任务实现独享该变量的办法可以是关中断再开中断,或使用信号量(如前面提到的那样)。请注意,任务只能通过全程变量与中断服务程序通讯,而任务并不知道什么时候全程变量被中断服务程序修改了,除非中断程序以信号量方式向任务发信号或者是该任务以查询方式不断周期性地查询变量的值。要避免这种情况,用户可以考虑使用邮箱或消息队列。
24、消息邮箱(Message Mail boxes)
通过内核服务可以给任务发送消息。典型的消息邮箱也称作交换消息,是用一个指针型变量,通过内核服务,一个任务或一个中断服务程序可以把一则消息(即一个指针)放到邮箱里去。同样,一个或多个任务可以通过内核服务接收这则消息。发送消息的任务和接收消息的任务约定,该指针指向的内容就是那则消息。
每个邮箱有相应的正在等待消息的任务列表,要得到消息的任务会因为邮箱是空的而被挂起,且被记录到等待消息的任务表中,直到收到消息。一般地说,内核允许用户定义等待超时,等待消息的时间超过了,仍然没有收到该消息,这任务进入就绪态,并返回出错信息,报告等待超时错误。消息放入邮箱后,或者是把消息传给等待消息的任务表中优先级最高的那个任务(基于优先级),或者是将消息传给最先开始等待消息的任务(基于先进先出)。图2.17示意把消息放入邮箱。用一个I字表示邮箱,旁边的小砂漏表示超时计时器,计时器旁边的数字表示定时器设定值,即任务最长可以等多少个时钟节拍(Clock Ticks),关于时钟节拍以后会讲到。
内核一般提供以下邮箱服务:
邮箱内消息的内容初始化,邮箱里最初可以有,也可以没有消息
将消息放入邮箱(POST)
等待有消息进入邮箱(PEND)
如果邮箱内有消息,就接受这则消息。如果邮箱里没有消息,则任务并不被挂起(ACCEPT),用返回代码表示调用结果,是收到了消息还是没有收到消息。
消息邮箱也可以当作只取两个值的信号量来用。邮箱里有消息,表示资源可以使用,而空邮箱表示资源已被其它任务占用。
25、消息队列(Message Queue)
像使用邮箱那样,当一个以上的任务要从消息队列接收消息时,每个消息队列有一张等待消息任务的等待列表(Waiting List)。如果消息队列中没有消息,即消息队列是空,等待消息的任务就被挂起并放入等待消息任务列表中,直到有消息到来。通常,内核允许等待消息的任务定义等待超时的时间。如果限定时间内任务没有收到消息,该任务就进入就绪态并开始运行,同时返回出错代码,指出出现等待超时错误。一旦一则消息放入消息队列,该消息将传给等待消息的任务中优先级最高的那个任务,或是最先进入等待消息任务列表的任务。图2.18示意中断服务子程序如何将消息放入消息队列。图中两个大写的I表示消息队列,“10”表示消息队列最多可以放10条消息,沙漏旁边的0表示任务没有定义超时,将永远等下去,直至消息的到来。
典型地,内核提供的消息队列服务如下:
消息队列初始化。队列初始化时总是清为空。
放一则消息到队列中去(Post)
等待一则消息的到来(Pend)
如果队列中有消息则任务可以得到消息,但如果此时队列为空,内核并不将该任务挂起(Accept)。如果有消息,则消息从队列中取走。没有消息则用特别的返回代码通知调用者,队列中没有消息。
26、中断
在前后台系统中,程序回到后台程序
对不可剥夺型内核而言,程序回到被中断了的任务
对可剥夺型内核而言,让进入就绪态的优先级最高的任务开始运行
中断使得CPU可以在事件发生时才予以处理,而不必让微处理器连续不断地查询(Polling)是否有事件发生。通过两条特殊指令:关中断(Disable interrupt)和开中断(Enable interrupt)可以让微处理器不响应或响应中断。在实时环境中,关中断的时间应尽量的短。关中断影响中断延迟时间(见2.26中断延迟)。关中断时间太长可能会引起中断丢失。微处理器一般允许中断嵌套,也就是说在中断服务期间,微处理器可以识别另一个更重要的中断,并服务于那个更重要的中断,如图2.19所示。
27、中断延迟
[2.2] 中断延迟 = 关中断的最长时间 + 开始执行中断服务子程序的第一条指令的时间
28、中断响应
对前后台系统,保存寄存器以后立即执行用户代码,中断响应时间由[2.3]给出。
[2.3] 中断响应时间 = 中断延迟 + 保存CPU内部寄存器的时间
对于不可剥夺型内核,微处理器保存内部寄存器以后,用户的中断服务子程序代码全立即得到执行。不可剥夺型内核的中断响应时间由表达式[2.4]给出。
[2.4] 中断响应时间 = 中断延迟 + 保存CPU内部寄存器的时间
对于可剥夺型内核,则要先调用一个特定的函数,该函数通知内核即将进行中断服务,使得内核可以跟踪中断的嵌套。对于 μC/OS-Ⅱ说来,这个函数是OSIntEnter(),可剥夺型内核的中断响应时间由表达式[2.5]给出:
[2.5] 中断响应 = 中断延迟 + 保存CPU内部寄存器的时间 + 内核的进入中断服务函数的执行时间
中断响应是系统在最坏情况下的响应中断的时间,某系统100次中有99次在50μs之内响应中断,只有一次响应中断的时间是250μs,只能认为中断响应时间是250μs。
29、中断恢复时间(Interrupt Recovery)
[2.6] 中断恢复时间 = 恢复CPU内部寄存器值的时间 + 执行中断返回指令的时间
和前后台系统一样,不可剥夺型内核的中断恢复时间也很简单,只包括恢复CPU内部寄存器值的时间和执行中断返回指令的时间,如表达式[2.7]所示。
[2.7] 中断恢复时间 = 恢复CPU内部寄存器值的时间 + 执行中断返回指令的时间
对于可剥夺型内核,中断的恢复要复杂一些。典型地,在中断服务子程序的末尾,要调用一个由实时内核提供的函数。在μC/OS-Ⅱ中,这个函数叫做OSIntExit(),这个函数用于辨定中断是否脱离了所有的中断嵌套。如果脱离了嵌套(即已经可以返回到被中断了的任务级时),内核要辨定,由于中断服务子程序ISR的执行,是否使得一个优先级更高的任务进入了就绪态。如果是,则要让这个优先级更高的任务开始运行。在这种情况下,被中断了的任务只有重新成为优先级最高的任务而进入就绪态时才能继续运行。对于可剥夺型内核,中断恢复时间由表达式[2.8]给出。
[2.8] 中断恢复时间 = 判定是否有优先级更高的任务进入了就绪态的时间 + 恢复那个优先级更高任务的CPU内部寄存器的时间 + 执行中断返回指令的时间
30、中断延迟、响应和恢复
图2.20到图2.22示意前后台系统、不可剥夺性内核、可剥夺性内核相应的中断延迟、响应和恢复过程。
注意,对于可剥夺型实时内核,中断返回函数将决定是返回到被中断的任务[图2.22A],还是让那个优先级最高任务运行。是中断服务子程序使那个优先级更高的任务进入了就绪态[图2.22B]。在后一种情况下,恢复中断的时间要稍长一些,因为内核要做任务切换。在本书中,我做了一张执行时间表,此表多少可以衡量执行时间的不同,假定μC/OS-Ⅱ是在33MHZ Intel 80186微处理器上运行的。此表可以使读者看到做任务切换的时间开销。(见表9.3,在33MHZ 80186上μC/OS-Ⅱ服务的执行时间).
31、 中断处理时间
32、非屏蔽中断(NMI)
[2.9] 中断延迟时间 = 指令执行时间中最长的那个时间 + 开始做非屏蔽中断服务的时间
[2.10] 中断响应时间 = 中断延迟时间 + 保存CPU寄存器花的时间
[2.11] 中断恢复时间 = 恢复CPU寄存器的时间 + 执行中断返回指令的时间。
在一项应用中,我将非屏蔽中断用于可能每150μS发生一次的中断。中断处理时间在80至125μS之间。所使用的内核的关中断时间是45μS。可以看出,如果使用可屏蔽中断的话,中断响应会推迟20μS。
33、时钟节拍(Clock Tick)
各种实时内核都有将任务延时若干个时钟节拍的功能。然而这并不意味着延时的精度是1个时钟节拍,只是在每个时钟节拍中断到来时对任务延时做一次裁决而已。
图2.25到 图2.27示意任务将自身延迟一个时钟节拍的时序。阴影部分是各部分程序的执行时间。请注意,相应的程序运行时间是长短不一的,这反映了程序中含有循环和条件转移语句(即if/else, switch, ? : 等语句)的典型情况。时间节拍中断服务子程序的运行时间也是不一样的。尽管在图中画得有所夸大。
第一种情况如图2.25所示,优先级高的任务和中断服务超前于要求延时一个时钟节拍的任务运行。可以看出,虽然该任务想要延时20mS,但由于其优先级的缘故,实际上每次延时多少是变化的,这就引起了任务执行时间的抖动。
第二种情况,如图2.26所示,所有高优先级的任务和中断服务的执行时间略微小于一个时钟节拍。如果任务将自己延时一个时钟节拍的请求刚好发生在下一个时钟节拍之前,这个任务的再次执行几乎是立即开始的。因此,如果要求任务的延迟至少为一个时钟节拍的话,则要多定义一个延时时钟节拍。换句话说,如果想要将一个任务至少延迟5个时钟节拍的话,得在程序中延时6个时钟节拍。
上述情况在所有的实时内核中都会出现,这与CPU负荷有关,也可能与系统设计不正确有关。以下是这类问题可能的解决方案:
增加微处理器的时钟频率
增加时钟节拍的频率
重新安排任务的优先级
避免使用浮点运算(如果非使用不可,尽量用单精度数)
使用能较好地优化程序代码的编译器
时间要求苛刻的代码用汇编语言写
如果可能,用同一家族的更快的微处理器做系统升级。如从8086向80186升级,从68000向68020升级等
不管怎么样,抖动总是存在的。
34、对存储器的需求
[2.12] 总代码量 = 应用程序代码 + 内核代码
因为每个任务都是独立运行的,必须给每个任务提供单独的栈空间(RAM)。应用程序设计人员决定分配给每个任务多少栈空间时,应该尽可能使之接近实际需求量 (有时,这是相当困难的一件事)。栈空间的大小不仅仅要计算任务本身的需求 (局部变量、函数调用等等),还需要计算最多中断嵌套层数(保存寄存器、中断服务程序中的局部变量等)。根据不同的目标微处理器和内核的类型,任务栈和系统栈可以是分开的。系统栈专门用于处理中断级代码。这样做有许多好处,每个任务需要的栈空间可以大大减少。内核的另一个应该具有的性能是,每个任务所需的栈空间大小可以分别定义(µC/OS-II可以做到)。相反,有些内核要求每个任务所需的栈空间都相同。所有内核都需要额外的栈空间以保证内部变量、数据结构、队列等。如果内核不支持单独的中断用栈,总的RAM需求由表达式[2.13]给出。
[2.13] RAM总需求 = 应用程序的RAM需求 + (任务栈需求 + 最大中断嵌套栈需求) * 任务数
如果内核支持中断用栈分离,总RAM需求量由表达式[2.14]给出
[2.14]=RAM总需求 = 应用程序的RAM需求 + 内核数据区的RAM需求 + 各任务栈需求之总和 + 最多中断嵌套之栈需求
除非有特别大的RAM空间可以所用,对栈空间的分配与使用要非常小心。为减少应用程序需要的RAM空间,对每个任务栈空间的使用都要非常小心,特别要注意以下几点:
定义函数和中断服务子程序中的局部变量,特别是定义大型数组和数据结构
函数(即子程序)的嵌套
中断嵌套
库函数需要的栈空间
多变元的函数调用
35、使用实时内核的优缺点
如果应用项目对额外的需求可以承受,应该考虑使用实时内核。这些额外的需求是:内核的价格,额外的ROM/RAM开销,2到4百分点的CPU额外负荷。
还没有提到的一个因素是使用实时内核增加的价格成本。在一些应用中,价格就是一切,以至于对使用RTOS连想都不敢想。
当今有80个以上的RTOS商家,生产面向8位、16位、32位、甚至是64位的微处理器的RTOS产品。一些软件包是完整的操作系统,不仅包括实时内核,还包括输入输出管理、视窗系统(用于显示)、文件系统、网络、语言接口库、调试软件、交叉平台编译(Cross-Platform compilers)。RTOS的价格从70美元到30,000美元。RTOS制造商还可能索取每个目标系统的版权使用费。就像从RTOS商家那买一个芯片安装到每一个产品上,然后一同出售。RTOS商家称之为硅片软件(Silicon Software)。每个产品的版权费从5美元到250美元不等。同如今的其它软件包一样,还得考虑软件维护费,这部分开销为每年还得花100到5,000美元!
36、实时系统小结
Background Non-Preemptive Kernel Preemptive Kernel
Interrupt latency (Time) MAX(Longest instruction,
User int. disable)
+ Vector to ISR MAX(Longest instruction,
User int. disable,
Kernel int. disable)
+ Vector to ISR MAX(Longest instruction,
User int. disable,
Kernel int. disable)
+ Vector to ISR
Interrupt response (Time) Int. latency
+ Save CPU’s context Int. latency
+ Save CPU’s context Interrupt latency
+ Save CPU’s context
+ Kernel ISR entry function
Interrupt recovery (Time) Restore background’s
context
+ Return from int. Restore task’s context
+ Return from int. Find highest priority task
+ Restore highest priority
task’s context
+ Return from interrupt
Task response (Time) Background Longest task
+ Find highest priority task
+ Context switch Find highest priority task
+ Context switch
ROM size Application code Application code
+ Kernel code Application code
+ Kernel code
RAM size Application code Application code
+ Kernel RAM
+ SUM(Task stacks
+ MAX(ISR stack)) Application code
+ Kernel RAM
+ SUM(Task stacks
+ MAX(ISR stack))
Services available? Application code must
provide Yes Yes
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/133889.html