原创 与信号量操作相关的六个函数

2010-7-7 14:28 2072 7 7 分类: MCU/ 嵌入式
 对于与硬件操作相关的任务,信号量操作是一个十分重要和关键的步骤。假设你的硬件上有两个串口:UART1和UART2。整个系统设置中共有3个任务与串口操作相关。任务运行到某个状态时,任务1在使用UART1通信,任务2在使用UART2通信,假设任务3的优先级高于前两者,这必然导致一个问题,任务3运行时必然剥夺任务1或任务2正在使用的串口资源,造成控制混乱。

    如果使用信号量,则完全可以避免上述的情况。UART1和UART2可以作为资源,即系统中有两个资源,那么与之相关的信号量初始值及为2。当这两个资源都被占用时,信号量的值变为0,现在哪怕任务3的优先级再高,也只能加入该信号量的等待列表,等待UART1或UART2释放资源,而不能直接剥夺资源使用权。


    uC/OS II中的信号量由两部分构成:一部分是16位的无符号整型信号量的计数值(计数范围0~65535);另一部分是等待该信号量的任务组成的等待任务。uC/OS II中队信号量的操作是通过6个函数进行的:OSSemCreate(),OSSemPend(),OSSemPost(),OSSemAccept()、OSSemQuery()和OSSemDel()。需要注意的是OSSemCreate(),OSSemPend()和OSSemPost()这三个函数是信号量所必须的,只要OS需要支持信号量,这三个函数不能被屏蔽,而OSSemAccept()、OSSemQuery()和OSSemDel()则可以通过配置常数#define OS_SEM_ACCEPT_EN、#define OS_SEM_QUERY_EN和#define OS_SEM_DEL_EN裁剪。


1. OS_EVENT  *OSSemCreate (INT16U cnt)
    创建信号量,创建工作必须在任务级代码中或者多任务启动之前完成。功能只要是先获取一个事件控制块ECB,写入一些参数。其中调用了OS_EeventWaitListInt()函数,对事件控制块的等待任务列表进行初始化。完成初始化工作后,返回一个该ECB类型的句柄(Handle)。源码如下:


OS_EVENT  *OSSemCreate (INT16U cnt)
{
#if OS_CRITICAL_METHOD == 3                                /* Allocate storage for CPU status register */
    OS_CPU_SR  cpu_sr;
#endif   
    OS_EVENT  *pevent;

    if (OSIntNesting > 0) {                                /* ISR中,不允许创建信号量                  */
        return ((OS_EVENT *)0);                         
    }
    OS_ENTER_CRITICAL();
    pevent = OSEventFreeList;                              /* 获得空链表指针                           */
    if (OSEventFreeList != (OS_EVENT *)0) {            /* 检查ECB是否被全部使用,如有空余,调整指针位置*/
        OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
    }
    OS_EXIT_CRITICAL();
    if (pevent != (OS_EVENT *)0) {                         /* ECB可用                                  */
        pevent->OSEventType = OS_EVENT_TYPE_SEM;           /* 类型设置为OS_EVENT_TYPE_SEM
        pevent->OSEventCnt  = cnt;                         /* 信号量设置为传递参数                     */
        pevent->OSEventPtr  = (void *)0;                   /* 指向空,被删除时重新链接空闲ECB链        */
        OS_EventWaitListInit(pevent);                      /* 初始化ECB的等待任务列表,全为0           */
    }
    return (pevent);                                       /* 返回创建ECB型的句柄(指针)              */
}


2. OS_EVENT  *OSSemDel (OS_EVENT *pevent, INT8U opt, INT8U *err)
    删除一个信号量,此段代码可以被屏蔽。需要注意的是删除信号量之前,必须首先删除操作该信号量的所有任务可以通过参数OS_DEL_ALWAYS强制删除该信号量,不过结果往往是灾难型的。使用该函数有一定的危险性,慎用!源码如下:


#if OS_SEM_DEL_EN > 0                                      /* 条件编译                                 */
OS_EVENT  *OSSemDel (OS_EVENT *pevent, INT8U opt, INT8U *err)
{
#if OS_CRITICAL_METHOD == 3                                /* Allocate storage for CPU status register */
    OS_CPU_SR  cpu_sr;
#endif   
    BOOLEAN    tasks_waiting;

    if (OSIntNesting > 0) {                                /* ISR中,不允许删除信号量                  */
        *err = OS_ERR_DEL_ISR;                            
        return (pevent);
    }
#if OS_ARG_CHK_EN > 0
    if (pevent == (OS_EVENT *)0) {                         /* 参数验证                                 */
        *err = OS_ERR_PEVENT_NULL;
        return (pevent);
    }
    if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {        /* 参数验证                                 */
        *err = OS_ERR_EVENT_TYPE;
        return (pevent);
    }
#endif
    OS_ENTER_CRITICAL();
    if (pevent->OSEventGrp != 0x00) {                      /* 检查是否有任务在等待该信号量             */
        tasks_waiting = TRUE;                              /* Yes                                      */
    } else {
        tasks_waiting = FALSE;                             /* No                                       */
    }
    switch (opt) {
        case OS_DEL_NO_PEND:                               /* 根据参数决定,无等待任务时,删除信号量   */
             if (tasks_waiting == FALSE) {
                 pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
                 pevent->OSEventPtr  = OSEventFreeList;    /* Return Event Control Block to free list  */
                 OSEventFreeList     = pevent;             /* Get next free event control block        */
                 OS_EXIT_CRITICAL();
                 *err = OS_NO_ERR;
                 return ((OS_EVENT *)0);                   /* Semaphore has been deleted               */
             } else {
                 OS_EXIT_CRITICAL();
                 *err = OS_ERR_TASK_WAITING;               /* 返回参数,说明有任务在等待此信号量       */
                 return (pevent);
             }

        case OS_DEL_ALWAYS:                                /* 根据参数决定,总是删除信号量             */
             while (pevent->OSEventGrp != 0x00) {          /*将所有等待态的任务加入就绪表,后果难以预料*/
                 OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM);
             }
             pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
             pevent->OSEventPtr  = OSEventFreeList;        /* Return Event Control Block to free list  */
             OSEventFreeList     = pevent;                 /* Get next free event control block        */
             OS_EXIT_CRITICAL();
             if (tasks_waiting == TRUE) {                  /* 当有任务等待该信号量时需要任务调度       */
                 OS_Sched();                               /* Find highest priority task ready to run  */
             }
             *err = OS_NO_ERR;
             return ((OS_EVENT *)0);                       /* Semaphore has been deleted               */

        default:
             OS_EXIT_CRITICAL();
             *err = OS_ERR_INVALID_OPT;
             return (pevent);
    }
}
#endif


3. void  OSSemPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)
    几乎所有的翻译都把这个函数称为等待一个信号量,这可能是受《嵌入式操作系统uC/OS II》的影响,不过这种翻译不利于理解这个函数的具体作用。我也是阅读了这个函数的源码后,才理解了这个函数的作用。等待一个信号量只是这个函数功能的一部分,当信号量的值为0时,这个函数确实是把当前任务从任务优先级列表中删除,加入等待该信号量的优先级列表。但是,当信号量不为0时,这个函数只是执行一些变量判断,然后将信号量减1,通过return语句跳出了函数,继续执行函数接下来的程序。源码如下:


void  OSSemPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)
{
#if OS_CRITICAL_METHOD == 3                           /* Allocate storage for CPU status register      */
    OS_CPU_SR  cpu_sr;
#endif   

    if (OSIntNesting > 0) {                           /* ISR中,不允许此操作                           */
        *err = OS_ERR_PEND_ISR;                     
        return;
    }
#if OS_ARG_CHK_EN > 0
    if (pevent == (OS_EVENT *)0) {                    /* 参数验证                                      */
        *err = OS_ERR_PEVENT_NULL;
        return;
    }
    if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {   /* 参数验证                                      */
        *err = OS_ERR_EVENT_TYPE;
        return;
    }
#endif
    OS_ENTER_CRITICAL();
    if (pevent->OSEventCnt > 0) {                     /* 如果信号量代表的资源至少有一个可以使用        */
        pevent->OSEventCnt--;                         /* 信号量减1,从函数返回,继续执行下面的程序     */
        OS_EXIT_CRITICAL();
        *err = OS_NO_ERR;
        return;
    }
                                                      /* 信号量为0,则必须等待该信号量被别的任务释放   */
    OSTCBCur->OSTCBStat |= OS_STAT_SEM;               /* 信号量类型                                    */
    OSTCBCur->OSTCBDly   = timeout;                   /* 等待超时时间                                  */
    OS_EventTaskWait(pevent);                         /* 任务挂起,详见《与ECB操作相关的四个函数》     */
    OS_EXIT_CRITICAL();
    OS_Sched();                                       /* 该优先级的任务不可用,寻找下一优先级任务      */
    OS_ENTER_CRITICAL();
    if (OSTCBCur->OSTCBStat & OS_STAT_SEM) {          /* 任务等待超时                                  */
        OS_EventTO(pevent);
        OS_EXIT_CRITICAL();
        *err = OS_TIMEOUT;                            /* 返回超时标志                                  */
        return;
    }
    OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0;
    OS_EXIT_CRITICAL();
    *err = OS_NO_ERR;
}


图1. OSSemPend流程图


点击看大图


4. INT8U  OSSemPost (OS_EVENT *pevent)
    《嵌入式操作系统uC/OS II》一书将此函数注释为发出一个信号量,原文注释为“This function signals a semaphore”,其实,我觉得翻译为释放信号量更为贴切。占用相关资源的任务执行完毕了,说明这个资源现在又有效了,可以被别的任务所使用,也就是说代表相关资源的信号量被释放了。源码入下:


INT8U  OSSemPost (OS_EVENT *pevent)
{
#if OS_CRITICAL_METHOD == 3                                /* Allocate storage for CPU status register */
    OS_CPU_SR  cpu_sr;                              
#endif   

#if OS_ARG_CHK_EN > 0
    if (pevent == (OS_EVENT *)0) {                         /* Validate 'pevent'                        */
        return (OS_ERR_PEVENT_NULL);
    }
    if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {        /* Validate event block type                */
        return (OS_ERR_EVENT_TYPE);
    }
#endif
    OS_ENTER_CRITICAL();
    if (pevent->OSEventGrp != 0x00) {                      /* 确定是否存在等待该信号量的任务           */
        OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM);   /* 如果有,将高优先级的任务放入就绪表       */
        OS_EXIT_CRITICAL();
        OS_Sched();                                        /* 任务调度                                 */
        return (OS_NO_ERR);
    }
    if (pevent->OSEventCnt < 65535) {                      /* 溢出判断                                 */
        pevent->OSEventCnt++;                              /* 资源被释放,信号量加1                    */
        OS_EXIT_CRITICAL();
        return (OS_NO_ERR);
    }
    OS_EXIT_CRITICAL();                               /* Semaphore value has reached its maximum       */
    return (OS_SEM_OVF);
}


图2. OSSemPost流程图


点击看大图


5. INT16U  OSSemAccept (OS_EVENT *pevent)
    这个函数的出现是由于ISR中不允许通过PEND功能等待,此函数只会检查count是否大于0,是的话信号量减1,否则直接返回。需要注意的是,如果信号量大于1,这个函数的最终返回值是信号量的值,而不是减1后的值,然后通过判断这个返回值,做适当的处理。典型的处理代码如下:


OS_EVENT *DispSem;
void Task (void *pdata)
{
    INT16U value;
    pdata = pdata;
   
    for (;;) {
        value = OSSemAccept(DispSem); /*查看设备是否就绪或事件是否发生 */
        if (value > 0) {
.                                     /* 就绪,执行处理代码            */
        }
    }
}

    源码如下:


#if OS_SEM_ACCEPT_EN > 0                              /* 条件编译                                      */
INT16U  OSSemAccept (OS_EVENT *pevent)
{
#if OS_CRITICAL_METHOD == 3                           /* Allocate storage for CPU status register      */
    OS_CPU_SR  cpu_sr;
#endif   
    INT16U     cnt;

#if OS_ARG_CHK_EN > 0
    if (pevent == (OS_EVENT *)0) {                    /* 参数验证                                      */
        return (0);
    }
    if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {   /* Validate event block type                     */
        return (0);
    }
#endif
    OS_ENTER_CRITICAL();
    cnt = pevent->OSEventCnt;
    if (cnt > 0) {                                    /* 检查资源是否可用                              */
        pevent->OSEventCnt--;                         /* 可用的话,资源值减1                           */
    }
    OS_EXIT_CRITICAL();
    return (cnt);                                     /* 返回实际资源值(减1之前的值)                 */
}
#endif

6. INT8U  OSSemQuery (OS_EVENT *pevent, OS_SEM_DATA *pdata)
   
OSSemQuery()函数用于获取某个信号量的信息。使用OSSemQuery()之前,应用程序需要先创立类型为OS_SEM_DATA 的数据结构,用来保存从信号量的事件控制块中取得的数据。使用OSSemQuery()可以得知是否有,以及有多少任务位于信号量的任务等待队列中(通过查询.OSEventTbl [ ]域),还可以获取信号量的标识号码。这个函数比较简单,唯一需要注意的就是OS_SEM_DATA数据结构的定义,在uCOS_II.H中。源码如下:


#if OS_SEM_QUERY_EN > 0
INT8U  OSSemQuery (OS_EVENT *pevent, OS_SEM_DATA *pdata)
{
#if OS_CRITICAL_METHOD == 3                                /* Allocate storage for CPU status register */
    OS_CPU_SR  cpu_sr;
#endif   
    INT8U     *psrc;
    INT8U     *pdest;

#if OS_ARG_CHK_EN > 0
    if (pevent == (OS_EVENT *)0) {                         /* Validate 'pevent'                        */
        return (OS_ERR_PEVENT_NULL);
    }
    if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {        /* Validate event block type                */
        return (OS_ERR_EVENT_TYPE);
    }
#endif
    OS_ENTER_CRITICAL();
    pdata->OSEventGrp = pevent->OSEventGrp;                /* 复制等待列表                             */
    psrc              = &pevent->OSEventTbl[0];
    pdest             = &pdata->OSEventTbl[0];
#if OS_EVENT_TBL_SIZE > 0
    *pdest++          = *psrc++;
#endif

#if OS_EVENT_TBL_SIZE > 1
    *pdest++          = *psrc++;
#endif

#if OS_EVENT_TBL_SIZE > 2
    *pdest++          = *psrc++;
#endif

#if OS_EVENT_TBL_SIZE > 3
    *pdest++          = *psrc++;
#endif

#if OS_EVENT_TBL_SIZE > 4
    *pdest++          = *psrc++;
#endif

#if OS_EVENT_TBL_SIZE > 5
    *pdest++          = *psrc++;
#endif

#if OS_EVENT_TBL_SIZE > 6
    *pdest++          = *psrc++;
#endif

#if OS_EVENT_TBL_SIZE > 7
    *pdest            = *psrc;
#endif
    pdata->OSCnt      = pevent->OSEventCnt;                /* Get semaphore count                      */
    OS_EXIT_CRITICAL();
    return (OS_NO_ERR);
}
#endif                                                     /* OS_SEM_QUERY_EN                          */


文章评论0条评论)

登录后参与讨论
我要评论
0
7
关闭 站长推荐上一条 /2 下一条