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

时间:2024-04-16 15:43:14

      ucos实时操作系统的任务间通信有好多种,本人主要学习了sem, mutex, queue, messagebox这四种。系统内核代码中,这几种任务间通信机制的实现机制相似,接下来记录一下本人对核心代码的学习心得,供以后回来看看,不过比较遗憾的是没有仔细学习扩展代码的功能实现部分。ucos操作系统的内核代码实现相对简单,但是对理解其他操作系统内核相同功能有帮助。

      ucos的任务间通信机制主要是基于event实现的,其实理解这个event不用翻译成中文事件,就叫event感觉还更容易接收。下面是操作系统event的的数据结构:

typedef struct os_event {
    INT8U    OSEventType;                    /* Type of event control block (see OS_EVENT_TYPE_xxxx)    */
    void    *OSEventPtr;                     /* Pointer to message or queue structure                   */
    INT16U   OSEventCnt;                     /* Semaphore Count (not used if other EVENT type)          */
    OS_PRIO  OSEventGrp;                     /* Group corresponding to tasks waiting for event to occur */
    OS_PRIO  OSEventTbl[OS_EVENT_TBL_SIZE];  /* List of tasks waiting for event to occur                */
} OS_EVENT;

      上面的event是实现sem, mutex, queue, messagebox等必不可少的结构,其实该结构相对简单,主要包括OSEventType用于记录当前event是前面四种机制中的哪一种;OSEventPtr是一个指针用于指向messagebox和queue要传递的内容的地址;OSEventCnt对于sem来说,是一个计数值,而对于mutex来说则是记录任务优先级的一个变量;OSEventGrp和OSEventTbl和前面讲过的OSRdyGrp和OSRdyTbl的类似,主要记录有哪些任务在等待当前event的。对于上面四种机制的实现,首先需要创建一个event才可以实现各自的功能,比如说OSSemCreate,OSMutexCreate等。

       ucos的sem主要的功能是对资源使用的一种限制,当信号量的值设置n就代表允许n个任务可以对当前资源进行使用,如果资源被n个任务占用,第n+1个任务要使用该资源时,需要等待前面n个任务中有1个或者多个释放对资源的使用权。sem的具体功能就是OSSemCreate,OSSemPend,OSSemPost等这三个函数实现的,下面将具体分析一下这三个函数。

对sem来说,首先需要创建一个event结构,然后设置这个sem event允许几个任务可以同时使用,具体函数OSSemCreate首先如下:

OS_EVENT  *OSSemCreate (INT16U cnt)
{
    OS_EVENT  *pevent;

    if (OSIntNesting > 0u) {                               /* See if called from ISR ...               */
        return ((OS_EVENT *)0);                            /* ... can\'t CREATE from an ISR             */
    }
    OS_ENTER_CRITICAL();
    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();
    if (pevent != (OS_EVENT *)0) {                         /* Get an event control block               */
        pevent->OSEventType    = OS_EVENT_TYPE_SEM;
        pevent->OSEventCnt     = cnt;                      /* Set semaphore value                      */
        pevent->OSEventPtr     = (void *)0;                /* Unlink from ECB free list                */
        OS_EventWaitListInit(pevent);                      /* Initialize to \'nobody waiting\' on sem.   */
    }
    return (pevent);
}

      上面提到的四种任务间通信机制不允许在中断中创建,所以OSSemCreate首先判断当前创建过程是不是在中断中,也就是判断OSIntNesting是否大于0,有全局变量OSEventFreeList记录当前操作系统通还有多少个free的event在列表中,OSSemCreate从free list中取一个event机构,然后OSEventFreeList指向下一个未被使用的event,然后设置OSEventType和OSEventFreeListCnt,cnt是有create函数带入的参数,有使用该函数的程序员自己设定,sem不使用OSEventPtr变量,因为是指针,所以设置为0,之后初始化event的event group和 event table,表示当前没有任务等待创建的sem,返回event结构的地址,给之后讲的pend和post使用。

      其次,就要讲到sem机制post和pend的两个操作了,这两个操作是成对使用的,简单点讲pend主要功能是检查create的event中的OSEventCnt是否大于0,如果大于0,说明需要保护的资源还允许任务使用,这时候只需要将OSEventCnt减1操作,表示又有一个任务占用了资源的使用权;如果OSEventCnt不大于0,则说明任务可以使用的资源的权限已经达到上限,这时候就要把要使用的该资源的任务挂起(1),让其处于等待状态,并把任务放到event的event group和event table中,处于等待状态的任务只能等其他任务释放对资源的使用权之后才可以继续运行。post的主要功能是要释放被占用的资源的使用权,其操作首先会检查当前event的group和table中有没有在等待当前event的任务存在,如果有使用权直接转给等待的任务(1),不需要对OSEventCnt加操作,如果没有等待当前event的任务,则只需要将OSEventCnt加1操作,说明资源的可使用权又大了一个。接下来我们看一下具体pend和post的代码具体流程。

void  OSSemPend (OS_EVENT  *pevent,
                 INT32U     timeout,
                 INT8U     *perr)
{
    if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {   /* Validate event block type                     */
        *perr = OS_ERR_EVENT_TYPE;
        return;
    }
    if (OSIntNesting > 0u) {                          /* See if called from ISR ...                    */
        *perr = OS_ERR_PEND_ISR;                      /* ... can\'t PEND from an ISR                    */
        return;
    }
    OS_ENTER_CRITICAL();
(1)===================================================================================================
if (pevent->OSEventCnt > 0u) { /* If sem. is positive, resource available ... */ pevent->OSEventCnt--; /* ... decrement semaphore only if positive. */ OS_EXIT_CRITICAL(); *perr = OS_ERR_NONE; return; } (2)===================================================================================================
/* Otherwise, must wait until event occurs */ OSTCBCur->OSTCBStat |= OS_STAT_SEM; /* Resource not available, pend on semaphore */ OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK; OSTCBCur->OSTCBDly = timeout; /* Store pend timeout in TCB */ OS_EventTaskWait(pevent); /* Suspend task until event or timeout occurs */ OS_EXIT_CRITICAL(); OS_Sched(); /* Find next highest priority task ready */ OS_ENTER_CRITICAL(); switch (OSTCBCur->OSTCBStatPend) { /* See if we timed-out or aborted */ case OS_STAT_PEND_OK: *perr = OS_ERR_NONE; break; case OS_STAT_PEND_ABORT: *perr = OS_ERR_PEND_ABORT; /* Indicate that we aborted */ break; case OS_STAT_PEND_TO: default: OS_EventTaskRemove(OSTCBCur, pevent); *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 */ OS_EXIT_CRITICAL();
(3)===================================================================================================== }

      OSSemPend函数首先判断当前的任务操作是否是一个sem event;然后判断是否中断中,如果是则返回错误;之后检查该sem event的资源限号量是否大于0,如果是则进行减操作表示信号量被一个任务占用,如果不大于0,说明对改event来说,已经没有信号量供使用需要的操作是挂起当前任务,并调用任务切换函数,在上面的代码中在OS_Sched()上面的四步操作是挂起任务操作,直到有可以使用的信号量再次执行任务切换时,会切换到当前任务继续从OS_Sched开始执行,可以看出下面的操作是从event的等待group和table中删除该任务,并且改变任务的状态为OS_STAT_RDY。当然任务的还有statpend状态,在此不做详细介绍。

INT8U  OSSemPost (OS_EVENT *pevent)
{
    if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {   /* Validate event block type                     */
        return (OS_ERR_EVENT_TYPE);
    }
    OS_ENTER_CRITICAL();
(1)=====================================================================================================
if (pevent->OSEventGrp != 0u) { /* See if any task waiting for semaphore */ /* Ready HPT waiting on event */ (void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_OK); OS_EXIT_CRITICAL(); OS_Sched(); /* Find HPT ready to run */ return (OS_ERR_NONE); }
(2)====================================================================================================
if (pevent->OSEventCnt < 65535u) { /* Make sure semaphore will not overflow */ pevent->OSEventCnt++; /* Increment semaphore count to register event */ OS_EXIT_CRITICAL(); return (OS_ERR_NONE); } OS_EXIT_CRITICAL(); /* Semaphore value has reached its maximum */ return (OS_ERR_SEM_OVF);
(3)=================================================================================================== }

      OSSemPost函数实现相对简单,在判断完当前操作是否是sem event之后,就判断当前的event等待group中是否有等待该event的任务,如果有就设置等待该event的任务拥有该信号量的使用权限设置改任务处于RDY状态然后执行任务调度,让等待的任务得以执行,在此有一个巧妙地设计就是对于信号量的计量值OSEventCnt不做操作,因为如果event等待group中有等待任务的话,意味着在此释放信号量,等待任务就获得了信号量。如果在等待group中没有等待任务的话,就会给OSEventCnt做加操作,表示有一个任务释放了该信号量。在此说明的是该函数主要的操作在OS_EventTaskRdy中,其主要功能就是把event的等待group和table中的任务解析出来放到任务Rdy列表中,并把event中的等待任务从列表中删除。

      如果信号量的值设置为1的话,就是一个特殊情况,表示只有1个任务拥有信号量,有点互斥的意味,但是与互斥锁不同的是,互斥锁为了防止低优先级占有资源,却因为优先级的低不被执行,而高优先级得不到被低优先级占有的资源而不能执行,使用了优先级继承机制。之后会介绍ucos互斥锁的内核实现。