Download presentation
Presentation is loading. Please wait.
1
ucOS-II任务之间的通讯与同步
2
任务之间的通讯与同步 在μC/OS-II中,有多种方法可以保护任务之间的共享数据和提供任务之间的通讯。
一是利用宏OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()来关中断和开中断。当两个任务或者一个任务和一个中断服务子程序共享某些数据时,可以采用这种方法。 二是利用函数OSSchedLock()和OSSchekUnlock()对μC/OS-II中的任务调度函数上锁和开锁,用这种方法也可以实现数据的共享。 另外三种用于数据共享和任务通讯的方法:信号量、邮箱和消息队列。
3
任务和任务之间、任务和中断服务子程序之间的通讯
一个任务或者中断服务子程序可以通过事件控制块ECB(Event Control Blocks)来向另外的任务发信号[上图(1)]。这里,所有的信号都被看成是事件(Event),用于通讯的数据结构叫做事件控制块(包括信号量、邮箱或者消息队列等)。 一个任务还可以等待另一个任务或中断服务子程序给它发送信号[上图(2)]。注意,只有任务可以等待事件发生,中断服务子程序是不行的。对于处于等待状态的任务,还可以给它指定一个最长等待时间(Timeout),以此来防止因为等待的事件没有发生而无限期地等下去。
4
任务和任务之间、任务和中断服务子程序之间的通讯
多个任务可以同时等待同一个事件的发生[上图]。在这种情况下,当该事件发生后,所有等待该事件的任务中,优先级最高的任务得到了该事件并进入就绪状态,准备执行。
5
任务和任务之间的通讯 当事件控制块是一个信号量时,任务可以等待它,也可以给它发送消息。
6
事件控制块ECB uCOS_II.H 中定义的OS_EVENT数据结构用来维护一个事件控制块的所有信息。
该结构中除了包含了事件本身的定义,如用于信号量的计数器,用于指向邮箱的指针,以及指向消息队列的指针数组等,还定义了等待该事件的所有任务的列表。
7
ECB数据结构程序 typedef struct { void *OSEventPtr;
/只有当所定义的事件是邮箱或者消息队列时才使用。当所定义的事件是邮箱时,它指向一个消息,当所定义的事件是消息队列时,它指向一个数据结构。 INT8U OSEventTbl[OS_EVENT_TBL_SIZE]; /处于就绪状态的等待任务列表 INT16U OSEventCnt; /当事件是一个信号量时,它是用于信号量的计数器 INT8U OSEventType; /事件的具体类型,它可以是信号量(OS_EVENT_SEM)、邮箱(OS_EVENT_TYPE_MBOX)或消息队列(OS_EVENT_TYPE_Q)中的一种。用户要根据该域的具体值来调用相应的系统函数,以保证对其进行操作的正确性。 INT8U OSEventGrp; /处于就绪状态的等待任务所在的组} OS_EVENT;
8
每个等待事件发生的任务都被加入到该事件的事件控制块的等待任务列表中,该列表包括.OSEventGrp和.OSEventTbl[]两个域。
.OSEventTbl[]数组的大小由系统中任务的最低优先级决定,这个值由uCOS_II.H中的OS_LOWEST_PRIO常数定义。
9
.OSEventGrp和.OSEventTbl[] 的对应关系(事件的等待任务列表)
.OSEventTbl[] 和 .OSEventGrp之间的关系很像前面讲到的OSRdyTbl[]和OSRdyGrp之间的关系
10
将一个任务插入到事件的等待任务列表中 pevent->OSEventGrp |= OSMapTbl[prio >> 3];
pevent->OSEventTbl[prio >> 3] |= OSMapTbl[prio & 0x07]; 其中,prio是任务的优先级,pevent是指向事件控制块的指针。 插入一个任务到等待任务列表中所花的时间是相同的,和列表中现有多少个任务无关。 任务优先级的最低3位决定了该任务在相应的.OSEventTbl[]中的位置,紧接着的3位则决定了该任务优先级在.OSEventTbl[]中的字节索引。 该算法中用到的查找表OSMapTbl[](定义在OS_CORE.C中)一般在ROM中实现。
11
查找表OSMapTbl[]
12
从等待任务列表中删除一个任务 if ((pevent->OSEventTbl[prio >> 3] &= ~OSMapTbl[prio & 0x07]) == 0) {pevent->OSEventGrp &= ~OSMapTbl[prio >> 3];} 该代码清除了任务在.OSEventTbl[]中的相应位,如果其所在的组中不再有处于等待该事件的任务时(即.OSEventTbl[prio>>3]为0),将.OSEventGrp中的相应位也清除。 从等待任务列表中查找处于等待状态的最高优先级任务的算法,不是从.OSEventTbl[0]开始逐个查询,而是采用了查找另一个表OSUnMapTbl[256](见文件OS_CORE.C)。 这里,用于索引的8位分别代表对应的8组中有任务处于等待状态,其中的最低位具有最高的优先级。用这个值索引,首先得到最高优先级任务所在的组的位置(0~7之间的一个数)。然后利用.OSEventTbl[]中对应字节在OSUnMapTbl[]中查找,可以得到最高优先级任务在组中的位置(也是0~7之间的一个数)。这样,最终就可以得到处于等待该事件状态的最高优先级任务。
13
在等待任务列表中查找最高优先级的任务 y = OSUnMapTbl[pevent->OSEventGrp];
x = OSUnMapTbl[pevent->OSEventTbl[y]]; prio = (y << 3) + x; 例如,如果.OSEventGrp的值是 (二进制),而对应的OSUnMapTbl[.OSEventGrp]值为3,说明最高优先级任务所在的组是3。类似地,如果.OSEventTbl[3]的值是 (二进制),OSUnMapTbl[.OSEventTbl[3]]的值为2,则处于等待状态的任务的最高优先级是3×8+2=26。
14
在μC/OS-II中,事件控制块的总数由用户所需要的信号量、邮箱和消息队列的总数决定,该值由OS_CFG
在μC/OS-II中,事件控制块的总数由用户所需要的信号量、邮箱和消息队列的总数决定,该值由OS_CFG.H 中的#define OS_MAX_EVENTS定义。 在μC/OS-II的初始化调用OSInit()时,所有事件控制块被链接成一个单向链表——空闲事件控制块链表(如下图示)。 每当建立一个信号量、邮箱或者消息队列时,就从该链表中取出一个空闲事件控制块,并对它进行初始化。因为信号量、邮箱和消息队列一旦建立就不能删除,所以事件控制块也不能放回到空闲事件控制块链表中。
15
空闲事件控制块链表 对事件控制块进行的一些通用操作包括: 1、初始化一个事件控制块 2、使一个任务进入就绪态
3、使一个任务进入等待该事件的状态 4、因为等待超时而使一个任务进入就绪态 为了避免代码重复和减小代码长度,μC/OS-II将上面的操作用4个系统函数实现:OSEventWaitListInit(),OSEventTaskRdy(),OSEventWait()和OSEventTO()。
16
初始化一个事件控制块OSEventWaitListInit()
void OSEventWaitListInit (OS_EVENT *pevent) { INT8U i; pevent->OSEventGrp = 0x00; for (i = 0; i < OS_EVENT_TBL_SIZE; i++) {pevent->OSEventTbl[i] = 0x00;} } 当建立一个信号量、邮箱或者消息队列时,相应的建立函数OSSemInit(),OSMboxCreate(),或者OSQCreate()通过调用OSEventWaitListInit()对事件控制块中的等待任务列表进行初始化。 该函数初始化一个空的等待任务列表,其中没有任何任务。该函数的调用参数只有一个,就是指向需要初始化的事件控制块的指针pevent。
17
使一个任务进入就绪态OSEventTaskRdy()
当发生了某个事件,该事件等待任务列表中的最高优先级任务(Highest Priority Task – HPT)要置于就绪态时,该事件利用对应的OSSemPost(),OSMboxPost(),OSQPost(),和OSQPostFront()函数调用OSEventTaskRdy()来实现该操作。 或者,该函数从等待任务队列中删除HPT任务(Highest Priority Task),并把该任务置于就绪态。 OSEventTaskRdy()函数要在中断禁止的情况下调用。
18
使一个任务进入就绪状态 该函数首先计算HPT任务在.OSEventTbl[]中的字节索引(1),其结果是一个从0到OS_LOWEST_PRIO/8+1之间的数,利用该索引可以得到该优先级任务在.OSEventGrp中的位屏蔽码(2)(从OSMapTbl[]可以得到该值)。然后,OSEventTaskRdy()函数判断HPT任务在.OSEventTbl[]中相应位的位置(3),其结果是一个从0到OS_LOWEST_PRIO/8+1之间的数,以及相应的位屏蔽码(4)。
19
使一个任务进入就绪状态程序 void OSEventTaskRdy (OS_EVENT *pevent, void *msg, INT8U msk) { OS_TCB *ptcb; INT8U x; INT8U y; INT8U bitx; INT8U bity; INT8U prio; y = OSUnMapTbl[pevent->OSEventGrp]; (1) /计算HPT任务在.OSEventTbl[]中的字节索引 bity = OSMapTbl[y]; (2) /利用该索引得到该优先级任务在.OSEventGrp中的位屏蔽码 x = OSUnMapTbl[pevent->OSEventTbl[y]]; (3) /判断HPT任务在.OSEventTbl[]中相应位的位置 bitx = OSMapTbl[x]; (4) /以及相应的位屏蔽码 prio = (INT8U)((y << 3) + x); (5) /计算出HPT任务的优先级 if ((pevent->OSEventTbl[y] &= ~bitx) == 0) {(6) /从等待任务列表中删除该任务 pevent->OSEventGrp &= ~bity; }
20
使一个任务进入就绪状态程序(续) ptcb = OSTCBPrioTbl[prio]; (7) /知道了HPT任务的优先级,就可以得到指向该任务的任务控制块的指针 ptcb->OSTCBDly = 0; (8) /最高优先级任务运行条件已经得到满足,必须停止OSTimeTick()函数对.OSTCBDly域的递减操作,所以将该域清0 ptcb->OSTCBEventPtr = (OS_EVENT *)0; (9) /因为该任务不再等待该事件的发生,所以将其任务控制块中指向事件控制块的指针指向NULL #if (OS_Q_EN && (OS_MAX_QS >= 2)) || OS_MBOX_EN ptcb->OSTCBMsg = msg; (10) /如果OSEventTaskRdy()是由OSMboxPost()或者OSQPost()调用的,该函数还要将相应的消息传递给HPT,放在它的任务控制块中 #else msg = msg; #endif ptcb->OSTCBStat &= ~msk; (11) /当OSEventTaskRdy()被调用时,位屏蔽码msk作为参数传递给它。该参数是用于对任务控制块中的位清零的位屏蔽码,和所发生事件的类型相对应 if (ptcb->OSTCBStat == OS_STAT_RDY) { (12) /根据.OSTCBStat判断该任务是否已处于就绪状态 OSRdyGrp |= bity; (13) /如果是, 则将HPT插入到μC/OS-II的就绪任务列表中 OSRdyTbl[y] |= bitx; } }
21
使一个任务进入等待某事件发生状态OSEventTaskWait()
void OSEventTaskWait (OS_EVENT *pevent) { OSTCBCur->OSTCBEventPtr = pevent; (1) /将指向事件控制块的指针放到任务的任务控制块中 if ((OSRdyTbl[OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0) {(2) /将任务从就绪任务表中删除 OSRdyGrp &= ~OSTCBCur->OSTCBBitY; } pevent->OSEventTbl[OSTCBCur->OSTCBY] |= OSTCBCur->OSTCBBitX; (3) /把该任务放到事件控制块的等待任务表中 pevent->OSEventGrp |= OSTCBCur->OSTCBBitY; } 当某个任务要等待一个事件的发生时,相应事件的OSSemPend(),OSMboxPend()或者OSQPend()函数会调用该函数将当前任务从就绪任务表中删除,并放到相应事件的事件控制块的等待任务表中。
22
由于等待超时而将任务置为就绪态OSEventTO()
void OSEventTO (OS_EVENT *pevent) { if ((pevent->OSEventTbl[OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0) {(1)/从事件控制块中的等待任务列表里将任务删除 pevent->OSEventGrp &= ~OSTCBCur->OSTCBBitY; } OSTCBCur->OSTCBStat = OS_STAT_RDY;(2)/把它置成就绪状态 OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0;(3)/从任务控制块中将指向事件控制块的指针删除 } 当在预先指定的时间内任务等待的事件没有发生时,OSTimeTick()函数会因为等待超时而将任务的状态置为就绪。在这种情况下,事件的OSSemPend(),OSMboxPend()或者OSQPend()函数会调用OSEventTO()来完成这项工作。应当注意,调用OSEventTO()也应当先关中断。
23
信号量 μC/OS-II中的信号量由两部分组成:一个是信号量的计数值,它是一个16位的无符号整数(0 到65,535之间);另一个是由等待该信号量的任务组成的等待任务表。用户要在OS_CFG.H中将OS_SEM_EN开关量常数置成1,这样μC/OS-II才能支持信号量。 在使用一个信号量之前,首先要建立该信号量(调用OSSemCreate()函数),对信号量的初始计数值赋值,该初始值为0到65,535之间的一个数。 如果信号量是用来表示一个或者多个事件的发生,那么该信号量的初始值应设为0。如果信号量是用于对共享资源的访问,那么该信号量的初始值应设为1(例如,把它当作二值信号量使用)。最后,如果该信号量是用来表示允许任务访问n个相同的资源,那么该初始值显然应该是n,并把该信号量作为一个可计数的信号量使用。
24
任务、中断服务子程序和信号量之间的关系 μC/OS-II提供了5个对信号量进行操作的函数:OSSemCreate(),OSSemPend(),OSSemPost(),OSSemAccept()和OSSemQuery()函数。 图中用钥匙或者旗帜的符号来表示信号量:如果信号量用于对共享资源的访问,那么信号量就用钥匙符号。符号旁边的数字N代表可用资源数,对于二值信号量,该值就是1;如果信号量用于表示某事件的发生,那么就用旗帜符号。这时的数字N代表事件已经发生的次数。 OSSemPost()函数可以由任务或者中断服务子程序调用,而OSSemPend()和OSSemQuery()函数只能由任务程序调用。
25
建立一个信号量OSSemCreate()
OS_EVENT *OSSemCreate (INT16U cnt) { OS_EVENT *pevent; OS_ENTER_CRITICAL(); pevent = OSEventFreeList; (1)/ 从空闲任务控制块链表中得到一个事件控制块 if (OSEventFreeList != (OS_EVENT *)0) { (2)/ 对空闲事件控制链表的指针进行适当的调整,使它指向下一个空闲的事件控制块 OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;} OS_EXIT_CRITICAL(); if (pevent != (OS_EVENT *)0) {(3)/ 如果有任务控制块可用 pevent->OSEventType = OS_EVENT_TYPE_SEM; (4)/ 将该任务控制块的事件类型设置成信号量OS_EVENT_TYPE_SEM pevent->OSEventCnt = cnt; (5)/ 用信号量的初始值对任务控制块进行初始化 OSEventWaitListInit(pevent); (6)/ 对事件控制任务控制块的等待任务列表进行初始化} return (pevent); (7)/ 返回给调用函数一个指向任务控制块的指针 }
26
等待一个信号量 OSSemPend() void OSSemPend (OS_EVENT *pevent, INT16U timeout, INT8U *err) { OS_ENTER_CRITICAL(); if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {(1)/检查指针pevent所指的任务控制块是否是由OSSemCreate()建立的 OS_EXIT_CRITICAL(); *err = OS_ERR_EVENT_TYPE; } if (pevent->OSEventCnt > 0) {(2)/如果信号量当前是可用的(信号量的计数值大于0) pevent->OSEventCnt--; (3)/将信号量的计数值减1 OS_EXIT_CRITICAL(); *err = OS_NO_ERR; } else if (OSIntNesting > 0) {(4)/如果此时信号量无效(计数器的值是0),要进一步检查它的调用函数是不是中断服务子程序 OS_EXIT_CRITICAL(); *err = OS_ERR_PEND_ISR; } else { OSTCBCur->OSTCBStat |= OS_STAT_SEM; (5)/通过将任务控制块中的状态标志.OSTCBStat置1,把任务置于睡眠状态 OSTCBCur->OSTCBDly = timeout; (6)/等待时间也同时置入任务控制块中
27
等待一个信号量 OSSemPend()(续)
OSEventTaskWait(pevent); (7)/真正将任务置入睡眠状态的操作 OS_EXIT_CRITICAL(); OSSched(); (8)/因为当前任务已经不是就绪态了,所以任务调度函数将下一个最高优先级的任务调入,准备运行 OS_ENTER_CRITICAL(); if (OSTCBCur->OSTCBStat & OS_STAT_SEM) { (9)/检查任务控制块中的状态标志,看该任务是否仍处于等待信号量的状态 OSEventTO(pevent); (10)/调用OSEventTO()函数将任务从等待任务列表中删除,并返回给它的调用任务一个“超时”的错误代码。 OS_EXIT_CRITICAL(); *err = OS_TIMEOUT; } else {OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; (11)/如果得到了该信号量,将指向信号量ECB的指针从该任务的任务控制块中删除,并返回一个“无错”的代码 OS_EXIT_CRITICAL(); *err = OS_NO_ERR; } }
28
发送一个信号量OSSemPost() INT8U OSSemPost (OS_EVENT *pevent)
{ OS_ENTER_CRITICAL(); if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { (1)/检查参数指针pevent指向的任务控制块是否是OSSemCreate()函数建立的 OS_EXIT_CRITICAL(); return (OS_ERR_EVENT_TYPE); } if (pevent->OSEventGrp) { (2)/检查是否有任务在等待该信号量 OSEventTaskRdy(pevent, (void *)0, OS_STAT_SEM); (3)/把其中的最高优先级任务从等待任务列表中删除并使它进入就绪状态 OS_EXIT_CRITICAL(); OSSched(); (4)/检查该任务是否是系统中的最高优先级的就绪任务 return (OS_NO_ERR); } else {if (pevent->OSEventCnt < 65535) {pevent->OSEventCnt++; (5)/如果没有任务在等待该信号量,该信号量的计数值就简单地加1 OS_EXIT_CRITICAL(); return (OS_NO_ERR); } else {OS_EXIT_CRITICAL(); return (OS_SEM_OVF); } } }
29
无等待地请求一个信号量 OSSemAccept()
INT16U OSSemAccept (OS_EVENT *pevent) { INT16U cnt; OS_ENTER_CRITICAL(); if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { (1)/检查参数指针pevent指向的事件控制块是否是由OSSemCreate()函数建立的 OS_EXIT_CRITICAL(); return (0); } cnt = pevent->OSEventCnt; (2)/从该信号量的事件控制块中取出当前计数值 if (cnt > 0) { (3)/检查该信号量是否有效(计数值是否为非0值) pevent->OSEventCnt--; (4)/如果有效,则将信号量的计数值减1 } OS_EXIT_CRITICAL(); return (cnt); (5)/将信号量的原有计数值返回给调用函数 }
30
查询一个信号量的当前状态 OSSemQuery()
INT8U OSSemQuery (OS_EVENT *pevent, OS_SEM_DATA *pdata) /两个参数:一个是指向信号量对应事件控制块的指针pevent。该指针是在产生信号量时,由OSSemCreate()函数返回的;另一个是指向用于记录信号量信息的数据结构OS_SEM_DATA(见uCOS_II.H)的指针pdata。 { INT8U I; INT8U *psrc; INT8U *pdest; OS_ENTER_CRITICAL(); if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { (1)/先检查pevent指向的事件控制块是否是OSSemCreate()产生的 OS_EXIT_CRITICAL(); return (OS_ERR_EVENT_TYPE); } pdata->OSEventGrp = pevent->OSEventGrp; (2)/将等待任务列表从OS_EVENT结构拷贝到OS_SEM_DATA 结构变量中去 psrc = &pevent->OSEventTbl[0]; pdest = &pdata->OSEventTbl[0]; for (i = 0; i < OS_EVENT_TBL_SIZE; i++) {*pdest++ = *psrc++; } pdata->OSCnt = pevent->OSEventCnt; (3)/将计数值从OS_EVENT结构拷贝到OS_SEM_DATA 结构变量中去 OS_EXIT_CRITICAL(); return (OS_NO_ERR); }
31
邮箱 邮箱可以使一个任务或者中断服务子程序向另一个任务发送一个指针型的变量。该指针指向一个包含了特定“消息”的数据结构。为了在μC/OS-II中使用邮箱,必须将OS_CFG.H中的OS_MBOX_EN常数置为1。 使用邮箱之前,必须先建立该邮箱。可以通过调用OSMboxCreate()函数来完成,并且要指定指针的初始值。一般情况下,这个初始值是NULL,但也可以初始化一个邮箱,使其在最开始就包含一条消息。如果使用邮箱的目的是用来通知一个事件的发生(发送一条消息),那么就要初始化该邮箱为NULL,因为在开始时,事件还没有发生。如果用户用邮箱来共享某些资源,那么就要初始化该邮箱为一个非NULL的指针。在这种情况下,邮箱被当成一个二值信号量使用。
32
任务、中断服务子程序和邮箱之间的关系 符号“I”表示邮箱。邮箱包含的内容是一个指向一条消息的指针。一个邮箱只能包含一个这样的指针(邮箱为满时),或者一个指向NULL的指针(邮箱为空时)。 任务或者中断服务子程序可以调用函数OSMboxPost(),但是只有任务可以调用函数OSMboxPend()和OSMboxQuery()。
33
建立一个邮箱OSMboxCreate()
OS_EVENT *OSMboxCreate (void *msg) { OS_EVENT *pevent; OS_ENTER_CRITICAL(); pevent = OSEventFreeList; if (OSEventFreeList != (OS_EVENT *)0){ OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;} OS_EXIT_CRITICAL(); if (pevent != (OS_EVENT *)0) { pevent->OSEventType = OS_EVENT_TYPE_MBOX; (1)/事件控制块的类型被设置成OS_EVENT_TYPE_MBOX pevent->OSEventPtr = msg; (2)/用.OSEventPtr域来容纳消息指针 OSEventWaitListInit(pevent);} return (pevent); (3)/返回一个指向事件控制块的指针 } 邮箱一旦建立,是不能被删除的。比如,如果有任务正在等待一个邮箱的信息,这时删除该邮箱,将有可能产生灾难性的后果。
34
等待一个邮箱中的消息OSMboxPend()
void *OSMboxPend (OS_EVENT *pevent, INT16U timeout, INT8U *err) { void *msg; OS_ENTER_CRITICAL(); if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) {(1)/检查该事件控制块是否由OSMboxCreate()函数建立 OS_EXIT_CRITICAL();*err = OS_ERR_EVENT_TYPE;return ((void *)0);} msg = pevent->OSEventPtr; /将.OSEventPtr域的值复制到局部变量msg中 if (msg != (void *)0) {(2)/当.OSEventPtr域是一个非NULL的指针时,说明该邮箱中有可用的消息 pevent->OSEventPtr = (void *)0; (3)/将.OSEventPtr置为NULL OS_EXIT_CRITICAL(); *err = OS_NO_ERR; } else if (OSIntNesting > 0) {(4)/如果此时邮箱中没有消息可用(.OSEventPtr域是NULL指针),OSMboxPend()函数检查它的调用者是否是中断服务子程序 OS_EXIT_CRITICAL(); *err = OS_ERR_PEND_ISR; } else {OSTCBCur->OSTCBStat |= OS_STAT_MBOX; (5)/如果邮箱中没有可用的消息,OSMboxPend()的调用任务就被挂起,直到邮箱中有了消息或者等待超时 OSTCBCur->OSTCBDly = timeout; OSEventTaskWait(pevent); OS_EXIT_CRITICAL(); OSSched(); OS_ENTER_CRITICAL();
35
等待一个邮箱中的消息OSMboxPend()(续)
if ((msg = OSTCBCur->OSTCBMsg) != (void *)0) {(6)/检查是否有消息被放到该任务的任务控制块中 OSTCBCur->OSTCBMsg = (void *)0; OSTCBCur->OSTCBStat = OS_STAT_RDY; OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; OS_EXIT_CRITICAL(); *err = OS_NO_ERR;} else if (OSTCBCur->OSTCBStat & OS_STAT_MBOX) {(7)/检查任务控制块中的.OSTCBStat域中的OS_STAT_MBOX位,可以知道是否等待超时。如果该域被置1,说明任务等待已经超时 OSEventTO(pevent); (8)/通过调用函数OSEventTo()可以将任务从邮箱的等待列表中删除 OS_EXIT_CRITICAL(); msg = (void *)0; (9)/此时邮箱中没有消息,返回的指针是NULL *err = OS_TIMEOUT; } else {msg = pevent->OSEventPtr; (10)/如果OS_STAT_MBOX位没有被置1,说明所等待的消息已经被发出。OSMboxPend()的调用函数得到指向消息的指针 pevent->OSEventPtr = (void *)0; (11)/通过将邮箱事件控制块的.OSEventPtr域置为NULL清空该邮箱 OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; (12)/将任务任务控制块中指向邮箱事件控制块的指针删除 OS_EXIT_CRITICAL(); *err = OS_NO_ERR;} } return (msg); }
36
发送一个消息到邮箱中OSMboxPost()
INT8U OSMboxPost (OS_EVENT *pevent, void *msg) { OS_ENTER_CRITICAL(); if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) {(1)/检查事件控制块是否是一个邮箱 OS_EXIT_CRITICAL(); return (OS_ERR_EVENT_TYPE);} if (pevent->OSEventGrp) {(2)/检查是否有任务在等待该邮箱中的消息 OSEventTaskRdy(pevent, msg, OS_STAT_MBOX); (3)/如果有任务在等待该消息,调用OSEventTaskRdy()将其中的最高优先级任务从等待列表中删除 OS_EXIT_CRITICAL(); OSSched(); (4)/检查该任务是否是系统中最高优先级的就绪任务 return (OS_NO_ERR); } else {if (pevent->OSEventPtr != (void *)0) {(5)/如果没有任何任务等待该消息 OS_EXIT_CRITICAL(); return (OS_MBOX_FULL); } else {pevent->OSEventPtr = msg; (6)/保存指向消息的指针到邮箱中 OS_EXIT_CRITICAL(); return (OS_NO_ERR);}} }
37
无等待地从邮箱中得到一个消息OSMboxAccept()
void *OSMboxAccept (OS_EVENT *pevent) { void *msg; OS_ENTER_CRITICAL(); if (pevent->OSEventType !=OS_EVENT_TYPE_MBOX) {(1) /检查事件控制块是否是由OSMboxCreate()函数建立的 OS_EXIT_CRITICAL(); return ((void *)0); } msg = pevent->OSEventPtr; (2) /得到邮箱中的当前内容 if (msg != (void *)0) {(3) /判断是否有消息可用 pevent->OSEventPtr = (void *)0; (4) /如果邮箱中有消息,就把邮箱清空 } OS_EXIT_CRITICAL(); return (msg); (5) /邮箱中原来指向消息的指针被返回给OSMboxAccept()的调用函数 }
38
查询一个邮箱的状态OSMboxQuery()
INT8U OSMboxQuery (OS_EVENT *pevent, OS_MBOX_DATA *pdata) { /两个参数:一个是指向邮箱的指针pevent(在建立该邮箱时,由OSMboxCreate()函数返回的);另一个是指向用来保存有关邮箱信息的OS_MBOX_DATA数据结构的指针pdata INT8U i; INT8U *psrc; INT8U *pdest; OS_ENTER_CRITICAL(); if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { (1) /先检查事件控制是否是邮箱 OS_EXIT_CRITICAL(); return (OS_ERR_EVENT_TYPE); } pdata->OSEventGrp = pevent->OSEventGrp; (2) /将邮箱中的等待任务列表从OS_EVENT数据结构复制到OS_MBOX_DATA数据结构 psrc = &pevent->OSEventTbl[0]; pdest = &pdata->OSEventTbl[0]; for (i = 0; i < OS_EVENT_TBL_SIZE; i++) {*pdest++ = *psrc++;} pdata->OSMsg = pevent->OSEventPtr; (3) /将邮箱中的消息从OS_EVENT数据结构复制到OS_MBOX_DATA数据结构 OS_EXIT_CRITICAL(); return (OS_NO_ERR); }
39
邮箱作为二值信号量 OS_EVENT *MboxSem; void Task1 (void *pdata)
{ INT8U err; for (;;) {OSMboxPend(MboxSem, 0, &err); /* 获得对资源的访问权 */ /* 任务获得信号量,对资源进行访问 */ . . OSMboxPost(MboxSem, (void*)1); /* 释放对资源的访问权 */ } } 在初始化时,将邮箱设置为一个非零的指针(如 void *1)。这样,一个任务可以调用OSMboxPend()函数来请求一个信号量,然后通过调用OSMboxPost()函数来释放一个信号量。 如果用户只需要二值信号量和邮箱,这样做可以节省代码空间。这时可以将OS_SEM_EN设置为0,只使用邮箱就可以了。
40
用邮箱实现延时,而不使用OSTimeDly()
OS_EVENT *MboxTimeDly; void Task1 (void *pdata) { INT8U err; for (;;) {OSMboxPend(MboxTimeDly, TIMEOUT, &err); /* 延时该任务 */ . . /* 延时结束后执行的代码 */ . } } void Task2 (void *pdata) { INT8U err; for (;;) {OSMboxPost(MboxTimeDly, (void *)1); /* 取消任务1的延时 */ . . } } 如果在指定的时间段TIMEOUT内,没有消息到来,Task1()函数将继续执行,这和OSTimeDly(TIMEOUT)功能很相似。但是,如果Task2()在指定的时间结束之前,向该邮箱发送了一个“哑”消息,Task1()就会提前开始继续执行,这和调用OSTimeDlyResume()函数的功能是一样的。
41
消息队列 消息队列是μC/OS-II中另一种通讯机制,它可以使一个任务或者中断服务子程序向另一个任务发送以指针方式定义的变量。因具体的应用有所不同,每个指针指向的数据结构变量也有所不同。 为了使用μC/OS-II的消息队列功能,需要在OS_CFG.H 文件中,将OS_Q_EN常数设置为1,并且通过常数OS_MAX_QS来决定μC/OS-II支持的最多消息队列数。 在使用一个消息队列之前,必须先通过调用OSQCreate()函数建立该消息队列,并定义消息队列中的单元数(消息数)。
42
任务、中断服务子程序和消息队列之间的关系
可以将消息队列看作是多个邮箱组成的数组,它们共用一个等待任务列表,每个指针所指向的数据结构是由具体的应用程序决定的。 N代表了消息队列中的总单元数,当调用OSQPend()或者OSQAccept()之前,调用N次OSQPost()或者OSQPostFront()就会把消息队列填满。 一个任务或者中断服务子程序可以调用OSQPost(),OSQPostFront(),OSQFlush()或者OSQAccept()函数。但是,只有任务可以调用OSQPend()和OSQQuery()函数。
43
实现消息队列所需要的各种数据结构 首先需要事件控制块来记录等待任务列表(1),而且,事件控制块可以使多个消息队列的操作和信号量操作、邮箱操作具有相同的代码。 当建立了一个消息队列时,一个队列控制块(OS_Q结构,见OS_Q.C文件)也同时被建立,并通过OS_EVENT中的.OSEventPtr域链接到对应的事件控制块(2)。 在建立一个消息队列之前,必须先定义一个含有与消息队列最大消息数相同个数的指针数组(3)。数组的起始地址以及数组中的元素数作为参数传递给OSQCreate()函数。
44
μC/OS-II初始化时建立的一个空闲队列控制块链表
45
空闲队列控制块链表 队列控制块是一个用于维护消息队列信息的数据结构,包含了以下的一些域:
.OSQPtr在空闲队列控制块中链接所有的队列控制块。一旦建立了消息队列,该域就不再有用了。 .OSQStart是指向消息队列的指针数组的起始地址的指针。在使用消息队列之前必须先定义该数组。 .OSQEnd是指向消息队列结束单元的下一个地址的指针。该指针使得消息队列构成一个循环的缓冲区。 .OSQIn是指向消息队列中插入下一条消息的位置的指针。当.OSQIn和.OSQEnd相等时,.OSQIn被调整指向消息队列的起始单元。 .OSQOut是指向消息队列中下一个取出消息的位置的指针。当.OSQOut和.OSQEnd相等时,.OSQOut被调整指向消息队列的起始单元。 .OSQSize是消息队列中总的单元数。该值是在建立消息队列时由用户应用程序决定的。在μC/OS-II中,该值最大可以是65,535。 .OSQEntries是消息队列中当前的消息数量。当消息队列是空的时,该值为0。当消息队列满了以后,该值和.OSQSize值一样。 在消息队列刚刚建立时,该值为0。
46
消息队列是一个由指针组成的循环缓冲区 其中每个单元包含一个指针。队列未满时,.OSQIn (1)指向下一个存放消息的地址单元。如果队列已满(.OSQEntries与.OSQSize相等),.OSQIn (3)则与.OSQOut指向同一单元。如果在.OSQIn指向的单元插入新的指向消息的指针,就构成FIFO(First-In-First-Out)队列。相反,如果在.OSQOut指向的单元的下一个单元插入新的指针,就构成LIFO队列(Last-In-First-Out)(2)。当.OSQEntries和.OSQSize相等时,说明队列已满。消息指针总是从.OSQOut (4)指向的单元取出。指针.OSQStart和.OSQEnd (5)定义了消息指针数组的头尾,以便在.OSQIn和.OSQOut到达队列的边缘时,进行边界检查和必要的指针调整,实现循环功能。
47
建立一个消息队列OSQCreate() OS_EVENT *OSQCreate (void **start, INT16U size)
{ OS_EVENT *pevent; OS_Q *pq; OS_ENTER_CRITICAL(); pevent = OSEventFreeList; (1) /从空闲事件控制块链表中取得一个事件控制块 if (OSEventFreeList != (OS_EVENT *)0) {OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr; (2) /调整剩下的空闲事件控制块列表的指针,使它指向下一个空闲事件控制块 } OS_EXIT_CRITICAL(); if (pevent != (OS_EVENT *)0) {OS_ENTER_CRITICAL(); pq = OSQFreeList; (3) /从空闲队列控制块列表中取出一个队列控制块 if (OSQFreeList != (OS_Q *)0) {OSQFreeList = OSQFreeList->OSQPtr;} OS_EXIT_CRITICAL(); if (pq != (OS_Q *)0) {pq->OSQStart = start; (4) /如果空闲队列链表中有控制块可以使用,就对其进行初始化 pq->OSQEnd = &start[size]; pq->OSQIn = start; pq->OSQOut = start; pq->OSQSize = size; pq->OSQEntries = 0;
48
建立一个消息队列OSQCreate()(续)
pevent->OSEventType = OS_EVENT_TYPE_Q; (5) /将事件控制块的类型设置为OS_EVENT_TYPE_Q pevent->OSEventPtr = pq; (6) /使其.OSEventPtr指针指向队列控制块 OSEventWaitListInit(pevent); (7) /调用OSEventWaitListInit()函数对事件控制块的等待任务列表初始化 } else {OS_ENTER_CRITICAL(); pevent->OSEventPtr = (void *)OSEventFreeList; (8) /如果没有队列控制块可以使用,为了不浪费事件控制块资源,OSQCreate()函数将把刚刚取得的事件控制块重新返还给空闲事件控制块列表 OSEventFreeList = pevent; OS_EXIT_CRITICAL(); pevent = (OS_EVENT *)0; } } return (pevent); (9) /向调用函数返回一个指向事件控制块的指针 } 消息队列一旦建立就不能再删除了。
49
等待一个消息队列中的消息OSQPend()
void *OSQPend (OS_EVENT *pevent, INT16U timeout, INT8U *err) { void *msg; OS_Q *pq; OS_ENTER_CRITICAL(); if (pevent->OSEventType != OS_EVENT_TYPE_Q) { (1) /检查事件控制块是否是由OSQCreate()函数建立的 OS_EXIT_CRITICAL(); *err = OS_ERR_EVENT_TYPE; return ((void *)0);} pq = pevent->OSEventPtr; if (pq->OSQEntries != 0) { (2) /检查消息队列中是否有消息可用(即.OSQEntries是否大于0) msg = *pq->OSQOut++; (3) /如果有消息可用,OSQPend()函数将指向消息的指针复制到msg变量中,并让.OSQOut指针指向队列中的下一个单元 pq->OSQEntries--; (4) /将队列中的有效消息数减1 if (pq->OSQOut == pq->OSQEnd) {(5) /检查.OSQOut是否超过了队列中的最后一个单元 pq->OSQOut = pq->OSQStart; (6) /当发生这种越界时,就要将.OSQOut重新调整到指向队列的起始单元 } OS_EXIT_CRITICAL(); *err = OS_NO_ERR; }
50
等待一个消息队列中的消息OSQPend()(续)
else if (OSIntNesting > 0) {(7) /如果这时消息队列中没有消息(.OSEventEntries是0),OSQPend()函数检查它的调用者是否是中断服务子程序 OS_EXIT_CRITICAL(); *err = OS_ERR_PEND_ISR; } else {OSTCBCur->OSTCBStat |= OS_STAT_Q; (8) /如果消息队列中没有消息,调用OSQPend()函数的任务被挂起 OSTCBCur->OSTCBDly = timeout; OSEventTaskWait(pevent); OS_EXIT_CRITICAL(); OSSched(); (9) /当有其它的任务向该消息队列发送了消息或者等待时间超时,并且该任务成为最高优先级任务时,OSSched()返回 OS_ENTER_CRITICAL(); if ((msg = OSTCBCur->OSTCBMsg) != (void *)0) { (10) /检查是否有消息被放到该任务的任务控制块中 OSTCBCur->OSTCBMsg = (void *)0; OSTCBCur->OSTCBStat = OS_STAT_RDY; OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; (11) /如果有,那么该次函数调用成功,把任务的任务控制块中指向消息队列的指针删除 OS_EXIT_CRITICAL(); *err = OS_NO_ERR; }
51
等待一个消息队列中的消息OSQPend()(续)
else if (OSTCBCur->OSTCBStat & OS_STAT_Q) { (12) /如果任务控制块中的.OSTCBStat域,其对应的OS_STAT_Q位被置1,说明任务等待已经超时 OSEventTO(pevent); (13) /通过调用函数OSEventTo()可以将任务从消息队列的等待任务列表中删除 OS_EXIT_CRITICAL(); msg = (void *)0; (14) /因为消息队列中没有消息,所以返回的指针是NULL *err = OS_TIMEOUT; } else {msg = *pq->OSQOut++; (15) /如果任务控制块标志位中的OS_STAT_Q位没有被置1,说明有任务发出了一条消息。OSQPend()函数从队列中取出该消息 pq->OSQEntries--; if (pq->OSQOut == pq->OSQEnd) {pq->OSQOut = pq->OSQStart;} OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; (16) /将任务的任务控制中指向事件控制块的指针删除 OS_EXIT_CRITICAL(); *err = OS_NO_ERR; } } return (msg); (17) /将对应的消息返回到调用函数 }
52
向消息队列发送一个消息(FIFO)OSQPost()
INT8U OSQPost (OS_EVENT *pevent, void *msg) { OS_Q *pq; OS_ENTER_CRITICAL(); if (pevent->OSEventType != OS_EVENT_TYPE_Q) {(1) /确认事件控制块是否为消息队列 OS_EXIT_CRITICAL(); return (OS_ERR_EVENT_TYPE); } if (pevent->OSEventGrp) {(2) /检查是否有任务在等待该消息队列中的消息 OSEventTaskRdy(pevent, msg, OS_STAT_Q); (3) /如果有,从列表中取出最高优先级的任务 OS_EXIT_CRITICAL(); OSSched(); (4) /将它置于就绪状态。然后调用函数OSSched() return (OS_NO_ERR); } else {pq = pevent->OSEventPtr; if (pq->OSQEntries >= pq->OSQSize) {(5) /如果没有任务等待该消息队列中的消息,而且此时消息队列未满 OS_EXIT_CRITICAL(); return (OS_Q_FULL); } else {*pq->OSQIn++ = msg; (6) /指向该消息的指针被插入到消息队列中,下一个调用OSQPend()函数的任务就可以马上得到该消息。 pq->OSQEntries++; if (pq->OSQIn == pq->OSQEnd) { pq->OSQIn = pq->OSQStart; } OS_EXIT_CRITICAL(); } }
53
向消息队列发送一个消息(后进先出LIFO)OSQPostFront()
INT8U OSQPostFront (OS_EVENT *pevent, void *msg) { OS_Q *pq; OS_ENTER_CRITICAL(); if (pevent->OSEventType != OS_EVENT_TYPE_Q) {OS_EXIT_CRITICAL(); return (OS_ERR_EVENT_TYPE); } if (pevent->OSEventGrp) {OSEventTaskRdy(pevent, msg, OS_STAT_Q); OS_EXIT_CRITICAL(); OSSched(); return (OS_NO_ERR); } else {pq = pevent->OSEventPtr; if (pq->OSQEntries >= pq->OSQSize) {OS_EXIT_CRITICAL(); return (OS_Q_FULL); } else {if (pq->OSQOut == pq->OSQStart) {(1) /.OSQOut指针指向的是已经插入了消息指针的单元,再插入新的消息指针前,必须先将.OSQOut指针在消息队列中前移一个单元。如果.OSQOut指针指向的当前单元是队列中的第一个单元 pq->OSQOut = pq->OSQEnd; (2) /需要将该指针指向队列的末尾 } pq->OSQOut--; (3) /由于.OSQEnd指向的是消息队列中最后一个单元的下一个单元,因此.OSQOut必须被调整到指向队列的有效范围内 *pq->OSQOut = msg; pq->OSQEntries++; OS_EXIT_CRITICAL(); } return (OS_NO_ERR); } } OSQPostFront()函数和OSQPost()基本上是一样的,只是在插入新的消息到消息队列中时,使用.OSQOut作为指向下一个插入消息的单元的指针,而不是.OSQIn。
54
无等待地从一个消息队列中取得消息OSQAccept()
void *OSQAccept (OS_EVENT *pevent) { void *msg; OS_Q *pq; OS_ENTER_CRITICAL(); if (pevent->OSEventType != OS_EVENT_TYPE_Q) {(1) /首先查看pevent指向的事件控制块是否是由OSQCreate()函数建立 OS_EXIT_CRITICAL(); return ((void *)0); } pq = pevent->OSEventPtr; if (pq->OSQEntries != 0) {(2) /检查当前消息队列中是否有消息 msg = *pq->OSQOut++; (3) /如果消息队列中有至少一条消息,那么就从.OSQOut指向的单元中取出消息 pq->OSQEntries--; if (pq->OSQOut == pq->OSQEnd) {pq->OSQOut = pq->OSQStart;} } else {msg = (void *)0; (4) /如果该指针是NULL值,说明消息队列是空的,其中没有消息 } OS_EXIT_CRITICAL(); return (msg); }
55
清空一个消息队列OSQFlush() INT8U OSQFlush (OS_EVENT *pevent)
{ OS_Q *pq; OS_ENTER_CRITICAL(); if (pevent->OSEventType != OS_EVENT_TYPE_Q) {(1) /首先检查pevent指针是否是执行一个消息队列 OS_EXIT_CRITICAL(); return (OS_ERR_EVENT_TYPE);} pq = pevent->OSEventPtr; pq->OSQIn = pq->OSQStart; (2) /将队列的插入指针和取出指针复位,使它们都指向队列起始单元,同时,将队列中的消息数设为0 pq->OSQOut = pq->OSQStart; pq->OSQEntries = 0; OS_EXIT_CRITICAL(); return (OS_NO_ERR); }
56
查询一个消息队列的状态OSQQuery()
OSQQuery()需要两个参数:一个是指向消息队列的指针pevent。它是在建立一个消息队列时,由OSQCreate()函数返回的;另一个是指向OS_Q_DATA(见uCOS_II.H)数据结构的指针pdata。该结构包含了有关消息队列的信息。 在调用OSQQuery()函数之前,必须先定义OS_Q_DATA数据结构变量。OS_Q_DATA结构包含下面的几个域: .OSMsg 如果消息队列中有消息,它包含指针.OSQOut所指向的队列单元中的内容。如果队列是空的,.OSMsg包含一个NULL指针。 .OSNMsgs是消息队列中的消息数(.OSQEntries的拷贝)。 .OSQSize是消息队列的总的容量 .OSEventTbl[]和.OSEventGrp是消息队列的等待任务列表。通过它们, OSQQuery()的调用函数可以得到等待该消息队列中的消息的任务总数。
57
查询一个消息队列的状态OSQQuery()
INT8U OSQQuery (OS_EVENT *pevent, OS_Q_DATA *pdata) { OS_Q *pq; INT8U i; INT8U *psrc; INT8U *pdest; OS_ENTER_CRITICAL(); if (pevent->OSEventType != OS_EVENT_TYPE_Q) {(1) /检查pevent指针指向的事件控制块是一个消息队列 OS_EXIT_CRITICAL(); return (OS_ERR_EVENT_TYPE); } pdata->OSEventGrp = pevent->OSEventGrp; (2) /复制等待任务列表 psrc = &pevent->OSEventTbl[0]; pdest = &pdata->OSEventTbl[0]; for (i = 0; i < OS_EVENT_TBL_SIZE; i++) {*pdest++ = *psrc++; } pq = (OS_Q *)pevent->OSEventPtr; if (pq->OSQEntries > 0) { (3) /如果消息队列中有消息 pdata->OSMsg = pq->OSQOut; (4) /.OSQOut指向的队列单元中的内容被复制到OS_Q_DATA结构中 } else {pdata->OSMsg = (void *)0; (5) /否则的话,就复制一个NULL指针 } pdata->OSNMsgs = pq->OSQEntries; (6) /复制消息队列中的消息数和消息队列的容量大小 pdata->OSQSize = pq->OSQSize; OS_EXIT_CRITICAL(); return (OS_NO_ERR); }
58
使用消息队列读取模拟量的值 在控制系统中,经常要频繁地读取模拟量的值。这时,可以先建立一个定时任务OSTimeDly() ,并且给出希望的抽样周期。然后,让A/D采样的任务从一个消息队列中等待消息。该程序最长的等待时间就是抽样周期,当没有其它任务向该消息队列中发送消息时,A/D采样任务因为等待超时而退出等待状态并进行执行。
59
使用一个消息队列作为计数信号量 在消息队列初始化时,可以将消息队列中的多个指针设为非NULL值(如void* 1),来实现计数信号量的功能,初始化为非NULL值的指针数就是可用的资源数。 系统中的任务可以通过OSQPend()来请求“信号量”,然后通过调用OSQPost()来释放“信号量” 。如果系统中只使用了计数信号量和消息队列,使用这种方法可以有效地节省代码空间。将OS_SEM_EN设为0,就可以不使用信号量,而只使用消息队列。 这种方法为共享资源引入了大量的指针变量,为了节省代码空间,牺牲了RAM空间。另外,对消息队列的操作要比对信号量的操作慢,因此,当用计数信号量同步的信号量很多时,这种方法的效率是非常低的。
60
使用一个消息队列作为计数信号量(续) OS_EVENT *QSem;void *QMsgTbl[N_RESOURCES];
void main (void) { OSInit(); QSem = OSQCreate(&QMsgTbl[0], N_RESOURCES); for (i = 0; i < N_RESOURCES; i++) {OSQPost(Qsem, (void *)1); } OSTaskCreate(Task1, .., .., ..); OSStart(); } void Task1 (void *pdata) { INT8U err; for (;;) {OSQPend(&QSem, 0, &err); /* 得到对资源的访问权 */ /* 任务获得信号量,对资源进行访问 */ OSMQPost(QSem, (void*)1); /* 释放对资源的访问权 */ } }
Similar presentations