重庆江北鲜花店:我的ucos学习分析 -- lx99's Blog

来源:百度文库 编辑:中财网 时间:2024/04/28 03:45:12

我的ucos学习分析

此资料由自己学习总结和网上收集整理 得来   大家相互学习交流
                                 龙心
                              2009.9.10
1.在uC/OS-II的帮助手册内,作者特地强调绝对不能在OSInit()或者OSStart()内
调用Timer初始化程序,那会破坏系统的可移植性同时带来性能上的损失。

所以,一个折中的办法就是:
在优先级最高的程序内调用,这样可以保证当OSStart()调用系统内部函数
OSStartHighRdy()开始多任务后,首先执行的就是Timer初始化程序。或者
专门开一个优先级最高的任务,只做一件事情,那就是执行 Timer初始化,
之后通过调用OSTaskSuspend()将自己挂起来,永远不再执行。不过这样会
浪费一个TCB空间。对于那些RAM吃紧的系统来说,还是不用为好。
2.(三) 一些重要的uC/OS-II API介绍

 

任何一个操作系统都会提供大量的API供程序员使用,uC/OS-II也不例外。由于uC/OS-II面向
的是嵌入式开发,并不要求大而全,所以内核提供的API也就大多和多任务息息相关。
主要的有以下几类:

1)任务类

2)消息类

3)同步类

4)时间类

5)临界区与事件类

我个人认为对于初级程序员而言,任务类和时间类是必须要首先掌握的两种类型的API。
下面我就来介绍比较重要的:

1) OSTaskCreate函数

 

这个函数应该至少再main函数内调用一次,在OSInit函数调用之后调用。作用就是创建
一个任务。目前有四个参数,分别是任务的入口地址,任务的参数, 任务堆栈的首地址和
任务的优先级。调用本函数后,系统会首先从TCB空闲列表内申请一个空的TCB指针,然后
将会根据用户给出参数初始化任务堆栈,并在内部的任务就绪表内标记该任务为就绪状态。
最后返回,这样一个任务就创建成功了。

2) OSTaskSuspend函数

 

这个函数很简单,一看名字就该明白它的作用,它可以将指定的任务挂起。如果挂起的是
当前任务的话,那么还会引发系统执行任务切换先导函数OSShed来进行一次任务切换。
这个函数只有一个参数,那就是指定任务的优先级。那为什么是优先级呢?事实上在系统
内部,优先级除了表示一个任务执行的先后次序外,还起着分别每一个任务的作用,换句话
说,优先级也就是任务的ID。所以uC/OS-II不允许出现相同优先级的任务。

3) OSTaskResume函数

 

这个函数和上面的函数正好相反,它用于将指定的已经挂起的函数恢复成就绪状态。如果
恢复任务的优先级高于当前任务,那么还为引发一次任务切换。其参数类似 OSTaskSuspend
函数,为指定任务的优先级。需要特别说明是,本函数并不要求和OSTaskSuspend函数成对使
用。

4) OS_ENTER_CRITICAL宏

 

很多人都以为它是个函数,其实不然,仔细分析一下OS_CPU.H文件,它和下面马上要谈到的
OS_EXIT_CRITICAL都是宏。他们都是涉及特定 CPU的实现。一般都被替换为一条或者几条
嵌入式汇编代码。由于系统希望向上层程序员隐藏内部实现,故而一般都宣称执行此条指
令后系统进入临界区。其实, 它就是关个中断而已。这样,只要任务不主动放弃CPU使用权,
别的任务就没有占用CPU的机会了,相对这个任务而言,它就是独占了。所以说进入临界区了。
这个宏能少用还是少用,因为它会破坏系统的一些服务,尤其是时间服务。并使系统对外界响
应性能降低。

5) OS_EXIT_CRITICAL宏

 

这个是和上面介绍的宏配套使用另一个宏,它在系统手册里的说明是退出临界区。其实它就
是重新开中断。需要注意的是,它必须和上面的宏成对出现,否则会带来意想不到的后果。
最坏的情况下,系统会崩溃。我们推荐程序员们尽量少使用这两个宏调用,因为他们的确会
破坏系统的多任务性能。

6) OSTimeDly函数

 

这应该程序员们调用最多的一个函数了,这个函数完成功能很简单,就是先挂起当起当前任务,
然后进行任务切换,在指定的时间到来之后,将当前任务恢复为就绪状态,但是并不一定运行,
如果恢复后是优先级最高就绪任务的话,那么运行之。简单点说,就是可以任务延时一定时间
后再次执行它,或者说,暂时放弃CPU的使用权。一个任务可以不显式的调用这些可以导致放弃CPU使用权的API,但那样多任务性能会大大降低,因为此时仅仅依靠时钟机制在进行任务切换。一个好的任务应该在完成一些操作主动放弃使用权,好东西要大家分享嘛!


3.我们推荐程序员们尽量少使用OS_ENTER_CRITICAL宏和 OS_EXIT_CRITICAL宏两个宏调用,
因为他们的确会破坏系统的多任务性能。why??


4.在以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.注意ARM Image for uCOSII for lpc213x 模板中的TargetInit()


对于很多使用ZLG ARM Image for uCOSII for lpc213x 模板的初学者,常常会置疑使用该模板
后自动生成的target.c文件,和在程序中调用的TargetInit()函数,我和 Zgpswh都是如此,这
个问题当初困扰了很久:当用户程序中不调用TargetInit()时,发现内核能运行,但是等待机制
失灵,调用 TargetInit(),很多硬件中断打不开,后来,在很多热心人的指点下解决了,现重新
总结如下:

请仔细察看ZLG模板里的target.c文件,这里的TargetInit()如下:

void TargetInit(void)
{
    OS_ENTER_CRITICAL();
    srand((uint32) TargetInit);
    VICInit();
    Timer0Init();
    OS_EXIT_CRITICAL();
}

其中的Timer0Init();用于硬件定时器0的初始化,事实上,ZLG的移植代码的μC/OS-Ⅱ的时钟节拍是
通过定时器0提供的,不在主程序里调用这个函数,μC/OS-Ⅱ的时钟源就无法打开;但是,没有开启
时钟源的μC/OS-Ⅱ是同样能运行的,只是内核提供的延时和等待时限机制都不起作用,系统虽能将
就运行,但因没调用TargetInit()而使内核功能不健全。

请注意,TargetInit()中的另一个函数VICInit ()是用来中断的初始化,它其中含有对UART0中断的
分配,在用户程序里需要根据使用的硬件中断修改这部分代码,否则,这些硬件中断无法开启;
再者,在不调用TargetInit()的时候,硬件的中断初始化是在硬件初始化函数中完成,
这也就是Zgpswh提到的现象:不调用TargetInit()内核运行异常,调用了却开不了UART0的中断。

解决的方法如下:
这在《ARM嵌入式系统基础教程》的430页7.4.3节中论述的很清楚:

……关键在于把程序与芯片相关中断源挂接,使芯片在产生相应的中断后会调用相应的处理程序。
这需要做两方面事情:
1. 增加汇编接口的支持。……
2. 初始化向量中断控制器。……

按照一下方法完成中断源的的挂接:
1、增加汇编接口的支持。方法是修改IRQ.s文件,在末尾添加本句代码:

UART0_Handler HANDLER UART0_Exception

追加定义了通用串口0 中断句柄。
2、初始化向量中断控制器。将target.c文件中的VICInit()修改如下:

void VICInit(void)
{ extern void IRQ_Handler(void);
   extern void Timer0_Handler(void);
   extern void UART0_Handler(void);
   VICIntEnClr = 0xffffffff;
   VICDefVectAddr = (uint32)IRQ_Handler;
   VICVectAddr0 = (uint32)Timer0_Handler;
   VICVectCntl0 = (0x20 | 0x04);
   VICIntEnable = 1 << 4;
   VICVectAddr14 = (uint32)UART0_Handler;
   VICVectCntl14 = (0x20 | 0x06);
   VICIntEnable = 1 << 6;
}

此为初始化向量中断控制器。包括定时器0和串口0,特别要注意的是,一定不可以省略对定
时器0的初始化
中断里,不可以调用延时

请注意,中断里面是不支持等待机制的。请用自己编的一个软件延时,问题就可以解决了。
中断源挂接正确是没问题的。

 

 

6.问
void TargetInit(void)
{
    OS_ENTER_CRITICAL();
    srand((uint32) TargetInit);
    VICInit();
    Timer0Init();
    OS_EXIT_CRITICAL();
}
在此单独用srand()函数有什么作用(用了有什么好处,不用又会怎样),一般srand()用于
给rand()设定种子(即srand给定rand运算式子的第一个值)。查了FAQ(P22)仅仅说明了
seed的译文。

答 2
你说的没有错,他就是用来设置随机数的种子。
每次编译一次,void TargetInit(void)函数在Ram或者Flash中的地址都不一样
(即种子也不一样)。如果你在程序中不用随机函数rand(),那么srand()
在这里对你来说是没有意义的,如果你要用rand(),那么每次编译程序后你的rand()
产生的随机数就不一样。当然,你也可以自己种种子咯。

 

 

7.
在ARM上移植操作系统有一点需要注意:建立任务的任务,最好不要做复杂的工作。
频繁的访问其他硬件或者做时序要求比较严的工作容易造成系统死机,希望大家多
多注意。我的做法是:将建立任务的任务,闲置起来,但是不能进入死循环。方法
是:利用一个空邮箱,让任务无限期的等待,这样可以实现与其他任务的切换。


8.//定义与编译器无关的数据类型

typedef unsigned char BOOLEAN; //布尔变量

typedef unsigned char INT8U; // 无符号8位整型变量

typedef signed char INT8S; //有符号8位整型变量

typedef unsigned short INT16U; //无符号16位整型变量

typedef signed short INT16S; //有符号16位整型变量

typedef unsigned int INT32U; //无符号32位整型变量

typedef signed int INT32S; //有符号32位整型变量

typedef float FP32; //单精度浮点数(32位长度)

typedef double FP64; //双精度浮点数(64位长度)

typedef INT32U OS_STK; //堆栈是32位宽度

注:这里为什么用typedef,因为如果用#define,那么代码中的每一个相应的类型都会被替代,
很有可能会出现问题,毕竟他只是一个替代的关系,且编译时间会增加,而用typedef则不会,
它就相当于我们C++里面的引用,一样的思维,在这里面就是说多了一个名称。还有要注意的是
我们怎么知道unsigned char 就是无符号8位整型变量,可以ARM公司里面下载ADS_CompilerGuide_D.PDF
文件,或者在你所装的ADS1.2目录里面有一个文件夹叫PDF,打开它就可以找到,具体页在259页。

9>OS_CPU_a.s:

;定义系统模式堆栈的大小

SVC_STACK_LEGTH EQU 32


NoInt EQU 0x80


USR32Mode EQU 0x10

SVC32Mode EQU 0x13

SYS32Mode EQU 0x1f

IRQ32Mode EQU 0x12

FIQ32Mode EQU 0x11


;T_bit用于检测进入异常前cpu是否处于THUMB状态

T_bit EQU 0x20


CODE32


AREA |subr|, CODE, READONLY


IMPORT OSTCBCur ;指向当前任务TCB的指针

IMPORT OSTCBHighRdy ;指向将要运行的任务TCB的指针

IMPORT OSPrioCur ;当前任务的优先级

IMPORT OSPrioHighRdy ;将要运行的任务的优先级

IMPORT OSTaskSwHook ;任务切换的钩子函数

IMPORT OSRunning ;uC/OS-II运行标志


IMPORT OsEnterSum ;关中断计数器(关中断信号量)

IMPORT SWI_Exception ;软中断异常处理程序

EXPORT __OSStartHighRdy

EXPORT OSIntCtxSw ;中断退出时的入口,参见startup.s中的IRQ_Handler

EXPORT SoftwareInterrupt ;软中断入口


;软件中断

//当CPU发现有软中断信号出现时,CPU的PC马上指到软中断服务地址处【见Startup.s】,然后通过跳转到下面这段软中断处理程序,进入后,首先要设置好管理模式下的堆栈,保存好用户任务的几个寄存器,其它寄存器系统会自动保存,然后取出SWI号。

SoftwareInterrupt

LDR SP, StackSvc ; 重新设置堆栈指针

STMFD SP!, {R0-R3, R12, LR} //为什么只保存这几个寄存器,见上面的解释

MOV R1, SP ; R1指向参数存储位置


MRS R3, SPSR //保存管理模式的状态寄存器

TST R3, #T_bit ; 中断前是否是Thumb状态

LDRNEH R0, [LR,#-2] ; 是: 取得Thumb状态SWI号

BICNE R0, R0, #0xff00

LDREQ R0, [LR,#-4] ; 否: 取得arm状态SWI号

BICEQ R0, R0, #0xFF000000

; r0 = SWI号,R1指向参数存储位置

CMP R0, #1

LDRLO PC, =OSIntCtxSw

LDREQ PC, =__OSStartHighRdy ; SWI 0x01为第一次任务切换


BL SWI_Exception

LDMFD SP!, {R0-R3, R12, PC}^

StackSvc DCD (SvcStackSpace + SVC_STACK_LEGTH * 4 - 4)


OSIntCtxSw

;下面为保存任务环境

LDR R2, [SP, #20] ;获取PC//其实他就是管理模式下的LR寄存器的值,也就是用户任务的PC值,为什么是SP+20,因为该栈是满栈递减,保存了R0-R3, R12, LR,所以LR的值为[SP+#20]。

LDR R12, [SP, #16] ;获取R12

MRS R0, CPSR //用通用寄存器R0保存该管理模式下的CPSR


MSR CPSR_c, #(NoInt | SYS32Mode)//切换到系统模式

MOV R1, LR //把用户的LR保存在通用寄存器R1中

STMFD SP!, {R1-R2} ;保存LR,PC//把系统模式的LR,PC压入用户任务的栈中

STMFD SP!, {R4-R12} ;保存R4-R12//把系统模式的R4-R12压入用户栈中


MSR CPSR_c, R0 //重新回到管理模式

LDMFD SP!, {R4-R7} ;获取R0-R3//实际上把用户的R0~R3放到管理模式下的R4-R7

ADD SP, SP, #8 ;出栈R12,PC//这两个已经保存了,所以SP要往上8个字节

MSR CPSR_c, #(NoInt | SYS32Mode//系统模式

STMFD SP!, {R4-R7} ;保存R0-R3//因为管理模式的R4-R11和系统模式的R4-R11是一样的,保它保存在用户任务的栈中

//接下来的我就不分析拉,因为你如果按照我上面的分析,应该不成问题的,如果还未明白,请多看几遍,如果有问题,请留言我们一起来探讨,谢谢合作!上面的分析除了红色部分是周立功公司的注释外,其余都是我的理解,如果有错的,真心希望您能指出!谢谢!

LDR R1, =OsEnterSum ;获取OsEnterSum

LDR R2, [R1]

STMFD SP!, {R2, R3} ;保存CPSR,OsEnterSum


;保存当前任务堆栈指针到当前任务的TCB

LDR R1, =OSTCBCur

LDR R1, [R1]

STR SP, [R1]


BL OSTaskSwHook ;调用钩子函数

;OSPrioCur <= OSPrioHighRdy

LDR R4, =OSPrioCur

LDR R5, =OSPrioHighRdy

LDRB R6, [R5]

STRB R6, [R4]

;OSTCBCur <= OSTCBHighRdy

LDR R6, =OSTCBHighRdy

LDR R6, [R6]

LDR R4, =OSTCBCur

STR R6, [R4]

OSIntCtxSw_1

;获取新任务堆栈指针

LDR R4, [R6]

ADD SP, R4, #68 ;17寄存器CPSR,OsEnterSum,R0-R12,LR,SP

LDR LR, [SP, #-8]

MSR CPSR_c, #(NoInt | SVC32Mode) ;进入管理模式

MOV SP, R4 ;设置堆栈指针


LDMFD SP!, {R4, R5} ;CPSR,OsEnterSum

;恢复新任务的OsEnterSum

LDR R3, =OsEnterSum

STR R4, [R3]

MSR SPSR_cxsf, R5 ;恢复CPSR

LDMFD SP!, {R0-R12, LR, PC }^ ;运行新任务

 

__OSStartHighRdy

MSR CPSR_c, #(NoInt | SYS32Mode)

;告诉uC/OS-II自身已经运行

LDR R4, =OSRunning

MOV R5, #1

STRB R5, [R4]


BL OSTaskSwHook ;调用钩子函数


LDR R6, =OSTCBHighRdy

LDR R6, [R6]

B OSIntCtxSw_1


AREA SWIStacks, DATA, NOINIT,ALIGN=2

SvcStackSpace SPACE SVC_STACK_LEGTH * 4 ;管理模式堆栈空间


END
 

类别:uc/os-ii | 添加到搜藏 | 浏览(180) | 评论 (0)  上一篇:模电学习笔记汇总    下一篇:uCos-II学习汇总(一) 相关文章:? 09年部分省市高考理科状元学习经...          ? English词汇学习专贴分类词汇汇...
学习Fluent的经验汇总(转摘的,个...          ? .net学习网站汇总
2010年东南大学-301学习科学研究...          ? 精彩英语听力口语学习网站-网址...
树状数组学习系列2 之 OJ题目大...          ? 学习娱乐两不误 OPPO S39评测大...
DIV CSS网页布局学习中容易出现...          ? 数据挖掘学习和研究的一些指导性...
更多>>
 最近读者: 登录后,您就出现在这里。 

嵌入式实时操作系统ucos ii的分析2009年03月18日 星期三 下午 05:23摘要:近年来,在单片机系统中嵌入操作系统已经成为人们越来越关心的一个话题。本文通过对一种源码公开的嵌入式实时操作系统ucos ii的分析,以51系列单片机为例,阐述了在单片机中使用该嵌入式操作系统的优缺点,以及在应用中应当注意的一些问题。
关键词:实时操作系统;ucos ii;单片机


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


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

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

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

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


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

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

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

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

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

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

 

系统分类: ARM   |   用户分类: 无分类   |   来源: 无分类   |   【推荐给朋友】   |   【添加到收藏夹】