打印

探讨: OS 和看门狗的问题

[复制链接]
6778|26
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
Airwill|  楼主 | 2009-2-17 08:46 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
沙发
hotpower| | 2009-2-17 08:51 | 只看该作者

坐沙发听“阶级敌人”讲故事~~~

使用特权

评论回复
板凳
hotpower| | 2009-2-17 09:00 | 只看该作者

转帖网文来热身~~~在单片机中嵌入操作系统的利弊

在单片机中嵌入操作系统的利弊早在20世纪60年代,就已经有人开始研究和开发嵌入式操作系统。但直到最近,它才在国内被越来越多的提及,在通信、电子、自动化等需要实时处理的领域所日益显现的重要性吸引了人们越来越多的注意力。但是,人们所谈论的往往是一些著名的商业内核,诸如VxWorks、PSOS等。这些商业内核性能优越,但价格昂贵,主要用于16位和32位处理器中,针对国内大部分用户使用的51系列8位单片机,可以选择免费的uC/OS-II。


uC/OS-II的特点

  1.uC/OS-II是由Labrosse先生编写的一个开放式内核,最主要的特点就是源码公开。这一点对于用户来说可谓利弊各半,好处在于,一方面它是免费的,另一方面用户可以根据自己的需要对它进行修改。缺点在于它缺乏必要的支持,没有功能强大的软件包,用户通常需要自己编写驱动程序,特别是如果用户使用的是不太常用的单片机,还必须自己编写移植程序。

  2.uC/OS-II是一个占先式的内核,即已经准备就绪的高优先级任务可以剥夺正在运行的低优先级任务的CPU使用权。这个特点使得它的实时性比非占先式的内核要好。通常我们都是在中断服务程序中使高优先级任务进入就绪态(例如发信号),这样退出中断服务程序后,将进行任务切换,高优先级任务将被执行。拿51单片机为例,比较一下就可以发现这样做的好处。假如需要用中断方式采集一批数据并进行处理,在传统的编程方法中不能在中断服务程序中进行复杂的数据处理,因为这会使得关中断时间过长。所以经常采用的方法是置一标志位,然后退出中断。由于主程序是循环执行的,所以它总有机会检测到这一标志并转到数据处理程序中去。但是因为无法确定发生中断时程序到底执行到了什么地方,也就无法判断要经过多长时间数据处理程序才会执行,中断响应时间无法确定,系统的实时性不强。如果使用uC/OS-II的话,只要把数据处理程序的优先级设定得高一些,并在中断服务程序中使它进入就绪态,中断结束后数据处理程序就会被立即执行。这样可以把中断响应时间限制在一定的范围内。对于一些对中断响应时间有严格要求的系统,这是必不可少的。但应该指出的是如果数据处理程序简单,这样做就未必合适。因为uC/OS-II要求在中断服务程序末尾使用OSINTEXIT函数以判断是否进行任务切换,这需要花费一定的时间。

  3.uC/OS-II和大家所熟知的Linux等分时操作系统不同,它不支持时间片轮转法。uC/OS-II是一个基于优先级的实时操作系统,每个任务的优先级必须不同,分析它的源码会发现,uC/OS-II把任务的优先级当做任务的标识来使用,如果优先级相同,任务将无法区分。进入就绪态的优先级最高的任务首先得到CPU的使用权,只有等它交出CPU的使用权后,其他任务才可以被执行。所以它只能说是多任务,不能说是多进程,至少不是我们所熟悉的那种多进程。显而易见,如果只考虑实时性,它当然比分时系统好,它可以保证重要任务总是优先占有CPU。但是在系统中,重要任务毕竟是有限的,这就使得划分其他任务的优先权变成了一个让人费神的问题。另外,有些任务交替执行反而对用户更有利。例如,用单片机控制两小块显示屏时,无论是编程者还是使用者肯定希望它们同时工作,而不是显示完一块显示屏的信息以后再显示另一块显示屏的信息。这时候,要是uC/OS-II即支持优先级法又支持时间片轮转法就更合适了。

  4.uC/OS-II对共享资源提供了保护机制。正如上文所提到的,uC/OS-II是一个支持多任务的操作系统。一个完整的程序可以划分成几个任务,不同的任务执行不同的功能。这样,一个任务就相当于模块化设计中的一个子模块。在任务中添加代码时,只要不是共享资源就不必担心互相之间有影响。而对于共享资源(比如串口),uC/OS-II也提供了很好的解决办法。一般情况下使用的是信号量的方法。简单地说,先创建一个信号量并对它进行初始化。当一个任务需要使用一个共享资源时,它必须先申请得到这个信号量,而一旦得到了此信号量,那就只有等使用完了该资源,信号量才会被释放。在这个过程中即使有优先权更高的任务进入了就绪态,因为无法得到此信号量,也不能使用该资源。这个特点的好处显而易见,例如当显示屏正在显示信息的时候,外部产生了一个中断,而在中断服务程序中需要显示屏显示其他信息。这样,退出中断服务程序后,原有的信息就可能被破坏了。而在uC/OS-II中采用信号量的方法时,只有显示屏把原有信息显示完毕后才可以显示新信息,从而可以避免这个现象。不过,采用这种方法是以牺牲系统的实时性为代价的。如果显示原有信息需要耗费大量时间,系统只好等待。从结果上看,等于延长了中断响应时间,这对于未显示信息是报警信息的情况,无疑是致命的。发生这种情况,在uC/OS-II中称为优先级反转,就是高优先级任务必须等待低优先级任务的完成。在上述情况下,在两个任务之间发生优先级反转是无法避免的。所以在使用uC/OS-II时,必须对所开发的系统了解清楚,才能决定对于某种共享资源是否使用信号量。


uC/OS-II在单片机使用中的一些特点
  1.在单片机系统中嵌入uC/OS-II将增强系统的可靠性,并使得调试程序变得简单。以往传统的单片机开发工作中经常遇到程序跑飞或是陷入死循环。可以用看门狗解决程序跑飞问题,而对于后一种情况,尤其是其中牵扯到复杂数学计算的话,只有设置断点,耗费大量时间来慢慢分析。如果在系统中嵌入uC/OS-II的话,事情就简单多了。可以把整个程序分成许多任务,每个任务相对独立,然后在每个任务中设置超时函数,时间用完以后,任务必须交出CPU的使用权。即使一个任务发生问题,也不会影响其他任务的运行。这样既提高了系统的可靠性,同时也使得调试程序变得容易。

  2.在单片机系统中嵌入uC/OS-II将增加系统的开销。现在所使用的51单片机,一般是指87C51或者89C51,其片内都带有一定的RAM和ROM。对于一些简单的程序,如果采用传统的编程方法,已经不需要外扩存储器了。如果在其中嵌入uC/OS-II的话,在只需要使用任务调度、任务切换、信号量处理、延时或超时服务的情况下,也不需要外扩ROM了,但是外扩RAM是必须的。由于uC/OS-II是可裁减的操作系统,其所需要的RAM大小就取决于操作系统功能的多少。举例来说,uC/OS-II允许用户定义最大任务数。由于每建立一个任务,都要产生一个与之相对应的数据结构TCB,该数据结构要占用很大一部分内存空间。所以在定义最大任务数时,一定要考虑实际情况的需要。如果定得过大,势必会造成不必要的浪费。嵌入uC/OS-II以后,总的RAM需求可以由如下表达式得出:

  RAM总需求=应用程序的RAM需求+内核数据区的RAM需求+(任务栈需求+最大中断嵌套栈需求)·任务数
所幸的是,uC/OS-II可以对每个任务分别定义堆栈空间的大小,开发人员可根据任务的实际需求来进行栈空间的分配。但在RAM容量有限的情况下,还是应该注意一下对大型数组、数据结构和函数的使用,别忘了,函数的形参也是要推入堆栈的。

  3.uC/OS-II的移植也是一件需要值得注意的工作。如果没有现成的移植实例的话,就必须自己来编写移植代码。虽然只需要改动两个文件,但仍需要对相应的微处理器比较熟悉才行,最好参照已有的移植实例。另外,即使有移植实例,在编程前最好也要阅读一下,因为里面牵扯到堆栈操作。在编写中断服务程序时,把寄存器推入堆栈的顺序必须与移植代码中的顺序相对应。 

  4.和其他一些著名的嵌入式操作系统不同,uC/OS-II在单片机系统中的启动过程比较简单,不像有些操作系统那样,需要把内核编译成一个映像文件写入ROM中,上电复位后,再从ROM中把文件加载到RAM中去,然后再运行应用程序。uC/OS-II的内核是和应用程序放在一起编译成一个文件的,使用者只需要把这个文件转换成HEX格式,写入ROM中就可以了,上电后,会像普通的单片机程序一样运行。 

结语
  由以上介绍可以看出,uC/OS-II具有免费、使用简单、可靠性高、实时性好等优点,但也有移植困难、缺乏必要的技术支持等缺点,尤其不像商用嵌入式系统那样得到广泛使用和持续的研究更新。但开放性又使得开发人员可以自行裁减和添加所需的功能,在许多应用领域发挥着独特的作用。当然,是否在单片机系统中嵌入uC/OS-II应视所开发的项目而定,对于一些简单的、低成本的项目来说,就没必要使用嵌入式操作系统了。

使用特权

评论回复
地板
hotpower| | 2009-2-17 09:02 | 只看该作者

改进uC/OS II,减少内存使用量(转帖)

改进uC/OS II,减少内存使用量 
在以uC/OS为操作系统的项目中,系统可能要处理各种不同的中断请求,如果某个中断处理程序需要调用uC/OS的各种Post函数向任务发出消息,那么uC/OS建议中断服务程序的写法是:
1、保存全部CPU寄存器
2、调用OSIntEnter或OSIntNesting直接加1
3、执行用户代码做中断服务
4、调用OSIntExit
5、恢复所有CPU寄存器
6、执行中断返回指令
暂且称为“标准中断”方式,这种方式实际上是将这个中断处理加入了任务调度系统,也就是说这个中断可以引起任务的切换。
如果在中断处理中没有调用各种Post函数的话,则可以用一般的、象原来没有操作系统时的写法:
1、保存中断处理程序需要用到的CPU寄存器
2、执行中断处理
3、恢复保存了的CPU寄存器
4、执行中断返回指令
暂且称为“快中断”方式,按照这种方法定义的中断永远不会引起任务切换。
在uC/OS系统中,每个任务都要定义独立的栈空间,一个栈空间的使用包括5个部分:
1、任务包括的各个函数的调用返回地址
2、任务包括的各个函数中可能在栈上分配的局部变量
3、发生了“标准中断”方式定义的中断或任务被挂起时,所要保存的任务上下文
4、发生了“快中断”方式定义的中断时,中断处理程序所需要的栈空间
5、中断嵌套时,所要保存的中断嵌套上下文
在这些使用的部分中,1,2,3,4的内存占用量是比较容易估算的,最精确和保险的确定方法是:查看由C生成的asm文件,并计算各个函数的栈使用量。但是第5部分的栈空间使用量是随中断嵌套的深度而不断增加的,是不确定的,一般的方法只能定义一个充分大的栈空间,使之不会溢出。
为每个任务都定义一个充分大的栈空间,这在某些内存稀缺的小项目中是非常痛苦的,有时不得不增扩内存,这就会使成本增加。
我深入研究了uC/OS后,认为,可以将所有任务栈空间使用的第5部分合并,这样将会大大的降低整个系统对内存的需求。
uC/OS的任务调度是靠OS_Sched和OSIntExit来完成的,这两个函数中都要先判断一个叫 OSIntNesting的系统变量,如果OSIntNesting不为0,则不进行任务切换。也就是说:在OSIntNesting为1(当前只有一个中断在处理中,并且没有嵌套的中断)时起,如果发生了嵌套的中断(不管嵌套的层数有深),那么在所有嵌套的中断一层一层地都返回直到 OSIntNesting再次为1时止,任务栈是不会切换的(栈指针都在一个任务的栈空间中变化)。
据此,我们可以这样改动:设置一个缓冲区OSInterruptStk,作为嵌套中断的栈空间,由所有任务共享,中断服务程序改为:
1、保存全部CPU寄存器
2、调用OSIntEnter或OSIntNesting直接加1
增加:2.1、判断OSIntNesting是否等于1,如果不是则转到3
增加:2.2、将栈指针SP保存到OSTCBCur->OSTCBStkPtr
增加:2.3、将SP指向OSInterruptStk的栈顶(注意栈增长的方向)。
3、执行用户代码做中断服务
4、调用OSIntExit
增加:4.1、判断OSIntNesting是否等于0,如果不是则转到5
增加:4.2、从OSTCBCur->OSTCBStkPtr中恢复栈指针SP
5、恢复所有CPU寄存器
6、执行中断返回指令
并且要修改OSIntCtxSw函数,原始的OSIntCtxSw函数的写法是:
1、调整栈指针来去掉在调用:OSIntExit,OSIntCtxSw过程中入栈的多余内容
2、将当前任务栈指针保存到OSTCBCur中(OSTCBCur->OSTCBStkPtr = __SP__)
3、如果需要则调用OSTaskSwHook
4、OSTCBCur = OSTCBHighRdy
5、OSPrio = OSPrioHighRdy
6、从OSTCBCur中恢复栈指针(__SP__ = OSTCBCur->OSTCBStkPtr)
7、恢复保存了的CPU寄存器
8、执行中断返回指令
新的写法只需将原写法中的1,2去掉即可,因为1,2步只是保存旧任务的栈指针,而新的写法中,这些步被移到了“中断服务程序”中的2.2。
以上的修改已在我的项目中验证通过了

使用特权

评论回复
5
hotpower| | 2009-2-17 09:03 | 只看该作者

uC/OS-II任务栈处理的一种改进方法(转帖)


uC/OS-II任务栈处理的一种改进方法
摘要:在μC/OS-II内核中,各个不同的任务使用独立的堆栈空间,堆栈的大小按每个任务所需要的最大堆栈深度来定义,这种方法可能会造成堆栈空间浪费。本文叙述如何在RTOS中多个任务共用连续存储空间作为任务栈的方法,并详细比较二者的优缺点和适用性。 
    关键词:μC/OS-II 任务堆栈 RTOS 共用空间堆栈
关于μC/OS-II这个实时内核及其应用已经有很多**介绍了,对于学习RTOS的人来说,这个系统是很好的学习起点。虽然文献[1]的源代码没有行号和函数名交叉索引表等,给源代码阅读造成一些困难(可使用BC31的grep查找功能,提高阅读效率),好在代码不是很长,前面又有详细的中文说明,对于有一定X86汇编和C语言基础的人来说,仍然可以在不长的时间内掌握。
μC/OS-II内核是一个抢先式内核,可以进行任务间切换,也可以让一个任务在得不到某个资源时休眠一定时间后再继续运行;提供了用于共享资源管理的信号灯,用于进程通信的消息队列和邮箱,甚至提供了存储器管理机制,一个比较全面的系统。
μC/OS-II内核有些地方仍然值得改进,比如该系统不支持时间片调度。如果有一个任务中一段死循环代码(或者条件循环代码),代码就会永远(或长时间)在此处执行,调度程序无法控制,其它任务也就是不到及时执行。这种抢先式实际上和非抢先式系统存在着同样问题。当然,如果这种代码不一个BUG,问题是可以解决的,在不提供时间片调度的抢先式系统中,一般采取信号灯,或者任务主动休眠的方法(对于μC/OS-II,很容易改造成支持时间片调度,只要在定时中断服务程序调用OSIntCtxSw()函数即可);非抢先式系统一般采取有限状态机方法,不使用这种耗时很长的循环代码。不过,无论如何,对RTOS的使用者来说,这毕竟会使得任务函数的编码不能随心所欲。
ΜC/OS-II内核的另外一个值得改进的地方就是其任务栈管理方法。在μC/OS-II内核中,各个不同的任务使用独立的堆栈空间,堆栈的大小按每个任务所需要的最大堆栈深度来定义,这种方法可能会造成堆栈空间的浪费。下面讨论如何在RTOS中多个任务共用一段连续存储空间作为傻堆栈。

1 任务切换要保存的数据

简单地说,一个任务可看作一个运行中的C函数。对于抢先式RTOS来说,在任务切换时,应保存当前任务的各种现场数据。现场数据包括局部变量、各个CPU寄存器、堆栈指针和程序被中止的任务指针。CPU寄存器是任何任务代码均会用到的;而局部变量,一般的编译器是将其它安排在堆栈空间中,堆栈指针也是各任务公用的,所以也需要保存。
对于全局变量,由于一般是在内存中的固定位置,各任务所占用的空间完全独立,所以不需要保存。
在X86环境中,要保存的CPU寄存器共14个16位寄存器;通用寄存器8个(AX、BX、CX、DX、SP、BP、SI、BI)、段寄存器4个(CS、DS、ES、SS)以及指令指针IP和标志寄存器FR各1个。
2 C编译器中变量在堆栈中的位置
对于一个存在函数调用嵌套的C程序来说,大部分编译器将传递的参数和函数本身的局部变量放在了堆栈中,编译器会自动生成压栈(push)和弹栈(pop)代码,以保存上级函数的运行寄存器。
假设函数main()调用funl(),而funl()调用fun2(),则在执行fun2()中的代码时,堆栈映像如图1所示(X86 CPU的情况)。
对于RTOS软件,堆栈中的各种数据就是一个任务的作现场。一般CPU的堆栈指针SP只有一个,在进行任务切换时,必须将挂起任务所使用的堆栈内容保存起来,以便使该任务在下次唤醒时能从原地继续运行。
3 μC/OS-II对任务栈的处理方法与缺陷
μC/OS-II为了保存任务堆栈中的数据,对每个任务定义一个数组变量作为堆栈,在任务切换时,将CPU堆栈指针SP指向该数组中的某个元素,即栈顶,如图2所示。
比如,在其ex21.c文件中定义的任务堆栈语句为:
OS_STK TaskStartStk[TASK_STK_SIZE]; /*启动任务堆栈*/
OS_STK TaskClkStk[TASK_STK_SIZE]; /*时钟任务堆栈*/
OS_STK TasklStk[TASK_STK_SIZE]; /*任务1#,任务堆栈*/
……
以上各任务堆栈数组变量在初始化函数OSTCBInit()中被会给了任务控制块OS_TCB的OSTCBStkPtr变量。在任务切换时,μC/OS-II调用OSCtxSw汇编过程(OS_CPU_A.ASM文件),将CPU的SP指针指向该变量,从而使每个任务使用独立的任务堆栈。
LES BX,DWORD PTR DS:_OSTCBCur
;保存挂起任务的堆栈指针SP
MOV ES:[BX+2],SS
MOV ES:[BX+0],SP
……
LESB X,DWORD PTR DS:_OSTCBHighRdy ;切换SP到要运行任务的堆栈空间
MOV SS,ES:[BX+2]
MOV SP,ES:[BX]
……

    在代码中,变量OSTCBHighRdy(OSTCBCur)和堆栈指针变量OSTCBStkPtr的数值是同同的,因为OSTCBStkPtr是结构OSTCBHighRdy的第一个变量。
这种任务栈处理方法的缺点是可能造成空间的浪费。因为一个任务如果堆栈满了,该任务也就无法运行,即使其它任务的堆栈还有空间可用。当然,这种方法的好处是任务栈切换的时间非常短,只需要几条指令。
4 共用空间的堆栈处理方法
(1)栈共用连续存储空间
如果多个任务使用同一段连续空间作为堆栈,这样各个堆栈之间就可以互补使用。在前面说过,共用空间的问题在于一个任务运行时不能破坏其它任务的堆栈数据。为简单起见,先看图3所示两个任务的情况。
假定任务1首次运行时任务栈为空。运行一段时间后任务2运行,堆栈空间继续往上生长。这次任务切换不需要修改CPU的SP数值,但需要记下任务1的栈顶位置SP1(图3中)。
在任务2运行一段时间后,RTOS又切换到任务1运行。在切换时,不能简单地将SP指针修改回SP1的数值,因为这样堆栈向上生长时会破坏任务2堆栈中的数据。办法是将原来任1务堆栈保存的数据移动到靠栈顶的位置,而将任务2堆栈数据下移到靠栈底的位置,堆栈指针SP实际上不需要修改(图3右)。
考虑到更为一般的情况,有N个任务,当前运行的任务为k,下一个运行的任务为j,在共用任务堆栈时必须做的工作有:
*为每个任务定义栈顶和栈底2个堆栈指针;
*在任务切换时,将待运行任务j的堆栈内容移动到靠栈顶位置,同时将其堆栈上方的任务堆栈下移,修改被移动推栈的任务堆栈指针。
假设我们定义的任务栈空间和任务的栈指针变量为:
void TaskSTK[MAX_STK_LEN];/*任务堆栈空间*/
typedef struct TaskSTKPoint{
int TaskID;
int pTopSTK;
int pBottomSTK;
}TASK_STK_POINT;
TASK_STK_POINT pTaskSTK[MAX_TASK_NUM]; /*存放每个任务的栈顶和栈底指针*/
任务栈指针数组pTaskSTK的元素个数同任务个数。为了堆栈交换,需要另外一块临时存储空间,其大小可按单个任务栈最大长度定义,用于中转堆栈交换的内容。堆栈内容交换的伪C算法可写为:
StkEechange(int CurTaskID,int RunTaskID)
{ /*2个参数为当前运行任务号和下一运行任务号*/
void TempSTK[MAX_PER_STK_LEN]; /*注意该变量长度可小于TaskSTK*/
L=任务RunTaskTD的堆栈长度;
①将TaskSTK顶部的L字节移动到TempSTK中;
②将RunTaskID任务的堆栈内容移动到TaskSTK顶部;
③将RunTaskID堆栈上方(移动前位置)所有内容下移L个字节;
④修改RunTask堆栈上方(移动前位置)所有任务栈顶和栈底指针(pTaskSTK变量);
};
该算法的平均时间复杂度可计算如下:
O(T)=SL/2+SL/2+SL×N/2
式中,第一、二项为步骤①和步骤②时间,第三项为步骤③时间;SL表示每个任堆栈的最大长度(即MAX_PER_STK_LEN),N表示任务数。
取SL为64字节,任务数为16个,则数据项平均移动次数为576。假设每次移动指令时间为2μs,则一次任务栈移动时间长达约1ms。所以在使用该方法时,为了执行时间尽量短,编码时应仔细推敲。
从空间上说,共用任务栈比独立任务栈优越。假设独立任务栈方法中每个堆栈空间为K,任务数为N,则独立任务栈方式的堆栈总空间为N×K。在共用任务栈时,考虑各任务互补的情况,TaskSTK变量不需要定义为N×K长度,可能定义为二分之一或者更小就可以了。
另外,这种方法不需要在任务切换时修改CPU的SP指针。
(2)工作栈和任务堆栈
上节共用任务栈算法的缺点是:任务切换时的堆栈内容交换算法复杂,占用时间长。另外一个折中的方法是设计一个工作堆栈,用于给当前运行的任务使用;在任务切换时,将工作栈内容换出得另外的存储空间,该空间可以动态申请,其大小按实际需要即可。
这种方法看起来和独立任务栈的方法类似,需要N+1块存储空间,其中一块用于工作栈空间。和独立任务堆栈相比,其区别有2点:
①SP指针所指向的空间始终是同一块存储空间,即工作栈;
②每个任务栈的大小不需要按最大空间定义,可以动态按实际大小从内存中分配空间。
对于8031这种处理器结构,由于堆栈指针只能指向其内部存储器,大小十分有限。采取这种方法,可将工作栈设在内部RAM,将任务栈设在外部RAM,扩展了堆栈空间。
和上一种共用堆栈方法相比,这种方法的交换时间要短,其时间复杂度约为1.5倍最大任务栈长度。
5 总结
独立任务栈的方法适合于存储器充足、任务切换频繁、对任务切换时间要求较高的场合,一般主要用在16位或者32位微处理器平台环境。值得注意的是,在某些微处理器中,虽然可使用的数据存储器可以设计得较大,但堆栈所能使用的存储器却是有限的。比如8031系列存储器,堆栈只能使用内部的128字节数据存储器,即使系统中有64K字节的外部数据存储器,任务栈的总空间也不能超过128字节。这种处理器使用共用任务栈结构的RTOS就更好一些。
由于共用任务栈系统需要较长的任务切换时间,不适于任务切换频繁的场合,在很多嵌入式系统中,长时间只有几个任务会处于运行状态,其它任务在特定的条件下才会运行。对于RTOS的使用者,也可以适当地划分任务,来减小任务切换的时间。
无论使用哪种方法,在存储空间有限时,任务栈的长度应仔细计算。计算的根据是任务中的函数嵌套数、函数局部变量长度。对于共用任务栈,还要考虑同时运行态和挂起态的最大任务数。一些编译器可以生成堆栈溢出检查代码,在调试时可将该编译开关打开,以测试需要的实际堆栈长度。

使用特权

评论回复
6
hotpower| | 2009-2-17 09:06 | 只看该作者

实时操作系统μC/OS-II的改进与应用研究 农学院 马德新

[/TABLE]
实时操作系统μC/OS-II的改进与应用研究 
来源:单片机及嵌入式系统应用 莱阳农学院 马德新    时间:2008-02-12     发布人:linyi020605
                                          
              
                                                                      
-->
  传统的嵌入式系统设计大多采用单任务顺序机制,应用程序是一个无限的大循环,所有的事件都按顺序执行,与时间相关性较强的事件靠定时中断来保证,由此带来系统的稳定性、实时性较差;尤其当系统功能较复杂,且对实时性要求较严格时,这种单任务机制的弱点暴露无遗。本文引入的嵌入式操作系统μC/OS-II是一个多任务的实时内核,主要提供任务管理功能。在实时系统中的多个任务,必须决定这些任务的优先级顺序,任务调度算法需要动态为就绪任务的优先级排序。为了满足对实时性要求越来越高的需要,同时避免频繁改变就绪任务的优先级,在分析μC/OS-II源代码的基础上,对其调度算法进行改进。 


1 μC/OS-II概述 
  μC/OS-II是一个完整的,可移植、可固化、可裁剪的占先式实时多任务内核;支持56个用户任务,支持信号量、邮箱、消息队列等常用的进程间通信机制;适用于各种微控制器和微处理器;所有代码用ANSI C语言编写,程序的可读性强,具有良好的可移植性,已被移植到多种处理器架构中,在某些实时性要求严格的领域中得到广泛应用。 
1.1 工作原理 
  μC/OS-II的核心工作原理是:近似地让最高优先级的就绪任务处于运行状态。首先初始化MCU,再进行操作系统初始化,主要完成任务控制块TCB初始化,TCB优先级表初始化,TCB链表初始化,事件控制块(ECB)链表初始化,空任务的创建等。然后,开始创建新任务,并可在新创建的任务中再创建其他新任务。最后,诃用OSStart()函数启动多任务调度。在多任务调度开始后,启动时钟节拍源开始计时,此节拍源给系统提供周期性的时钟中断信号,实现延时和超时确认。 
1.2 任务调度 
  操作系统在下面的情况下进行任务调度:中断(系统占用的时间片中断OSTimeTick()、用户使用的中断)和调用API函数(用户主动调用)。一种是当时钟中断来临时,系统把当前正在执行的任务挂起,保护现场,进行中断处理,判断有无任务延时到期;若没有别的任务进入就绪态,则恢复现场继续执行原任务。另一种调度方式是任务级的调度,即调用API函数(由用户主动调用),足通过发软中断命令或依靠处理器在任务执行中调度。当没有任何任务进入就绪态时,就去执行空任务。 


2 调度算法的改进 
2.1 实时系统的调度策略 
  在操作系统的多任务调度算法的设计上,要根据系统的具体需求来确定调度策略。实时调度策略按不同的方法可以分为:静态/动态,基于优先级/不基于优先级,抢占式/非抢占式,单处理器/多处理器。其中,静态是指在任务的整个生命期内优先级保持不变,任务的优先级是在系统建立任务时确定的;动态是指在任务的生命期内,随时确定或改变它的优先级别,以适应系统工作环境和条件的变化。 
  μC/OS-II系统采用的是静态优先级分配策略,由用户来为每个任务指定优先级。虽然任务的优先级可通过  OSTaskChangePrio()函数改变,但函数功能简单,仅以用户指定的新优先级来替换任务当前的优先级。随着实时嵌入式技术的发展,对嵌入式系统的实时性要求越来越高,多样化的调度方法己成为一种趋势。本文讨论动态优先级调度中的最优算法截止期最早优先算法的改进及其在μC/OS-II中的实现。 
2.2 调度算法的改进 
  截止期最早优先算法是动态优先级调度算法中的最优算法。在截止期最早优先算法中,系统按任务的截止期给每个任务分配优先级。任务的截止期越早其优先级越高,反之亦然。为此,在本文所述截止期最早优先算法的改进中.需在μC/OS-II系统中增加表l所列的项目。 
 


  在截止期最早优先算法中,需要用户为任务指定其截止期。在本改进中,将OSTaskCreate()和OSTaskCreateExt()中的参数INT8U Prio改为INT8U deadline,并在函数内定义局部变量INT8U Prio来记录分配给任务的优先级。该算法改进也要在系统中增加OSTaskPrioCreate()函数,函数优先级分配的方法是按任务的截止期分配。该模块流程如图l所示。 
     


  在对就绪任务优先级进行调整时,该模块首先在数组中对任务的优先级完成调整并记录任务优先级的调整情况。在执行此函数后,就绪任务队列中任务的优先级可能会改变,园此还需要在μC/OS-II系统中添加prio_adjust()函数。该函数应用μC/OS-II系统原有的函数OSTaskChangePrio()来更新就绪任务,代码如下: 
   

  为防止多个任务同时调用OSTaskPrioCreate()函数造成混乱,这段代码应按临界资源来处理,需要在调用前关中断,调用后再开中断。 


3 应用及评价 
3.1 系统结构 
  在液压测量控制HPMC模块中,系统要求在18ms内完成对7个位置的传感器和用户键盘数据的实时采集、处理及显示;且对于采集到的不同测量数据,要求系统根据任务的紧迫程度,作出优先级不同的实时响应。 
系统的结构如图2所示。由外向内分为3层:硬件电路层、任务层和操作系统层。 



  硬件电路层主要包括HPMC模块、用户操作、单片机控制模块。大致功能如下:HPMC模块主要完成传感器数据的实时采集;用户模块主要完成用户的操作;单片机控制模块用于控制数据的接收、处理、发送、短消息的收发等。 
任务层并行存在lO个任务,每个任务均由以下3部分组成:应用程序、任务堆栈以及任务控制块,主要完成任务优先权的动态设置以及任务状态的转换。 
  操作系统层的设计主要是将μC/OS-II移植到单片机上。本系统采用Atmel公司的MCS-5l系列兼容单片机,同时完成各个任务的具体编程。 
3.2 算法评估 
  选择用动态调度还是静态调度是很重要的,这会对系统产生深远的影响。静态调度对时间触发系统的设计很适合,而动态调度对事件触发系统的设计很适合。静态调度必须事先仔细设计,并要花很大的力气考虑选择各种各样的参数;动态调度不要求事先作多少工作,而是在执行期间动态地作出决定。 
  在HPMC模块中,由于需对现场采集到的测量数据进行实时处理,故对系统的实时性提出了很高的要求。若采用μC/OS-II的静态优先级调度算法,当系统中任务优先级变化时则显得无能为力;同时通过在液压测量控制系统中的应用表明,改进后系统的实时性得到了极大改善。 


结语 
  本文针对μC/OS-II静态调度算法进行改进,在系统中实现了截止期优先调度算法。通过在液压测量控制系统中的应用,表明这种改进能明显提高系统的实时性;但是改进后的算法对系统的内存、CPU等提出了更高的要求,存在一定的局限性。 







使用特权

评论回复
7
hotpower| | 2009-2-17 09:08 | 只看该作者

uCOS II就绪表(Ready List)分析(转帖)

3.0 就绪表(Ready List) 
  每个任务被赋予不同的优先级等级,从0级到最低优先级OS_LOWEST_PR1O,包括0和OS_LOWEST_PR1O在内(见文件OS_CFG.H)。当uCOS II初始化的时候,最低优先级OS_LOWEST_PR1O总是被赋给空闲任务idle task。注意,最多任务数目OS_MAX_TASKS和最低优先级数是没有关系的。用户应用程序可以只有10个任务,而仍然可以有32个优先级的级别(如果用户将最低优先级数设为31的话)。 
  每个任务的就绪态标志都放入就绪表中的,就绪表中有两个变量OSRedyGrp和OSRdyTbl[]。在OSRdyGrp中,任务按优先级分组,8个任务为一组。OSRdyGrp中的每一位表示8组任务中每一组中是否有进入就绪态的任务。任务进入就绪态时,就绪表OSRdyTbl[]中的相应元素的相应位也置位。就绪表OSRdyTbl[]数组的大小取决于OS_LOWEST_PR1O(见文件OS_CFG.H)。当用户的应用程序中任务数目比较少时,减少OS_LOWEST_PR1O的值可以降低uCOS II对RAM(数据空间)的需求量。 
  为确定下次该哪个优先级的任务运行了,内核调度器总是将OS_LOWEST_PR1O在就绪表中相应字节的相应位置1。  OSRdyGrp和OSRdyTbl[]之间的关系见图3.3,是按以下规则给出的: 
  当OSRdyTbl[0]中的任何一位是1时,OSRdyGrp的第0位置1, 
  当OSRdyTbl[1]中的任何一位是1时,OSRdyGrp的第1位置1, 
  当OSRdyTbl[2]中的任何一位是1时,OSRdyGrp的第2位置1, 
  当OSRdyTbl[3]中的任何一位是1时,OSRdyGrp的第3位置1, 
  当OSRdyTbl[4]中的任何一位是1时,OSRdyGrp的第4位置1, 
  当OSRdyTbl[5]中的任何一位是1时,OSRdyGrp的第5位置1, 
  当OSRdyTbl[6]中的任何一位是1时,OSRdyGrp的第6位置1, 
  当OSRdyTbl[7]中的任何一位是1时,OSRdyGrp的第7位置1, 

  程序清单3.5中的代码用于将任务放入就绪表。Prio是任务的优先级。 

  程序清单 L3.5 使任务进入就绪态 (这两行代码简直是神来之笔啊!!!) 

  代码
/*    
这行代码功能是找到列, 把列上的值置为1   
不妨假设prio的值为13, 即优先级为13. prio>>3 右移3位后值为1, 可以查表T3.1找出 OSMapTbl[1] 的值为 0000 0010. 再用 0000 0010 和 OSRdyGrp 进行异或运算   
*/   
OSRdyGrp |= OSMapTbl[prio >> 3];     
/*   
    
*/   
OSRdyTbl[prio >> 3] |= OSMapTbl[prio & 0x07];     



  读者可以看出,任务优先级的低三位用于确定任务在总就绪表OSRdyTbl[]中的所在位。接下去的三位用于确定是在OSRdyTbl[]数组的第几个元素。OSMapTbl[]是在ROM中的(见文件OS_CORE.C)屏蔽字,用于限制OSRdyTbl[]数组的元素下标在0到7之间,见表3.1 
  表 T3.1 OSMapTbl[]的值 Index Bit Mask (Binary) 
Index  Bit Mask (Binary) 
0 00000001 
1 00000010 
2 00000100 
3 00001000 
4 00010000 
5 00100000 
6 01000000 
7 10000000 
          
                        图3.3 uCOS II就绪表 

  如果一个任务被删除了,则用程序清单3.6中的代码做求反处理。 

  程序清单 L3.6 从就绪表中删除一个任务 


    代码
if ((OSRdyTbl[prio >> 3] &= ~OSMapTbl[prio & 0x07]) == 0)     
    OSRdyGrp &= ~OSMapTbl[prio >> 3];    

  以上代码将就绪任务表数组OSRdyTbl[]中相应元素的相应位清零,而对于OSRdyGrp,只有当被删除任务所在任务组中全组任务一个都没有进入就绪态时,才将相应位清零。也就是说OSRdyTbl[prio>>3]所有的位都是零时,OSRdyGrp的相应位才清零。为了找到那个进入就绪态的优先级最高的任务,并不需要从OSRdyTbl[0]开始扫描整个就绪任务表,只需要查另外一张表,即优先级判定表OSUnMapTbl([256])(见文件OS_CORE.C)。OSRdyTbl[]中每个字节的8位代表这一组的8个任务哪些进入就绪态了,低位的优先级高于高位。利用这个字节为下标来查OSUnMapTbl这张表,返回的字节就是该组任务中就绪态任务中优先级最高的那个任务所在的位置。这个返回值在0到7之间。确定进入就绪态的优先级最高的任务是用以下代码完成的,如程序清单L3.7所示。 
  程序清单 L3.7 找出进入就绪态的优先级最高的任务 


 代码
y    = OSUnMapTbl[OSRdyGrp];     
x    = OSUnMapTbl[OSRdyTbl][y]];     
prio = (y << 3) + x;    

  例如,如果OSRdyGrp的值为二进制01101000,查OSUnMapTbl[OSRdyGrp]得到的值是3,它相应于OSRdyGrp中的第3位bit3,这里假设最右边的一位是第0位bit0。类似地,如果OSRdyTbl[3]的值是二进制11100100,则OSUnMapTbl[OSRdyTbc][3]]的值是2,即第2位。于是任务的优先级Prio就等于26(3*8+2)。利用这个优先级的值。查任务控制块优先级表OSTCBPrioTbl[],得到指向相应任务的任务控制块OS_TCB的工作就完成了。 






· 上一条:实时操作系统μC/OS-II的改进与应用研究 
· 下一条:ucos II+ucGUI+s3c2410+LCD+触摸屏整合 


 
 
  


网友回复 相关广告或供求信息可以到商务站发布,在此回复的广告将被管理员删除 
 ·纯粹抄书 游客[2008-02-15]
   纯粹抄书

 

使用特权

评论回复
8
hotpower| | 2009-2-17 09:10 | 只看该作者

找了半天,确实没发现“OS 和看门狗”的问题

~~~

使用特权

评论回复
9
yewuyi| | 2009-2-17 10:22 | 只看该作者

有问题吗?

使用特权

评论回复
10
kanprin| | 2009-2-17 10:40 | 只看该作者

我的做法是

使用一个任务来喂狗,其他任务每次运行设置一个标志,喂狗任务负责检测这些任务运行的标志,直到所有任务的运行标志都置位时,才将这些标志清除,否则,在一定周期内,只要有某个任务运行标志没置位,则启动看门狗功能。这样可以保证每个任务都看好。

使用特权

评论回复
11
computer00| | 2009-2-17 10:48 | 只看该作者

看门狗和OS有必然联系吗?

使用特权

评论回复
12
hotpower| | 2009-2-17 14:00 | 只看该作者

10楼自己人~~~

使用特权

评论回复
13
dld2| | 2009-2-17 15:54 | 只看该作者

老hot不厚道

使用特权

评论回复
14
Airwill|  楼主 | 2009-2-17 17:19 | 只看该作者

汗颜!!!!!!!!!

  我这个"敌人", 今天有升格了, 成为“阶级敌人”了!

不过看得出, hot 发这么多贴, 虽然有点 "文不对题", 
但也足表示这些天的确研究了很多理论问题了.

楼上 ○○ 的反问表达了一个意思, 的确, 我也感觉 os 和看门狗不应该有关,
所以, ucos 里没有谈到看门狗的问题.

那使用 os 的系统里,到底要不要看门狗了呢?

使用特权

评论回复
15
hotpower| | 2009-2-17 18:46 | 只看该作者

看是“狗论”的“缔造者”,故不可能让俺抛弃之~~~

使用特权

评论回复
16
Airwill|  楼主 | 2009-2-18 12:15 | 只看该作者

霸道嘛

 因为是“狗论”的“缔造者”,故不可能让抛弃之

你就不怕 "狗咬一口, 入木三分"?

使用特权

评论回复
17
hotpower| | 2009-2-18 12:34 | 只看该作者

头可断,血可流,狗俺是决不丢~~~

使用特权

评论回复
18
xwj| | 2009-2-18 12:38 | 只看该作者

LS是123jj吗?

使用特权

评论回复
19
学生D| | 2009-2-18 18:17 | 只看该作者

这么研究OS理论?

请问,winXP带了狗吗?它需要看门吗?

一个OS最基础的活动是自己的硬件时钟节拍——系统心跳,它的作用本身就是一个看门狗:假设某当前运行任务A出现异常时,时钟节拍的ISR能够舍弃这个任务(放弃这个虚拟MCU及其资源)而切换到其它任务,当再次轮换到A任务时,OS可以使其从头开始重新运行。——这不正是看门狗的恢复作用?愿意的话,当然可以设计使OS切换到A任务出现故障时的那一点重新开始运行。这与设计看门狗恢复程序是一样的。

呵呵,谁愿意再养一条看门狗,那是属于用户意愿,如00所说,跟OS无关!

OS是内核,有自己的心跳。(只有一个心跳,只喂一条狗。——养2条狗听哪条狗叫?)  用户是核外程序,愿意养多少条狗都行。

使用特权

评论回复
20
HotC51| | 2009-2-19 07:30 | 只看该作者

哈哈~~~楼上对狗的了解估计一般般~~~

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

556

主题

17724

帖子

884

粉丝