(忙于论文与找工作,多日未学习usos,今日论文初稿已交,协议书给了ali,得以继续学习。。。。。。。)
事件的类型:
信号量、互斥信号量、消息邮箱、消息队列
事件控制块的结构:
程序清单 L6.1 ECB数据结构 |
#if (OS_EVENT_EN > 0) && (OS_MAX_EVENTS > 0) typedef struct { INT8U OSEventType; INT8U OSEventGrp; INT16U OSEventCnt; void *OSEventPtr; INT8U OSEventTbl[OS_EVENT_TBL_SIZE]; } OS_EVENT; #endif |
.OSEventPtr指针,只有在所定义的事件是邮箱或者消息队列时才使用。当所定义的事件是邮箱时,它指向一个消息,而当所定义的事件是消息队列时,它指向一个数据结构。
.OSEventTbl[] 和 .OSEventGrp 很像前面讲到的OSRdyTbl[]和OSRdyGrp,只不过前两者包含的是等待某事件的任务,而后两者包含的是系统中处于就绪状态的任务。(见 就绪表)
.OSEventCnt 当事件是一个信号量时,.OSEventCnt是用于信号量的计数器。
.OSEventType定义了事件的具体类型。它可以是信号量(OS_EVENT_TYPE_SEM)、互斥信号量(OS_EVENT_TYPE_MUTEX)、邮箱(OS_EVENT_TYPE_MBOX)或消息队列(OS_EVENT_TYPE_Q)中的一种。用户要根据该域的具体值来调用相应的系统函数,以保证对其进行的操作的正确性。
每个等待事件发生的任务都被加入到该事件事件控制块中的等待任务列表中,该列表包括.OSEventGrp和.OSEventTbl[]两个域。
将一个任务放到事件的等待任务列表
程序清单 L6.2——将一个任务插入到事件的等待任务列表中 |
pevent->OSEventGrp |= OSMapTbl[prio >> 3]; |
pevent->OSEventTbl[prio >> 3] |= OSMapTbl[prio & 0x07]; |
其中,prio是任务的优先级,pevent是指向事件控制块的指针。
从等待任务列表中删除一个任务
程序清单 L6.3 从等待任务列表中删除一个任务 |
if ((pevent->OSEventTbl[prio >> 3] &= ~OSMapTbl[prio & 0x07]) == 0) { |
pevent->OSEventGrp &= ~OSMapTbl[prio >> 3]; |
} |
在等待任务列表中查找最高优先级的任务
程序清单 L6.4 在等待任务列表中查找最高优先级的任务 |
y = OSUnMapTbl[pevent->OSEventGrp]; |
x = OSUnMapTbl[pevent->OSEventTbl[y]]; |
prio = (y << 3) + x; |
空闲事件控制块链表
在µC/OS-II中,事件控制块的总数由用户所需要的信号量、邮箱和消息队列的总数决定。该值由OS_CFG.H 中的#define OS_MAX_EVENTS定义。在调用OSInit()时,所有事件控制块被链接成一个单向链表——空闲事件控制块链表(图 F6.3)。每当建立一个信号量、邮箱或者消息队列时,就从该链表中取出一个空闲事件控制块,并对它进行初始化。调用相应的删除操作OS???Del()时,可将事件控制块放回到空闲事件控制块链表中。
事件控制块使用的一些通用操作:
初始化一个事件控制块OSEventWaitListInit():
当建立一个信号量、邮箱或者消息队列时,相应的建立函数OSSemCreate(),OSMboxCreate(),或者OSQCreate()通过调用OSEventWaitListInit()对事件控制块中的等待任务列表进行初始化。该函数初始化一个空的等待任务列表,其中没有任何任务。该函数的调用参数只有一个,就是指向需要初始化的事件控制块的指针pevent。
使一个任务进入就绪态OSEventTaskRdy():
当发生了某个事件,该事件等待任务列表中的最高优先级任务(Highest Priority Task – HPT)要置于就绪态时,该事件对应的OSSemPost(),OSMboxPost(),OSQPost(),和OSQPostFront()函数调用OSEventTaskRdy()实现该操作。换句话说,该函数从等待任务队列中删除HPT任务(Highest Priority Task),并把该任务置于就绪态。
.OSEventTaskRdy()函数要在中断禁止的情况下调用。
使一个任务进入等待该事件的状态OSEventTaskWait():
当某个任务要等待一个事件的发生时,相应事件的OSSemPend(),OSMboxPend()或者OSQPend()函数会调用该函数将当前任务从就绪任务表中删除,并放到相应事件的事件控制块的等待任务表中。
因为等待超时而使一个任务进入就绪态OSEventTO():
当在预先指定的时间内任务等待的事件没有发生时,OSTimeTick()函数会因为等待超时而将任务的状态置为就绪。在这种情况下,事件的OSSemPend(),OSMboxPend()或者OSQPend()函数会调用OSEventTO()来完成这项工作。该函数负责从事件控制块中的等待任务列表里将任务删除[L6.8(1)],并把它置成就绪状态[L6.8(2)]。最后,从任务控制块中将指向事件控制块的指针删除[L6.8(3)]。用户应当注意,调用OSEventTO()也应当先关中断。
信号量的用法:
l 如果信号量是用来表示一个或者多个事件的发生,那么该信号量的初始值应设为0。
l 如果信号量是用于对共享资源的访问,那么该信号量的初始值应设为1(例如,把它当作二值信号量使用)。
l 如果该信号量是用来表示允许任务访问n个相同的资源,那么该初始值显然应该是n,并把该信号量作为一个可计数的信号量使用。
功能函数:
µC/OS-II提供了6个对信号量进行操作的函数。它们是:OSSemCreate(),OSSemDel(),OSSemPend(),OSSemPost(),OSSemAccept()和OSSemQuery()函数。
各个函数使用者情况表:
功能函数 |
任务 |
中断服务程序 |
OSSemCreate |
Y |
N |
OSSemDel |
Y |
N |
OSSemPend |
Y |
N |
OSSemPost |
Y |
Y |
OSSemAccept |
Y |
Y |
OSSemQuery |
Y |
Y |
等待一个信号量, OSSemPend()
如果信号量当前是可用的(信号量的计数值大于0)[L6.10(2)],将信号量的计数值减1[L6.10(3)],然后函数将“无错”错误代码返回给它的调用函数。
如果信号量的计数值为0,而OSSemPend()函数又不是由中断服务子程序调用的,则调用OSSemPend()函数的任务要进入睡眠状态,等待另一个任务(或者中断服务子程序)发出该信号量(见下节)。OSSemPend()允许用户定义一个最长等待时间作为它的参数,这样可以避免该任务无休止地等待下去。如果该参数值是一个大于0的值,那么该任务将一直等到信号有效或者等待超时。如果该参数值为0,该任务将一直等待下去。OSSemPend()函数通过将任务控制块中的状态标志.OSTCBStat置1(OS_STAT_SEM),把任务置于睡眠状态[L6.10(5)],等待时间也同时置入任务控制块中[L6.10(6)],该值在OSTimeTick()函数中被逐次递减。注意,OSTimeTick()函数对每个任务的任务控制块的.OSTCBDly域做递减操作(只要该域不为0)[见3.10节,时钟节拍]。真正将任务置入睡眠状态的操作在OSEventTaskWait()函数中执行 [见6.03节,让一个任务等待某个事件,OSEventTaskWait()][L6.10(7)]。
因为当前任务已经不是就绪态了,所以任务调度函数将下一个最高优先级的任务调入,准备运行(OS_Sched())。当信号量有效或者等待时间到后,调用OSSemPend()函数的任务将再一次成为最高优先级任务。这时OSSched()函数返回。这之后,OSSemPend()要检查任务控制块中的状态标志,看该任务是否仍处于等待信号量的状态[L6.10(9)]:
1)如果是,说明该任务还没有被OSSemPost()函数发出的信号量唤醒。事实上,该任务是因为等待超时而由TimeTick()函数把它置为就绪状态的。这种情况下,OSSemPend()函数调用OSEventTO()函数将任务从等待任务列表中删除[L6.10(10)],并返回给它的调用任务一个“超时”的错误代码。
2)如果任务的任务控制块中的OS_STAT_SEM标志位没有置位(经OSSemPost函数调用OS_EventTaskRdy函数清除),就认为调用OSSemPend()的任务已经得到了该信号量,将指向信号量ECB的指针从该任务的任务控制块中删除,并返回给调用函数一个“无错”的错误代码。
发送一个信号量, OSSemPost()
它首先检查参数指针pevent指向的任务控制块是否是OSSemCreate()函数建立的[L6.11(1)],接着检查是否有任务在等待该信号量[L6.11(2)]。如果该任务控制块中的.OSEventGrp域不是0,说明有任务正在等待该信号量。这时,就要调用函数OSEventTaskRdy()[见6.02节,使一个任务进入就绪状态,OSEventTaskRdy()],把其中的最高优先级任务从等待任务列表中删除[L6.11(3)]并使它进入就绪状态。然后,调用OSSched()任务调度函数检查该任务是否是系统中的最高优先级的就绪任务[L6.11(4)]。如果是,这时就要进行任务切换[当OSSemPost()函数是在任务中调用的],准备执行该就绪任务。如果不是,OSSched()直接返回,调用OSSemPost()的任务得以继续执行。如果这时没有任务在等待该信号量,该信号量的计数值就简单地加1[L6.11(5)]。
上面是由任务调用OSSemPost()时的情况。当中断服务子程序调用该函数时,不会发生上面的任务切换。如果需要,任务切换要等到中断嵌套的最外层中断服务子程序调用OSIntExit()函数后才能进行(见3.09节,µC/OS-II中的中断)。