嵌入式实时操作系统实验手册.docx
《嵌入式实时操作系统实验手册.docx》由会员分享,可在线阅读,更多相关《嵌入式实时操作系统实验手册.docx(17页珍藏版)》请在冰豆网上搜索。
嵌入式实时操作系统实验手册
嵌入式实时操作系统实验手册
系别:
电子信息与计算机科学系
专业班级:
文电1131、1132文通1141
实验地点:
一教203、204
实验时间:
9-17周,周三,3、4节
指导教师:
荆蕾
学期:
2013-2014学年度第二学期
目录
目录2
学时进度安排2
实验一uCOS-II操作系统的文件结构和任务的创建3
一、实验目的3
二、开发环境介绍3
三、实验任务:
7
实验二任务的删除8
一实验目的8
二理论知识8
三实验任务:
9
实验三信号量的使用10
一实验目的10
二实验原理10
三实验任务:
11
实验四互斥型信号量的使用12
一实验目的12
二理论知识12
三实验内容13
实验五消息队列的使用14
一实验目的14
二实验原理14
三实验内容16
实验六信号量集的使用17
一实验目的:
17
二实验原理:
17
三实验内容19
学时进度安排
时间
实验内容
9、11周,周三,3,4节
实验一、操作系统的文件结构和任务的创建
12周,周三,3,4节
实验二、任务的删除
13、14周,周三,3,4节
实验三、信号量的使用
15周,周三,3,4节
实验四、互斥型信号量的使用
16周,周三,3,4节
实验五、消息队列的使用
17周,周三,3,4节
实验六、信号量集的使用
实验一uCOS-II操作系统的文件结构和任务的创建
一、实验目的
1.熟悉并掌握基于uC/OS-II的开发工具TurboC3.0
2.了解uC/OS-II的文件结构、文件之间的依赖关系。
3.了解任务创建的方法。
二、开发环境介绍
(一)为什么选择用TurboC3.0编译UCOS(uC/OS-II)?
按照课本(任哲老师)给出的BorlandC编译方式,配置起来有些复杂(课本第二章讲了使用BorlandC编译的配置方法,请感兴趣的同学自己研究)。
所以这里我们选择了配置好的工程文件,使用TurboC3.0编译。
TurboC是Borland公司开发的DOS下16位C语言集成开发工具。
有2.0和3.0版本,2.0只支持C语言编译,不支持鼠标操作,而3.0版本可以支持C/C++两种语言编译,而且还支持鼠标和//注释方式;TC2.0是80年代开发的,使用了很多年一直到现在WINDOWS系统才逐渐退出舞台。
TurboC2.0不仅是一个快捷、高效的编译程序,同时还有一个易学、易用的集成开发环境。
使用TurboC2.0无需独立地编辑、编译和连接程序,就能建立并运行C语言程序。
因为这些功能都组合在Turbo2.0的集成开发环境内,并且可以通过一个简单的主屏幕使用这些功能。
(二)在开始做之前,请确保您已经有Software文件夹(即ucos源文件和PC上的移植文件)。
编译UCOS需要的源文件如下:
1.SOURCE文件夹(..\Software\project\source\)的文件如下:
含OS_CORE.C、OS_FLAG..C、OS_MBOX.C、OS_MEM.C、OS_MUTEX.C、OS_Q.C、OS_SEM.C、OS_TASK.C、OS_TIME.C、uCOS_II.c、uCOS_II.H等11个文件
2.Project文件夹下的文件如下:
OS_CPU.H、OS_CPU_A.ASM、OS_CPU_C.C(三个文件)
INCLUDE.H、OS_CFG.H、PC.H、PC.C
我们的源程序文件TEST.C
OS_CPU_A.ASM汇编语言源程序文件,需要用TASM汇编,即TASM.exe和TASM2MSG..exe这两个文件。
Output文件夹中包含了编译生成的OBJ和EXE文件
(三)程序的编译和运行步骤
该工程文件的使用方法:
1、双击TCPP30E.EXE启动TurboC3.0。
可以用Alt+Enter切换,使TC全屏。
2、在Project菜单---选择OpenProject..,在弹出的对话框中选择UCOS2.PRJ即为我们已经建立好的工程文件。
如下图所示:
打开后TC界面可能没什么变化,你可能以为还没打开,可以将Project窗口打开
能够看到刚才打开的UCOS2工程包含的文件了
3、OK我们开始编译
Alt+C选择编译菜单的make
编译成功
4、Alt+R选择Run菜单下的run执行。
(注意:
运行的结果如下图所示,但是这个结果是根据Test.c程序的不同而变化的。
)
5、按ESC键可以返回TC界面,你就可以开始写自己的TEST.C文件了
6、如果需要往工程中添加文件,请选择Project菜单----Additem…(不需要添加的将此步骤忽略)
添加文件additem
添加c文件
三、实验任务:
1、请通过工程文件来了解uC/OS-II的文件体系
uC/OS-II包括三个部分:
(P13,图1-7)
1)核心代码部分,这部分代码与处理器无关,包括七个源代码文件和一个头文件,这七个源代码文件负责的功能分别是内核管理、事件管理、消息队列管理、存储管理、消息管理、信号量处理、任务调度和定时管理。
2)设置代码部分,包括两个头文件,用来配置事件控制块的数目以及是否包含消息管理相关代码等。
3)处理器相关的移植代码部分,这部分包括一个头文件,一个汇编文件和一个C代码文件,在uC/OS-II的移植过程中,用户所需要关注的就是这部分文件。
请大家区分下:
该工程中的各个文件分别属于上述哪一个部分?
2、uC/OS-II任务的创建,使用函数OSTaskCreate()
想让µC/OS-Ⅱ管理用户的任务,用户必须要先建立任务。
用户可以通过传递任务地址和其它参数到以下两个函数之一来建立任务:
OSTaskCreate()或OSTaskCreateExt()。
OSTaskCreate()与µC/OS是向下兼容的,OSTaskCreateExt()是OSTaskCreate()的扩展版本,提供了一些附加的功能。
用两个函数中的任何一个都可以建立任务。
我们常用的是OSTaskCreate()函数。
任务可以在多任务调度开始前建立,也可以在其它任务的执行过程中被建立。
在开始多任务调度(即调用OSStart())前,用户必须建立至少一个任务。
任务不能由中断服务程序(ISR)来建立。
OSTaskCreate()需要四个参数:
task是任务代码的指针,pdata是当任务开始执行时传递给任务的参数的指针,ptos是分配给任务的堆栈的栈顶指针prio是分配给任务的优先级。
1)请编写一个有3个任务的应用程序,每个任务均会在显示器上显示一个字符,让3个任务具有不同的等待时间,观察并解释该程序运行的时候任务被调度的情况。
2)当第一个任务运行了20次的时候,让第一个任务请求挂起第二个任务、当第一个任务运行到40次的时候,恢复第二个任务。
请编写程序解决上面两个问题,并且要给出对于现象的解释。
实验二任务的删除
一实验目的
掌握任务删除操作所使用的方法。
二理论知识
删除任务,就是将该任务处于睡眠状态。
并不是说任务的代码真的被删除了,只是任务的代码不再被操作系统调用。
也就是删除该任务的任务控制块即可。
通过调用OSTaskDel()就可以完成删除任务自身或除了空闲任务之外的其他任务的操作。
●如果删除自身,则使用OSTaskDel(OS_PRIO_SELF);
●如果删除其它任务,则使用OSTaskDel(被删除任务的优先级);
任务不能由请求删除方直接删除,会导致被删除任务所使用的有些临时空间不能释放。
所以请求删除任务方只是提出删除请求,被删除方收到请求后将自己删除。
双方通信使用的是被删除方任务控制块的成员OSTCBDelReq(双方都可以访问),访问该成员使用的函数为OSTaskDelReq(prio)。
例程:
请求删除方可以这么写:
while(OSTaskDelReq(prio)!
=OS_TASK_NOT_EXIST)
{
OSTimeDly
(1);//延时等待,直到被删除任务已被删除
}
解释:
prio为被删除任务的优先级。
请求删除方使用该函数,可以将OSTCBDelReq成员的值置为:
OS_TASK_DEL_REQ;该函数如果返回值为OS_TASK_NOT_EXIST意味着该被删除的任务已经删除掉。
被删除方程序可以这么写:
if(OSTaskDelReq(OS_PRIO_SELF)==OS_TASK_DEL_REQ)
{
释放资源和动态内存的代码;
OSTaskDel(OS_PRIO_SELF);
}
else
{
其它应用代码;
}
这里,被删除方使用OSTaskDelReq(OS_PRIO_SELF)函数,返回的是其任务控制块中的成员OSTCBDelReq的数值。
所以如果该值==OS_TASK_DEL_REQ,意味着已经有任务提出删除该任务的请求,要释放资源,将自己删除掉。
三实验任务:
创建两个任务MyTask、YouTask,MyTask任务显示“M”,YouTask任务显示“Y”,使任务MyTask显示了30个M之后,提出删除YouTask请求,YouTask收到请求后,延时5s之后删除自身。
请参考例题3-10。
实验三信号量的使用
一实验目的
1)掌握在基于嵌入式实时操作系统μC/OS-II的应用中,任务使用信号量的一般原理。
二实验原理
1.信号量
µC/OS-II中的信号量由两部分组成:
1)一个是信号量的计数值,它是一个16位的无符号整数(0到65,535之间);
2)另一个是由等待该信号量的任务组成的任务等待表。
用户要在OS_CFG.H中将OS_SEM_EN开关量常数置成1,这样µC/OS-II才能支持信号量。
关于信号量计数值:
在使用一个信号量之前,首先要建立该信号量,也即调用OSSemCreate()函数,对信号量的初始计数值赋值。
1)如果信号量是用来表示一个或者多个事件的发生,那么该信号量的初始值应设为0。
2)如果信号量是用于对共享资源的访问,那么该信号量的初始值应设为1(例如,把它当作二值信号量使用)。
3)如果该信号量是用来表示允许多个任务访问这个共同的资源,那么该初始值显然应该是n,并把该信号量作为一个可计数的信号量使用。
µC/OS-II提供了5个对信号量进行操作的函数。
它们是:
OSSemCreate(),OSSemPend(),OSSemPost(),OSSemAccept()和OSSemQuery()函数。
2、建立一个信号量,OSSemCreate()
首先,它从空闲任务控制块链表中得到一个空事件控制块。
接着,用信号量的初始值对任务控制块进行初始化
最后,OSSemCreate()返回给调用函数一个指向任务控制块的指针。
以后对信号量的所有操作,如OSSemPend(),OSSemPost(),OSSemAccept()和OSSemQuery()都是通过该指针完成的。
因此,这个指针实际上就是该信号量的句柄。
如果系统中没有可用的任务控制块,OSSemCreate()将返回一个NULL指针。
3、如果要使用共享资源,首先请求信号量,OSSemPend()。
4、使用完共享资源后要发送信号量。
OSSemPost()。
三实验任务:
运行第五章的例程例5-1、5-2、5-3、5-4,观察运行结果,掌握信号量的访问原理,并对运行结果进行解释。
实验四互斥型信号量的使用
一实验目的
1)掌握在基于优先级的可抢占嵌入式实时操作系统的应用中,出现优先级反转现象的原理。
2)掌握互斥型信号量在解决优先级反转现象中的应用。
二理论知识
1、优先级反转:
是指在剥夺式OS中,当任务以独占方式使用共享资源时,低优先级的任务可能先于高优先级任务得到系统调度而运行的现象。
优先级反转发生在有多个任务需要使用共享资源的情况下,可能会出现高优先级任务被低优先级任务阻塞,并等待低优先级任务执行的现象。
高优先级任务需要等待低优先级任务释放资源,而低优先级任务又正在等待中等优先级任务。
两个任务都试图访问共享资源是出现优先级反转最通常的情况。
问题的严重性:
如果中等优先级的任务较多时,会造成高优先级的任务最迟完成,而影响系统的设计目标。
解决办法:
暂时提升获得共享资源任务的优先级别,尽快释放共享资源,之后再恢复其原有的优先级别。
2、互斥型信号量是一个二值信号量,是一种处理“任务优先级反转”现象的特殊信号量,主要用于处理任务对共享资源独占问题。
3、互斥型信号量所用的操作:
1)OS_EVENT*OSMutexCreate(
INT8Uprio,//信号量优先级别
INT8U*err//函数结果状态信息
);
函数操作说明:
①该函数从空事件控制块队列(OSEventFreeList)获得一个ECB,并将其初始化。
②用户查看*err可知道本函数的执行结(OS_NO_ERR)
③用户通过本函数返回的事件指针来使用该“信号”。
2).请求互斥型信号量
当任务要访问一个独占共享资源时,要先调用系统函数OSMutexPend()函数申请相应的互斥信号量;其原型如下:
voidOSMutexPend(
OS_EVENT*pevent,//信号量指针
INT16Utimeout,//等待时间
INT8U*err//函数结果状态信息
);
3)发送互斥型信号量
调用系统函数OSMutexPost()发送(释放)互斥型信号量,其原型如下:
INT8UOSMutexPost(
OS_EVENT*pevent//信号量指针
);
三实验内容
1.运行例程5-6,解释出现任务优先级反转的原因。
2.修改例程5-6,达到例程5-7的效果,解决任务优先级反转问题,并解释原因。
并请描述前五行显示的原因。
第三行和第四行之间有停顿,停顿时间和什么有关系?
第三行的显示和Hertask任务优先级的提升有没有冲突?
实验五消息队列的使用
一实验目的
练习使用消息队列在不同任务之间传递数据的方法。
二实验原理
消息邮箱只能传递一条消息,为了能传递多条消息,就要使用消息队列。
消息队列由3部分组成:
事件控制块ECB、消息队列和消息。
为了管理多条消息,建立了消息指针数组,里面保存的是若干条消息的指针。
但是消息指针数组中不一定全部都使用,随着消息的增多使用的元素个数增多。
为了对消息指针数组进行管理,又建立了队列控制块。
里面包含消息指针数组的信息。
事件控制块ECB中的OSEventType为OS_EVENT_TYPE_Q,表明消息队列。
EventPtr指向队列控制块。
所以建立一个消息队列就对应一个事件控制块ECB,根据ECB找到队列控制块,根据队列控制块找到指针数组,并找到消息指针。
消息指针数组在物理结构上仅仅是个数组,但是该数组中的元素不一定全部使用,涉及到往指针数组插入消息以及取出消息等操作,这些要在OS_Q队列控制块的控制下完成。
向指针数组中插入消息指针的方式有2种:
先进先出(FIFO)方式和后进先出(LIFO)方式。
◆消息队列表现为FIFO方式时,OSQIn是队列的写入端,OSQOut是队列的读出端;
◆表现为LIFO方式时,OSQOut既是队列的写入端,也是队列的读出端;
◆消息队列以何种方式(FIFO/LIFO)表现是通过不同的系统函数实现的;
队列控制块的操作函数如下:
OS_EVENTOSQCreate(void*start,INT16Usize);
创建一个消息队列首先需要定义一个指针数组,然后把各个消息数据缓冲区的首地址存入该数组中,最后再调用OSQCreate()创建消息队列。
参数start为存放消息缓冲区指针数组的地址,参数size为该数组的大小,函数的返回值为消息队列的指针。
void*OSQPend(OS_EVENT*pevent,INT16Utimeout,INT8U*err);
参数pevent是要访问的消息队列的事件控制块ECB的指针。
函数要通过访问事件控制块的成员OSEventPtr-队列控制块OS_Q的成员OSQEntries来判断是否有消息可用。
如果有消息可用,则返回OS_Q成员OSQOut指向的消息,同时调整指针OSQOut,使之指向下一条消息并把有效消息数的变量OSQEntries减1;
如果无消息可用(即OSQEntries=0),则使调用函数OSQPend()的任务挂起,使之处于等待状态并引发一次任务调度OSShed()。
INT8UOSQPost/OSQPostFront(OS_EVENT*pevent,void*msg);
参数msg为待发消息的指针。
OSQPost使用FIFO方式组织消息队列。
OSQPostFront使用LIFO方式组织消息队列。
三实验内容
请运行例程5-9,并解释显示结果的原因和每一行出现的时间点。
例如
0ticks时:
因为Starttask优先级高,所以显示。
。
。
。
。
。
。
200ticks时:
mytask的状态,youtask的状态,显示的内容描述。
。
。
。
。
。
。
。
。
。
。
。
。
实验六信号量集的使用
一实验目的:
1.掌握信号量集的结构。
2.学会对信号量集的操作。
3.掌握信号量集的创建,查询,删除等操作。
二实验原理:
在实际应用中,任务常常需要与多个事件同步,即要根据多个信号量组合作用的结果来决定任务的运行方式。
UCOSII为了实现多个信号量组合的功能定义了一种特殊的数据结构——信号量集。
信号量集所能管理的信号量都是一些二值信号,所有信号量集实质上是一种可以对多个输入的逻辑信号进行基本逻辑运算的组合逻辑
不同于信号量、消息邮箱、消息队列等事件,UCOSII不使用事件控制块来描述信号量集,而使用了一个叫做标志组的结构OS_FLAG_GRP来描述。
OS_FLAG_GRP结构如下:
typedefstruct
{
INT8U OSFlagType; //识别是否为信号量集的标志
void*OSFlagWaitList; //指向等待任务链表的指针
OS_FLAGS OSFlagFlags; //所有信号列表
}OS_FLAG_GRP;
成员OSFlagWaitList是一个指针,当一个信号量集被创建后,这个指针指向了这个信号量集的等待任务链表。
信号量集用一个双向链表来组织等待任务,每一个等待任务都是该链表中的一个节点(Node)。
标志组OS_FLAG_GRP的成员OSFlagWaitList就指向了信号量集的这个等待任务链表。
等待任务链表节点OS_FLAG_NODE的结构如下:
typedefstruct
{
void *OSFlagNodeNext; //指向下一个节点的指针
void *OSFlagNodePrev; //指向前一个节点的指针
void*OSFlagNodeTCB; //指向对应任务控制块的指针
void*OSFlagNodeFlagGrp; //反向指向信号量集的指针
OS_FLAGSOSFlagNodeFlags; //信号过滤器
INT8U OSFlagNodeWaitType; //定义逻辑运算关系的数据
}OS_FLAG_NODE;
其中OSFlagNodeWaitType是定义逻辑运算关系的一个常数(根据需要设置),也就是说如果OSFlagNodeWaitType取值为WAIT_CLR_ALL,那么当所有选择的信号量全部为0时,为有效状态,等待该信号量集的任务进入就绪状态。
常数
信号有效状态
等待任务的就绪条件
WAIT_CLR_ALL或WAIT_CLR_AND
0
信号全部有效(全0)
WAIT_CLR_ANY或WAIT_CLR_OR
0
信号有一个或一个以上有效(有0)
WAIT_SET_ALL或WAIT_SET_AND
1
信号全部有效(全1)
WAIT_SET_ANY或WAIT_SET_OR
1
信号有一个或一个以上有效(有1)
OSFlagFlags、OSFlagNodeFlags、OSFlagNodeWaitType三者的关系如下图
图中为了方便说明,我们将OSFlagFlags定义为8位,但是UCOSII支持8位/16位/32位定义,这个通过修改OS_FLAGS的类型来确定(UCOSII默认设置OS_FLAGS为16位)。
上图清楚的表达了信号量集各成员的关系:
OSFlagFlags为信号量表,通过发送信号量集的任务设置,任务发送信号量,对应位置1,否则为0;OSFlagNodeFlags为信号滤波器,由请求信号量集的任务设置,用于选择性的挑选OSFlagFlags中的部分(或全部)位作为有效信号;OSFlagNodeWaitType定义有效信号的逻辑运算关系,也是由请求信号量集的任务设置,用于选择有效信号的组合方式(0/1?
与/或?
)。
举个简单的例子,假设请求信号量集的任务设置OSFlagNodeFlags的值为0x0F,设置OSFlagNodeWaitType的值为WAIT_SET_ANY,那么只要OSFlagFlags的低四位的任何一位为1,请求信号量集的任务将得到有效的请求,从而执行相关操作,如果低四位都为0,那么请求信号量集的任务将得到无效的请求。
接下来我们看看在UCOSII中,与信号量集相关的几个函数。
1) 创建信号量集函数
任务可以通过调用函数OSFlagCreate来创建一个信号量集。
函数OSFlagCreate的原型为:
OS_FLAG_GRP *OSFlagCreate(OS_FLAGSflags,INT8U*err )。
其中,flags为信号量的初始值(即OSFlagFlags的值),err为错误信息,返回值为该信号量集的标志组的指针,应用程序根据这个指针对信号量集进行相应的操作。
2) 请求信号量集函数
任务可以通过调用函数OSFlagPend请求一个信号量集,函数OSFlagPend的原型为:
OS_FLAGSOSFlagPend(OS_FLAG_GRP*pgrp,OS_FLAGSflags,INT8Uwait_type,INT16Utimeout,INT8U*err)。
其中,pgrp为所请求的信号量集指针,flags为滤波器(即OSFlagNodeFlags的值),wait_type为逻辑运算类型(即OSFlagNodeWaitType的值),timeout为等待时限,err为错误信息。
3) 向信号量集发送信号函数
任务可以通过调用函数OSFlagPost向信号量集发信号,函数OSFlagPost的原型为:
OS_FLAGSOSFlagP