




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、29嵌入式系统软件设计方法和代码实现 第25:uc/os-ii 实时操作系统第25:uc/os-ii 实时操作系统 本章所涉及的操作系统理论知识可参阅各种操作系统教科书,比如现代操作系统 【美】andrew s.tanenbaum著 陈向群等译 机械工业出版社 1999年11月目 录:2.5.1 概述2.5.2 任务栈切换方法2.5.3 优先级别算法2.5.4 任务通信使用邮箱2.5.5 任务通信使用消息队列2.5.6 对共享资源的互斥访问控制与信号(semaphore)2.5.7 任务同步与信号(signal)2.5.8 内存分配方法2.5.8 缺陷与改进(1:任务调度问题,2:通信问题:任
2、务通信模型与uc/os-ii通信机制的缺陷)附录1:uc/os-ii文件列表附录2:相关函数列表附录3:主要变量和数据类型列表2.5.1 概述技术指标 uc/os-ii是一个占先式实时多任务操作系统内核,但不支持时间片调度,支持任务间通信,提供了多种对共享资源的访问控制,如禁止切换,调度上锁等。uc/os-ii的也是一个可剪裁的系统,可以根据需要保留或者删除某些功能,任务数最多可达64个。下表中给出了2种常用情况下的目标代码大小。序号目标码(程序+数据)任务数邮箱消息队列信号量任务栈大小18k6k6255151225k+4k160101512注:该表数据是根据 uc/os-ii -源码公开的实
3、时嵌入式操作系统书中p229,p230 的表9.1和表9.2综合得到的.通过改变os_cfg.h文件中的各种配制,可以取消或者保留某些功能,并且改变某些数据结构的大小,在uc/os-ii中,影响内存大小的数据结构主要包括任务数量,事件控制块数量和任务堆栈大小。uc/os-ii源代码 uc/os-ii的x86版本代码规模约为5000行,包括用于应用任务的例子代码和辅助代码,核心代码约3500行,分布在 17 个文件中【详见附录1】。 任务状态 uc/os-ii的任务一般会处于以下6状态之一,1. 运行态2. 就绪态3. 等待信号量4. 等待邮箱消息5. 等待消息队列消息6. 挂起在某一时刻,系统
4、中只会有一个任务处于“运行态”,处于其他各种状态的任务数量没有限制,“挂起态”的进入是任务主动进行的,其他状态一般由各种外部因素造成。只有处于“就绪态”的任务才可能成为下个要执行的任务。任务切换过程和优先级别算法 uc/os-ii不支持时间片调度切换,而仅仅提供按任务优先级别的切换,提供了2种任务切换方式,主动切换和被动切换,前者是应用任务通过调用系统函数自动将自己挂起,后者是操作系统根据当前任务运行情况,将正在运行的任务强制挂起,从而切换到另一个任务执行。被动切换对于任务来说是不可预知的,因此可能发生在任意代码位置。uc/os-ii的任务优先级别一般采用常量定义,实际上属于静态优先级别,即在
5、任务运行过程中,系统不改变任务的优先级别,但提供了一个优先级别改变的接口函数,用户任务可以通过调用该函数来修改自身或者其他任务的优先级别。由于uc/os-ii不支持时间片调度,因此如果一个任务函数代码中包含无限循环代码的话,其他低优先级别得任务将永远得不到调度。对于高优先级别任务,也可能得不到执行。任务之间的数据传递 在uc/os-ii中,提供了邮箱和消息队列2种方式来满足任务之间的数据传递,在使用邮箱时,如果收信任务没有将数据取走,就无法传送下个消息,而一个消息队列方式实际上相当与多个邮箱,因此可以连续传送多条消息。共享资源访问控制 uc/os-ii中可以使用禁止调度、设置信号量来保证共享资
6、源访问的排它性。系统的信号量数量由事件控制块数量决定。任务同步 如果一个任务需要立即切换到另外一个指定任务,可以通过设置信号量来实现。在uc/os-ii中,实现任务间同步运行需要较为较为负责的处理。uc/os-ii的使用问题 uc/os-ii只是一个任务调度内核,没有用于某个特定硬件环境的i/o系统,例如tcp/ip协议栈、图形gui等,需要另外开发。此外,uc/os-ii上也不能使用磁盘或者flash存储器的文件系统,不支持中文显示和中文输入接口,在uc/os-ii上开发应用产品任然需要做大量工作。2.5.2 调度过程:任务栈切换方法任务栈的定义与赋初值 uc/os-ii是采用独立任务栈的切
7、换方法,给每个任务分配一个任务栈数组,比如在例子1中ex1l.c文件中定义的“taskstk ”变量, 在切换时将处理器的堆栈指针sp指向该数组,从而实现任务切换。任务栈数组(如“taskstk0 ”)在系统初始化时被赋予每个任务控制块(os_tcb)的“os_stk *ostcbstkptr”变量,这个赋值过由函数:taskstart()- ostaskcerat( ) -ostaskstkinit()ostcbinit()的依次执行完成,具体过程见【表1】:表格 1:任务栈的初始化过程步骤文件函数名堆栈变量传递 : sp指针说明1ex1l.cmain( )taskstkn_taskstas
8、k_stk_size 所有任务的栈数组2ex1l.ctaskstart( )taskstkitask_stk_size-1 一个任务栈的栈底。3os_task.costaskcerat( )ptospspptos:栈底psp:初始化后底栈顶。4os_cpu_c.costaskstkinit( )ptospsp5os_core.costcbinit( )ptosostcbstkptrostcbstkptr= ptos首次执行的任务 uc/os-ii内核初始化完成后,执行函数“_osstarthighrdy( ) ”来启动第一个任务,而第一个任务底选择依据是任务的优先级别,选择过程在“osstar
9、t ( )”中完成,【图1】表明系统启动后,任务首次调度过程。main( )osstart ( )_osstarthighrdy( )任务函数starttask( )图 1任务的首次启动执行过程在一般的函数调用中,子函数执行完成后,继续返回主调函数执行,而调度函数通过改变堆栈内容来变更原来的程序执行路线,【图1】中,函数“ _osstarthighrdy( )”执行“reti”指令后,并不返回“osstart ( )”,而是执行了一个任务函数,例如uc/os-ii的例2中,执行“starttask( )”。下次执行的任务一个任务在运行时,在遇到外部资源和条件未准备好时,可以调用内核切换函数,以
10、挂起本任务,暂时切换到另外一个任务运行,还有一种切换是在硬件中断中进行的,当前任务被动地被中止,uc/os-ii支持这2种切换。在uc/os-ii中,完成任务栈切换的函数是“_osctxsw( )”和“_osintctxsw( )”,前者用于任务的主动切换,后者任务被动切换,在中断服务例程中使用,二者的原理相同,主要的作用是保留当前任务堆栈,将cpu的sp寄存器指向下个任务堆栈数组。“_osctxsw( )”切换相关的代码如下: pusha ; 保存当前任务的现场 push es ; pushds ; mov ax, seg _ostcbcur ; 重装当前任务的数据段寄存器 mov ds,
11、ax ; les bx, dword ptr ds:_ostcbcur ; 保存挂起任务的堆栈指针sp mov es:bx+2, ss mov es:bx+0, sp .les bx, dword ptr ds:_ostcbhighrdy ; 切换sp到要运行任务的堆栈空间 mov ss, es:bx+2 mov sp, es:bx pop ds ; 恢复新任务的现场环境pop es ; popa ; iret ; 该指令执行后,将执行新任务,而不是;返回其主调函数。现在,uc/os-ii如何从当前任务切换到一个新的任务已经清楚了,现在的问题是:如何确定那个任务是下次执行的任务呢?2.5.2
12、调度过程:优先级别算法任务选择和任务优先级别 uc/os-ii调度程序是将“任务就绪表”中的最高优先级别的任务作为下个要执行的任务,换句话说,任务运行要符合2个条件,一是其状态是“就绪态”的,二是在所有就绪态任务中,该任务的优先级别是最高的(数值最小)。系统中可能存在多个就绪的任务,即就绪表中有多个比特被置位,uc/os-ii通过一个简单的查表算法,可以快速得到其中的最高优先级任务,其算法代码如下(见“ossched( )” 和“ osstart ( )”代码):y = osunmaptblosrdygrp; (1)x = osunmaptblosrdytbly; (2)ospriohighr
13、dy = (int8u)(y 下,同一字节中为左右,即左上方的位,其优先级别最高(0),右下方的的数字表示优先级别最低(63)在这样一个256比特的就绪表中,如何确定最高优先级别的任务所在的位置呢?也就是计算除最高优先级别的任务优先级别?在上图中为“12”。一种方法是从最高位开始逐位判断是否为“1”,如为“1”,则停止向下搜索,在具体计算中,先找到其组号,再从该组的就绪表字节中找到位号,如下:char osgethprio( char osrdygrp, char osrdytbl )chari, j,/*i=组号变量,j=组内位号*/bitmask = 0x01, 0x02, 0x04, 0
14、x08, 0x10, 0x20, 0x40, 0x80;for(i=0;i8;i+)/*找到最高优先级在就绪表中的组号i*/if( osrdygrp & bitmaski !=0 ) break;for(j=0; jostcbprio )?思考题4:一个任务调用 “ossched( )”后是否会必然不会回到原处继续执行,而是转向执行其他任务?思考题5:在【图表3】中的系统函数中,那些执行时一定会发生任务切换?,那些函数是在一定条件下发生任务切换?对于在一定条件下发生任务切换的函数,切换调试是什么?2.5.4 任务间的通信:邮箱邮政系统模型 在现实邮政系统中,一次信件的传递如下图:xx收11图
15、5:邮政系统模型示意:公用邮箱和私用邮箱在这里,有2个邮箱存在,一个是公用的发件邮箱,一个是私用的收件邮箱,邮递员完成2个邮箱之间的传递,收发信人从邮箱中取或者往邮箱里发邮件。在邮政系统中,为了使收信人和邮递员对信件进行识别,信件上还必须写明收件人和发件人地址。uc/os-ii中的邮箱特性 uc/os-ii的邮箱通信比现实邮政系统简单,为了完成一次2个任务间的消息传递,只需要一个邮箱,并且邮箱中只能存放一个消息,所以,在消息没有取走前,无法往邮箱里存放新的消息。和邮政系统的另一个不同点是,邮政传送中,邮箱存放和传递的是信件内容本身,uc/os-ii只是存放和传递消息内容缓冲区的位置指针。假设系
16、统中2个任务,只使用一个邮箱,那么,通信只能是实现简单arq协议,也就是无窗口的半双工通信【图4】,除非你使用多个邮箱。时间t任务a 任务b邮箱图 6:一个邮箱只能实现无窗口的半双工通信在uc/os-ii中,调度系统不能自动通知某个任务邮箱中已经有消息,需要任务主动去取邮件(调用函数 osmboxpend ( )),这种情况好像邮政系统中的平信传递,邮局只负责将信放入邮箱,但并不通知收信人,如果我们一直开邮箱,信件会永远躺在邮箱中睡大觉。从这点上说,uc/os-ii的邮箱通信机制并不是一个很有效率的通信机制,在很多应用中,我们希望一种类似邮政快件的通信机制,在有信件后,收信人应自动并立即得到通
17、知,而不同平信机制,即使我们一年中只有一封信,也要开365次信箱。但也有一点不同,对于同一邮箱的取信操作和发信操作,从时间上有2种情况,即先发后取和先取后发,在先取后发的情况下,取信任务在取不到信件后会进入挂起状态,同时进行登记,调度系统可以在另一任务发信后,自动调度事先取信的任务立即执行。这好像我们在取信未果时在邮箱里给邮递员留个便条,让他在信来时给自己挂个电话。邮箱的操作过程 uc/os-ii邮箱通信的过程是先建立一个数据缓冲区,即创建邮箱,一个系统中或者一个任务可以建立多个邮箱,不同邮箱通过邮箱指针来识别,该指针在建立邮箱时产生。任务之间使用邮箱方式来传递数据的过程和使用的函数如下:1.
18、 建立邮箱(osmboxcreate ( ))2. 往邮箱“发”消息(osmboxpost ( ))3. 从邮箱“取”消息(osmboxpend ( ) )邮箱由使用邮箱的任务函数建立,也可在系统初始化函数中建立,邮箱一但建立,建立后将得到“os_event”类型的结构指针,任务函数通过该指针可对邮箱进行存取操作。一个系统中可以存在多个邮箱,对于接收消息的任务函数来说,应该知道从那个邮箱中取消息,邮箱的识别是创建邮箱时“osmboxcreate ( )”函数的返回值来得到的。互相发送和接收消息的2个任务使用同一个全局变量(“os_event”类型)来标识一个邮箱。如果从2个任务存取邮箱操作的时
19、间先后来看,有2种情况:1. 先发消息,后取消息2. 先取消息,后发消息对于第1种情况,没有什么需要解释,如果是第2种情况,则取消息的任务会因为取不到消息而进入挂起状态【图5】,同时在一个被称为“事件链表 就是os_event类型的数据结构,该结构在 osinit ( ) 函数中被初始化为一个空链表。链表的长度是系统同时允许存在的事件数目,该值由 os_max_events 宏定义决定。”的数据结构中进行登记, 这样当发消息时,函数 自动检查该“事件链表”,发现已经有任务等待该消息,则会立即执行它。发信任务a 收信任务b邮箱满任务b挂起任务a挂起邮箱空时间轴图 7:先收后发时的任务挂起在下面发
20、消息的函数osmboxpost()代码中,在路径时,表明之前已经有任务等待该任务,从而会执行该任,而将发消息的任务挂起。在路径时为正常的发消息过程,将消息指针“msg”付给事件链表“pevent”的消息指针“oseventptr”。int8u osmboxpost (os_event *pevent, void *msg) os_enter_critical(); if (pevent-oseventtype != os_event_type_mbox) /* 非邮箱操作,返回 */ os_exit_critical(); return (os_err_event_type); /* 是否已经
21、任务在等待取邮件 ,如有,则切还到该任务执行 */ if (pevent-oseventgrp) oseventtaskrdy(pevent, msg, os_stat_mbox); /* 在多个等待任务中,将最优先级最高者在就绪表中置位 */ os_exit_critical(); ossched(); /* 切换执行 */ return (os_no_err); else if (pevent-oseventptr != (void *)0) /* 检查邮箱的空/满? */ os_exit_critical(); return (os_mbox_full); /* 邮箱为满,不能发消息 *
22、/ else pevent-oseventptr = msg; /* 邮箱为空,发消息 */ os_exit_critical(); return (os_no_err); 邮箱数据结构和变量 和邮箱相关的数据结构主要是“os_event”,另外一个结构“os_mbox_data”实际上只在函数中作为局部变量使用,似乎是多余的。此外,在先收后发的情况下,也使用了结构“os_tcb”的“ostcbmsg”分量来存储消息指针,造成代码过于复杂和不一致性【见思考题1】。邮箱消息处理的数据结构如下定义如下:typedef struct void *oseventptr; /* 消息指针 */int8u
23、 oseventtblos_event_tbl_size; /* 所有等待某个事件的任务列表 */ int16u oseventcnt; /* count of used when event is a semaphore */int8u oseventtype; /* 邮箱事件os_event_type_mbox, 消息列表时间os_event_type_q 信号量事件os_event_type_sem */int8u oseventgrp;/* 等待某个事件的任务所属组号 */ os_event;该结构在uc/os-ii中被称为“事件”,因为不仅用于邮箱消息,也用于任务之间的事件通知,所以
24、定义了一些其他变量,对于邮箱通信,只需要其中的“oseventptr” 、“oseventtbl”、和“oseventgrp”3个域就可以了。邮箱的容量 在邮箱创建时,系统并不分配邮箱的大小,邮箱的存储空间实际上是有发信任务定义的,在消息传递过程中,只传递所发消息的指针,函数osmboxpost( )将消息指针传递给“os_event”的“oseventptr”域。对于使用消息的任务函数来说,要注意的是,由于是指针传递,使用局部变量来存储发送消息,消息可能会在被收到前消失,所以,最好用全局变量来存储发送的消息 在uc/os-ii的例子代码中(如ex2l.c 的task2( )函数),发送消息函
25、数位于无限循环代码中,因此虽然使用局部变量存储消息,但该函数永远不会退出。邮箱消息的通知机制 “oseventtbl”、和“oseventgrp”变量用于记录那些任务在等待邮箱消息或者其他事件,记录的方式和就绪表“osrdytbl”、“osrdygrp”是一致的,查找方式也一样,使用了“osunmaptbl ”映射表。下面代码在ex2l.c文件中,说明了“task4( )”和“ task5()”2个任务间的邮箱通信过程,在这里,使用了2个邮箱实现了全双工的应答式通信:void task4 (void *data)char txmsg;int8u err;data = data;txmsg =
26、a;for (;) while (txmsg oseventptr; os_exit_critical(); if (pevent != (os_event *)0) /* 判断事件控制块是否为空,如为空,直接返回*/ os_enter_critical(); pq = osqfreelist; /* 取出消息队列链表中的首个队列结构,*/ if (osqfreelist != (os_q *)0) osqfreelist = osqfreelist-osqptr; os_exit_critical(); if (pq != (os_q *)0) /* 如消息队列不为空,则对此队列进行初始化*
27、/ pq-osqstart = start; pq-osqend = &startsize; pq-osqin = start; pq-osqout = start; pq-osqsize = size; pq-osqentries = 0; pevent-oseventtype = os_event_type_q; pevent-oseventptr = pq; oseventwaitlistinit(pevent);/* 将事件控制块中的任务等待标志置为“0”*/ else /* 无消息队列可用,将空闲事件控制块链表的首指针恢复 */ os_enter_critical(); pevent
28、-oseventptr = (void *)oseventfreelist; oseventfreelist = pevent; os_exit_critical(); pevent = (os_event *)0; return (pevent);/* 返回该消息队列操作的事件句柄 */以上创建消息队列的过程和邮箱基本类似,不同的是,消息队列变量(os_q *pq)是从一个消息队列链表中得到的,该链表在“ osqinit ()” osqinit ()被初始化函数“osinit( )”所调用( 被osinit( )调用 )中被初始化。消息队列链表的作用在系统中有多个消息队列的情况下,uc/os
29、-ii将多个队列形成一个链表,使用消息链表的本意是,在某个消息队列不再使用时,可以释放该结构,以便其他任务使用,这样就能节约存储空间,可系统没有提供释放消息队列的操作函数,消息链表的存在没有实际意义。而另一方面,对于嵌入式系统,任务之间的通信情况一般是可预知的,所以,一但创建了消息队列,并不需要丢弃。发送消息 在一个任务调用 “osqpost ( )”函数发送消息时,有3种情况:1. 已经有任务在等待该消息。2. 消息队列已满,无法传递新的消息。3. 消息队列未满,可以正常传递消息,同时也没有其他任务在等待该消息。这3种情况的处理是不同的, 情况1时会立即转向执行等待该消息的任务, 情况2返回一个错误信息,情况3
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 语言、文化与交际知到课后答案智慧树章节测试答案2025年春湖南大学
- 江苏省徐州市2024-2025学年高一上学期1月期末信息技术试题 含解析
- 2024年自然资源部第一海洋研究所招聘真题
- 2025汽车零部件供应商合同管理咨询协议
- 高一英语学案:预习导航SectionⅡ
- 深圳施工总价合同范本
- 2024年山东济南福和数控机床有限公司招聘真题
- 2024年梅河口市市属事业单位考试真题
- 2024年廉江市市属事业单位考试真题
- 光缆颗粒采购合同范本
- ICH-Q9:风险管理分享课件
- 整流变压器试验报告
- 施工进场通知书
- 步进电机控制系统课件
- 幼儿园小班科学艺术:《欢乐的小芽儿》 课件
- 子宫肌瘤课件PPT(共38张PPT)
- 汉字的五行属性与三才五格计算方法
- 唐山高科总部大厦幕墙工程幕墙招标技术评估总结
- 苏教版三年级下册数学 第三单元 解决问题的策略 测试卷
- 《学前教育科学研究方法》全套课件(完整版)
- 机电经典安装工程相册图解PPT86页
评论
0/150
提交评论