标签:
Ucos为了任务之间的通讯定义了信号量,互斥性信号量,消息对象 消息队列等结构以及api,为了统一的管理这些同步,定义了一个结构叫做时间控制块OS_EVENT,如下
typedef struct os_event {
INT8U OSEventType;
void *OSEventPtr;
INT16U OSEventCnt;
OS_PRIO OSEventGrp;
OS_PRIO OSEventTbl[OS_EVENT_TBL_SIZE];
#if OS_EVENT_NAME_EN > 0u
INT8U *OSEventName;
#endif
} OS_EVENT;
OSEventType为事件类型,有信号量类型,互斥性信号量类型,消息邮箱类型,消息队列类型和未定义类型五种
OSEventPtr消息或者消息队列的指针
OSEventCnt 信号量计数器
OSEventTbl 等待任务表
OSEventGrp 等待事件的任务组
该结构我们称之为时间控制体ECB,系统中拥有的全部ECB的数量为
OS_EXT OS_EVENT OSEventTbl[OS_MAX_EVENTS];
OS_MAX_EVENTS是在系统配置文件中指定的,也就是编译时决定有几个事件控制体
每个事件控制体内部都有OSEventTbl这个数组, OS_EVENT_TBL_SIZE是一个宏定义,展开之后的定义为((OS_LOWEST_PRIO) / 8u + 1u),和系统就绪表的大小一致
那么正好和OSEventGrp合作起来,用与系统就绪表的方法类似的标识方式作为等待事件的任务标识,具体的解析请查看系统就绪表的说明文章,只介绍一点,当某个任务等待某个已经生成的特定事件的时候,就会在该事件对应的ecb中的OSEventTbl中相应的位置1,并且这个元素对应的OSEventGrp的位置也会置一,当系统在查找等待该事件的任务的时候,就可以直接通过与任务就绪表相同的方式找到等待该事件的最高优先级的任务.如下
y = OSUnMapTbl[pevent->OSEventGrp];
x = OSUnMapTbl[pevent->OSEventTbl[y]];
prio = (INT8U)((y << 3u) + x);
这是os_eventtaskready函数的实现可以看到就是直接从表中得到的等待任务的优先级.
其次是OSEventPtr,在系统初始化的时候,他并不是消息或者消息队列的指针,而是操作系统借助OSEventPtr该元素将整个事件控制块数组都连接在了一起,类似于tcb的连接,形成了一个链表,我们叫做空事件控制块链表,如下
OS_MemClr((INT8U *)&OSEventTbl[0], sizeof(OSEventTbl));
for (ix = 0u; ix < (OS_MAX_EVENTS - 1u); ix++) {
ix_next = ix + 1u;
pevent1 = &OSEventTbl[ix];
pevent2 = &OSEventTbl[ix_next];
pevent1->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent1->OSEventPtr = pevent2;
#if OS_EVENT_NAME_EN > 0u
pevent1->OSEventName = (INT8U *)(void *)"?";
#endif
}
pevent1 = &OSEventTbl[ix];
pevent1->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent1->OSEventPtr = (OS_EVENT *)0;
#if OS_EVENT_NAME_EN > 0u
pevent1->OSEventName = (INT8U *)(void *)"?";
#endif
OSEventFreeList = &OSEventTbl[0];
#else
OSEventFreeList = &OSEventTbl[0];
OSEventFreeList->OSEventType = OS_EVENT_TYPE_UNUSED;
OSEventFreeList->OSEventPtr = (OS_EVENT *)0;
这段代码类似于tcb初始化链接的代码,引入了一个新的全局变量OSEventFreeList代表系统空闲事件控制块的指针,连接好了之后,使用时方法如下
pevent = OSEventFreeList;
if (OSEventFreeList != (OS_EVENT *)0) {
OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
}
创建新的事件的时候,将空事件控制链表的第一个元素拿来存放事件,链表头更新为原来的第二个元素,返回取下来的元素的ecb事件控制体的指针等待用户操作.
系统创建事件相关的函数有四个
OSMboxCreate 创建消息邮箱
OSMutexCreate 创建互斥性信号量
OSQCreate 创建qs消息队列
OSSemCreate 创建信号量
删除事件的函数也对应的有四个,
OSMboxDel 删除消息邮箱
OSMutexDel 删除互斥信号量
OSQDel 删除消息队列
OSSemDel 删除信号量
这个函数都有自己的实现,但是创建函数统一的都调用了一个函数,为
OS_EventWaitListInit(pevent);参数是可用的事件ecb指针
功能很简单,将事件结构体中的OSEventTbl和OSEventGrp清零
接下来应该讲述各个信号的操作了,但是为了更好地讲述,我们需要先来认识几个函数
OS_EventTaskWait
该函数是当一个任务请求了事件但是不能获得的时候将任务登记在时间的等待任务列表中,并把任务控制块的任务等待状态置为非就绪任务,其接受的参数是一个时间控制体的指针,代码如下
OSTCBCur->OSTCBEventPtr = pevent;
pevent->OSEventTbl[OSTCBCur->OSTCBY] |= OSTCBCur->OSTCBBitX;
pevent->OSEventGrp |= OSTCBCur->OSTCBBitY;
y = OSTCBCur->OSTCBY;
OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
第一句将当前任务的tcb中的事件指针指向传入参数的ecb
二三句相当于在ecb中标识了哪个优先级的任务在等待事件
最后几句是将系统就绪表中当前任务的任务状态设置为非ready状态,此时任务被挂起进入等待任务状态,该函数主要在事件请求中调用,OSXXXPEND()
当任务获取到事件的时候就应当从挂起状态进入到就绪状态,使用的函数为
OS_EventTaskRdy,参数为事件ecb的指针
该函数实现的功能为调用这个函数的任务在任务等待ecb中清零,然后将任务就绪表中对应位置写1,标识任务已经就绪,该函数主要在事件发送过程中调用,OSXXXPOST()函数调用.代码如下
y = OSUnMapTbl[pevent->OSEventGrp];
x = OSUnMapTbl[pevent->OSEventTbl[y]];
prio = (INT8U)((y << 3u) + x);
ptcb = OSTCBPrioTbl[prio];
ptcb->OSTCBDly = 0u;
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) {
OSRdyGrp |= ptcb->OSTCBBitY;
OSRdyTbl[y] |= ptcb->OSTCBBitX;
OS_EventTaskRemove(ptcb, pevent);
可以看到,获取ecb指针之后,从ecb等待任务表中获取最高优先级的等待任务的优先级,用优先级获取tcb,在任务未挂起的情况下,将系统就绪表中的就绪标志置一,从而标识系统就绪,
综合起来看,也就是说,任务请求消息的时候可能会让自身被挂起,而任务发送消息的时候,会让消息ecb中最高优先级的等待消息任务被激活.
OS_EventTaskRemove代码为
y = ptcb->OSTCBY;
pevent->OSEventTbl[y] &= (OS_PRIO)~ptcb->OSTCBBitX;
if (pevent->OSEventTbl[y] == 0u) {
pevent->OSEventGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
}
从任务ecb中将当前最高优先级的等待任务清除掉,因为该任务已经获得事件了
有时候任务是有等待时限的,当任务等待一个事件的时候,设置了等待时限,当等待时限到达,任务会继续执行,防止因为等待事件造成任务卡死,在OSXXXPend中有这样的代码
OSTCBCur->OSTCBStat |= OS_STAT_MBOX; /* Message not available, task will pend */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK;
OSTCBCur->OSTCBDly = timeout; /* Load timeout in TCB */
此处将OSTCBStat设置为OS_STAT_MBOX,并设置了一个等待时间timeout,那就必然和OSTimeTick有关系,查找OSTimeTick,发现这么一段
if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {
ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_PEND_ANY;
ptcb->OSTCBStatPend = OS_STAT_PEND_TO;
} else {
ptcb->OSTCBStatPend = OS_STAT_PEND_OK;
}
也就是说,timetick工作的时候在将OSTCBDly递减,当递减到0的时候,检测OSTCBStat,如果这是一个事件,那么ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_PEND_ANY;这句话能够让任务成为ready状态,同时OSTCBStatPend设置为OS_STAT_PEND_TO,是的任务能够在下面的调度中运行起来.
但是,这个请求并不是被删除了,而是依旧存在于任务ecb中,当系统post消息的时候,依旧会将消息给之前等待的任务.
事件的基本调度过程就是如上所说,下面将讲述如何具体的对各个信号量进行调用和调度.
标签:
原文地址:http://www.cnblogs.com/dengxiaojun/p/4322476.html