UCOS你问我答系列之信号量详解

UCOS中信号量一般有以下用途:
1是表示一个或多个事件的发生.这种情况下信号量的初值应该设置为0.
2是对于共享资源的互斥访问,此时信号量的初值设置为1.这种使用方式类似于互斥量的效果.

本文使用的UCOS版本:V2.91.

事件控制块

说到信号量, 首先需要讲解一下UCOS的事件控制块.UCOS的信号量,消息邮箱,消息队列.都是使用OSEVENT结构体来标识的. 结构体成员变量OSEventType标识不同的事件类型(信号量,消息队列等).
OS
EVENT的结构体定义如下:

typedef struct os_event {
    // 标识不同的事件类型,  OS_EVENT_TYPE_SEM  OS_EVENT_TYPE_MBOX等.
    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)          */
    /*
        OSEventGrp和OSEventTbl[OS_EVENT_TBL_SIZE]的组合构成优先级.可以判断出该事件是在哪个任务中挂起的.
        进而在post此事件时,将pend此事件的任务放入就绪表.
        一般通过判断 OSEventGrp != 0 确认是否有任务在pend此事件.
    */
    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                */

#if OS_EVENT_NAME_EN > 0u
    INT8U   *OSEventName;
#endif
} OS_EVENT;

信号量创建

本文对于针对共享资源的互斥访问角度讲解信号量,即类似于互斥量的使用.对于这种情况,信号量的初值设置为1,对于互斥资源的访问处请求获取信号量,互斥资源访问结束后释放信号量.首先讲解信号量的创建函数.具体的讲解在代码中以注释的形式体现.

OS_EVENT  *OSSemCreate (INT16U cnt)
{
    OS_EVENT  *pevent;
#if OS_CRITICAL_METHOD == 3u                               /* Allocate storage for CPU status register */
    OS_CPU_SR  cpu_sr = 0u;
#endif
    // 中断中不允许创建.
    if (OSIntNesting > 0u) {                               /* See if called from ISR ...               */
        return ((OS_EVENT *)0);                            /* ... can't CREATE from an ISR             */
    }
    OS_ENTER_CRITICAL();
    /*
        在事件控制块链表上找到一个可用的事件控制块,(实质为 OSEventTbl[OS_MAX_EVENTS]数组中的一个可用项.)
        设置对应的pevent结构体成员变量的属性值. 最后调用OS_EventWaitListInit清除 OSEventGrp OSEventTbl值.
        OSSemCreate执行完成后,我们得到了一个OS_EVENT*类型的信号量,此时信号量的计数值为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();
    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                */
#if OS_EVENT_NAME_EN > 0u
        pevent->OSEventName    = (INT8U *)(void *)"?";
#endif
        OS_EventWaitListInit(pevent);                      /* Initialize to 'nobody waiting' on sem.   */
    }
    return (pevent);
}

信号量请求

信号量请求主要是为了获取到刚才我们创建的那个信号量,当计数值为1时,表示信号量可用,计数值为0时,表示信号量暂时不可用. 信号量请求时可以指定超时时间timeout, 超时时间为0,表示永远等待直到信号量可用. 非0值时,表示等待至指定的超时时间后仍不可用,就返回.OSSemPend函数的详解在代码内以注释的形式体现.代码有删减.

void  OSSemPend (OS_EVENT  *pevent,
                 INT32U     timeout,
                 INT8U     *perr)
{
#if OS_CRITICAL_METHOD == 3u                          /* Allocate storage for CPU status register      */
    OS_CPU_SR  cpu_sr = 0u;
#endif
    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;
    }
    if (OSLockNesting > 0u) {                         /* See if called with scheduler locked ...       */
        *perr = OS_ERR_PEND_LOCKED;                   /* ... can't PEND when locked                    */
        return;
    }
    OS_ENTER_CRITICAL();
    //  如果信号量可用,信号量减一,返回成功.
    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;
    }
    /*
        设置当前任务的相关状态, OS_EventTaskWait函数将当前任务的优先级放入此信号量的结构体成员变量,
        并将此任务的优先级从就绪表中移除.然后触发一次任务调度.CPU转到其他任务运行.
        此任务即处于了挂起等待此信号量的状态.
    */
                                                      /* 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();任务被切换.
    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                          */
#if (OS_EVENT_MULTI_EN > 0u)
    OSTCBCur->OSTCBEventMultiPtr = (OS_EVENT **)0;
#endif
    OS_EXIT_CRITICAL();
}

信号量发送

信号量发送函数的主要作用是对OSEventCnt值进行累加,对于二值型信号量,一般在共享资源的访问结束后调用OSSemPost释放信号量.使信号量处于可用状态.

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

#if OS_ARG_CHK_EN > 0u
    if (pevent == (OS_EVENT *)0) {                    /* Validate 'pevent'                             */
        return (OS_ERR_PEVENT_NULL);
    }
#endif
    if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {   /* Validate event block type                     */
        return (OS_ERR_EVENT_TYPE);
    }
    OS_ENTER_CRITICAL();
    /*
        OSEventGrp != 0 时表示有其他任务在等待信号量. OS_EventTaskRdy函数的作用是通过pevent的结构体成员
        找到等待此信号量的那个任务(假定为任务A),并将任务A的优先级放入就绪表,且从等待事件表移除.然后
        触发一次任务调度.
    */
    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);
    }
    /*
        如果post信号的时候没有任务正在等待此信号, 就会走这个分支,OSEventCnt的值累加.
    */
    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);
}

问答环节

1.问:信号量如何确保UCOS的实时性的.
答: 某个任务pend信号量时,如果不能获取到信号量,会立即把自己从就绪表移除,并产生一次调度. 某个任务post信号量时,会立即找到pend信号量的那个任务,将其放入就绪表.如果此时post信号量的任务 是就绪表中优先级最高的任务,就会立即得以运行.

2.问:假定场景.任务A,优先级1, 任务B, 优先级2. 任务内都调用如下函数(伪代码),share_res()比较耗时,但内部无任何挂起任务的操作. 两个任务while循环中都调用OSTimeDly让出一段时间CPU.简述可能产生的调度情况.

int test(void)
{
    OSSemPend(sem, 0, &err);
    share_res();
    OSSemPost(sem);
}

答: 刚上电时,任务A优先级高,任务A先执行. 当任务A执行test函数时, OSSemPend检测到信号量可用,成功返回,并调用shareres()函数.假设在这个时候系统时钟中断到来,因为A的优先级高,不会触发任务调度,继续执行A.执行完毕后OSSemPost(sem)释放信号量.A任务调用OSTimeDly触发任务调度, B任务得以执行.B任务成功获取到信号量,执行完毕后释放信号量.一段时间后,可能存在某一时刻.B任务运行到shareres();时产生系统时钟中断, 在时钟中断中检测到A任务的延时时间结束.A任务优先级被放入就绪表,退出中断时,A任务得以运行.此时A任务抢占了B任务的运行. 当A任务运行到OSSemPend(sem, 0, &err);时,A任务因信号量不可用而挂起,产生调度,B任务得以接着运行.共享资源得以互斥访问. B任务运行到OSSemPost(sem);时,释放信号量,A任务得以就绪,调度到A任务运行. A任务运行到OSTimeDly时触发调度,让出CPU, B任务得以继续运行.

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值