您的位置:首页 > 其它

ucos实时操作系统学习笔记——任务间通信(队列)

2016-02-21 13:15 260 查看
ucos操作系统中的queue机制同样使用了event机制来实现,其实和前面的sem,mutex实现类似,所不同的是对sem而言,任务想获得信号量,对mutex而言,任务想获得的是互斥锁。任务间通信的queue机制则是想获得在queue中的消息,通过队列先进先出的形式存放消息。其实queue中存放的是放消息的内存的地址,通过读取地址可以获得消息的内容。

queue机制是有一段循环使用的内存来存放增加的消息,然后从这段内存中读取消息的一个过程。有专门的操作系统queue结构(OS_Q)来描述这段内存。系统中OS_Q的个数也是有限的,在创建queue时,每一个OS_Q和event是一一对应的。OS_Q的结构体代码如下所示:

typedef struct os_q {                   /* QUEUE CONTROL BLOCK                                         */
struct os_q   *OSQPtr;              /* Link to next queue control block in list of free blocks     */
void         **OSQStart;            /* Pointer to start of queue data                              */
void         **OSQEnd;              /* Pointer to end   of queue data                              */
void         **OSQIn;               /* Pointer to where next message will be inserted  in   the Q  */
void         **OSQOut;              /* Pointer to where next message will be extracted from the Q  */
INT16U         OSQSize;             /* Size of queue (maximum number of entries)                   */
INT16U         OSQEntries;          /* Current number of entries in the queue                      */
} OS_Q;


结构体中第1个参数是一个os_q指针,指向下一个空闲的os_q;第2和第3个参数是描述queue内存地址开始和结束的二维指针;第4和第5个参数是描述queue内存地址中消息放入和取出的地址的指针,第6个参数是这个queue的大小;第7个参数则是表示有多少个消息实体在这个queue里;这些中OSQStart,OSQEnd,OSQIn和OSQOut都是二维指针,其中放的都是一些信息的地址,这些信息可以在不同的地方创建,地址可以不连续。

使用语言描述一下这个结构体具体实现的是一个什么东东,OSQStart和OSQEnd唯一确定一个OSQSize大小的queue;OSQIn在创建queue之初的初始化过程中指向的位置是OSQStart,每次当有新的信息加入到queue中时,OSQIn会向OSQEnd方向地址加一操作,同时OSQEntries会加一操作,当OSQIn的地址值等于OSQEnd时,表示queue已经满了,需要从心开始OSQIn等于OSQStart;OSQOut同样也是指向OSQStart的位置,当有信息从queue中取出时,OSQOut同样会向OSQEnd方向加一操作,同时OSQEntries会减一操作。

从queue的创建开始看其具体的实现机制是如何的,queue的创建时通过OSQCreate函数实现的,代码如下:

OS_EVENT  *OSQCreate (void **start, INT16U size)
{
OS_EVENT  *pevent;
OS_Q      *pq;

if (OSIntNesting > 0) {                      /* See if called from ISR ...                         */
return ((OS_EVENT *)0);                  /* ... can't CREATE from an ISR                       */
}
OS_ENTER_CRITICAL();
(1)==========================================================================================================
pevent = OSEventFreeList;                    /* Get next free event control block                  */
if (OSEventFreeList != (OS_EVENT *)0) {      /* See if pool of free ECB pool was empty             */
OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
}
OS_EXIT_CRITICAL();
(2)=========================================================================================================
if (pevent != (OS_EVENT *)0) {               /* See if we have an event control block              */
OS_ENTER_CRITICAL();
pq = OSQFreeList;                        /* Get a free queue control block                     */
if (pq != (OS_Q *)0) {                   /* Were we able to get a queue control block ?        */
OSQFreeList            = OSQFreeList->OSQPtr; /* Yes, Adjust free list pointer to next free*/
OS_EXIT_CRITICAL();
pq->OSQStart           = start;               /*      Initialize the queue                 */
pq->OSQEnd             = &start[size];
pq->OSQIn              = start;
pq->OSQOut             = start;
pq->OSQSize            = size;
pq->OSQEntries         = 0;
pevent->OSEventType    = OS_EVENT_TYPE_Q;
pevent->OSEventCnt     = 0;
pevent->OSEventPtr     = pq;
OS_EventWaitListInit(pevent);                 /*      Initalize the wait list              */
} else {
pevent->OSEventPtr = (void *)OSEventFreeList; /* No,  Return event control block on error  */
OSEventFreeList    = pevent;
OS_EXIT_CRITICAL();
pevent = (OS_EVENT *)0;
}
}
return (pevent);
(3)=======================================================================================================
}


可以从代码中看出,queue创建的开始部分和sem,mutex相同,就是不要在中断中创建,第二部分也相同就是从空闲event中取一个空闲的event结构体。不同的是第三部分,sem,mutex没有专门的描述机制相关的结构体,所以说不需要对其进行初始化,只要对event结构初始化就可以,但是对于queue来说除了初始化event之外,还需要初始化queue结构,上面的代码已经很清晰的做了相关操作,结合struct os_q的参数介绍,可以很好的理解,最后queue的地址放在event的OSEventPtr指针中。

void  *OSQPend (OS_EVENT *pevent, INT16U timeout, INT8U *perr)
{
void      *pmsg;
OS_Q      *pq;

if (pevent->OSEventType != OS_EVENT_TYPE_Q) {/* Validate event block type                          */
*perr = OS_ERR_EVENT_TYPE;
return ((void *)0);
}
if (OSIntNesting > 0) {                      /* See if called from ISR ...                         */
*perr = OS_ERR_PEND_ISR;                 /* ... can't PEND from an ISR                         */
return ((void *)0);
}
if (OSLockNesting > 0) {                     /* See if called with scheduler locked ...            */
*perr = OS_ERR_PEND_LOCKED;              /* ... can't PEND when locked                         */
return ((void *)0);
}
OS_ENTER_CRITICAL();
(1)====================================================================================================
pq = (OS_Q *)pevent->OSEventPtr;             /* Point at queue control block                       */
if (pq->OSQEntries > 0) {                    /* See if any messages in the queue                   */
pmsg = *pq->OSQOut++;                    /* Yes, extract oldest message from the queue         */
pq->OSQEntries--;                        /* Update the number of entries in the queue          */
if (pq->OSQOut == pq->OSQEnd) {          /* Wrap OUT pointer if we are at the end of the queue */
pq->OSQOut = pq->OSQStart;
}
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
return (pmsg);                           /* Return message received                            */
}
(2)=====================================================================================================
OSTCBCur->OSTCBStat     |= OS_STAT_Q;        /* Task will have to pend for a message to be posted  */
OSTCBCur->OSTCBStatPend  = OS_STAT_PEND_OK;
OSTCBCur->OSTCBDly       = timeout;          /* Load timeout into TCB                              */
OS_EventTaskWait(pevent);                    /* Suspend task until event or timeout occurs         */
OS_EXIT_CRITICAL();
OS_Sched();                                  /* Find next highest priority task ready to run       */
OS_ENTER_CRITICAL();
switch (OSTCBCur->OSTCBStatPend) {                /* See if we timed-out or aborted                */
case OS_STAT_PEND_OK:                         /* Extract message from TCB (Put there by QPost) */
pmsg =  OSTCBCur->OSTCBMsg;
*perr =  OS_ERR_NONE;
break;

case OS_STAT_PEND_ABORT:
pmsg = (void *)0;
*perr =  OS_ERR_PEND_ABORT;               /* Indicate that we aborted                      */
break;

case OS_STAT_PEND_TO:
default:
OS_EventTaskRemove(OSTCBCur, pevent);
pmsg = (void *)0;
*perr =  OS_ERR_TIMEOUT;                  /* Indicate that we didn't get event within TO   */
break;
}+
OSTCBCur->OSTCBStat          =  OS_STAT_RDY;      /* Set   task  status to ready                   */
OSTCBCur->OSTCBStatPend      =  OS_STAT_PEND_OK;  /* Clear pend  status                            */
OSTCBCur->OSTCBEventPtr      = (OS_EVENT  *)0;    /* Clear event pointers                          */
OSTCBCur->OSTCBMsg           = (void      *)0;    /* Clear  received message                       */
OS_EXIT_CRITICAL();
return (pmsg);                                    /* Return received message                       */
(3)========================================================================================================
}


queue队列的pend函数是OSQPend,从函数的代码看,其与sem主要的不同在第二部分,而与mutex的不同主要是在第二和第三部分(因为mutex有优先级的继承操作)。pend操作的第二部分主要的实现是从event的OSEventPtr中取出queue的描述结构体os_q,然后通过OSQEntries是否大于0判断在当前的queue中是否有信息,如果有则从OSQOut中取出信息,并将OSQOut加操作,让其指向下一个信息地址,同时将queue中代表信息个数的OSQEntries剪操作;如果OSQOut已经达到queue的最后一个位置即OSQOut==OSQEnd,则循环开始从OSQOut=OSQStart,重新获取信息;如果queue队列中没有信息,则直接将当前调度pend的任务挂起,并且重新进行任务调度,当任务重新获得运行的时候表示已经获得了event中的queue信息,则重新将当前任务加到运行等待列表中,这是第三部分的内容。

INT8U  OSQPost (OS_EVENT *pevent, void *pmsg)
{
OS_Q      *pq;

if (pevent->OSEventType != OS_EVENT_TYPE_Q) {      /* Validate event block type                    */
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
(1)====================================================================================================
if (pevent->OSEventGrp != 0) {                     /* See if any task pending on queue             */
/* Ready highest priority task waiting on event */
(void)OS_EventTaskRdy(pevent, pmsg, OS_STAT_Q, OS_STAT_PEND_OK);
OS_EXIT_CRITICAL();
OS_Sched();                                    /* Find highest priority task ready to run      */
return (OS_ERR_NONE);
}
(2)====================================================================================================
pq = (OS_Q *)pevent->OSEventPtr;                   /* Point to queue control block                 */
if (pq->OSQEntries >= pq->OSQSize) {               /* Make sure queue is not full                  */
OS_EXIT_CRITICAL();
return (OS_ERR_Q_FULL);
}
*pq->OSQIn++ = pmsg;                               /* Insert message into queue                    */
pq->OSQEntries++;                                  /* Update the nbr of entries in the queue       */
if (pq->OSQIn == pq->OSQEnd) {                     /* Wrap IN ptr if we are at end of queue        */
pq->OSQIn = pq->OSQStart;
}
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
(3)====================================================================================================
}


queue的post函数是OSQPost,该函数的入参是event指针,和要加入queue的信息的地址pmsg,和sem以及mutex类似,queue首先判断的是event中是否有在等待queue信息的任务,如果有的话将该pmsg直接交给等待任务,从代码中可以找到任务TCB的结构体中有一个专门存放pmsg的变量OSTCBMsg,这样的话,任务会通过访问OSTCBMsg直接获得pmsg的信息;如果在event中没有等待信息的任务存在则会进入到第三部分,就是从event中取出描述queue的os_q结构体,然后判断当前的queue中存在的信息是否达到了queue的上限OSQSize,如果已经达到上限,则会返回queue满了的错误,如果没有的话,会将pmsg放到OSQIn中,并且将queue中表示信息个数的OSQEntries加操作,如果OSQIn已经达到queue的上限,则会循环从Queue开始的地方存放pmsg (OSQIn == OSQStart),其实OSQIn的范围和OSQOut的范围是一样的,OSQIn访问的内存地址,OSQOut必然会访问到。 在queue中如果有msg的存在的话,在event任务等待列表中就不会有任务在等待msg,这是和其他的机制相通的地方。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: