从零开始学习UCOSII操作系统7--信号量

从零开始学习UCOSII操作系统7--信号量


参考博客:@ http://blog.csdn.net/gatiemehttps://blog.csdn.net/gatieme/article/details/21071379

前言:这里一定要分析清楚,因为信号量分析清楚后,后面的邮箱等其他的通信的东西都是大同小异的。

1、信号量的组成

(1)一部分是16位无符号的整型信号量计数值(0~65536)

(2)另一部分是由等待信号量的任务组成的等待任务表。

(3)信号量的6个基本函数:
OSSemAccept(), OSSemCreate(),OSSemDel()
OSSemPend(),OSSemPost()以及OSSemQuery()

其中要使用这些信号量的函数,必须在OS_CFG.h中将配置常数置为1,这样UCOSII才能支持信号量。

当OS_SEM_EN设为0的时候,所有的信号量都不能使用。
需要使用这些函数的时候,需要把这些宏定义置为1;

2、信号量使用的函数

(1)OSSemAccept(),OSSemPost()以及OSSemQuery()函数可以由任务或者中断服务子程序调用。

(2)OSSemDel()和OSSemPend()函数只能由任务程序调用。

3、信号量的两个关键问题

(1)任务怎么得到一个信号量的问题?

想要得到一个信号量的任务,必须执行等待的操作(pend)
如果信号量有效(非0),则信号量减1,任务继续运行。

如果此时信号量无效,则等待信号量的任务就被列入等待信号量的任务表中。此时可以设置超时时间,如果等待了多少时间后,仍然没有信号发生。
则此任务进入就绪态,准备运行,并且显示出错的代码---等待超时失败。

(2)任务对信号量的释放问题
任务执行发信号的POST操作来释放信号量,如果没有任务等待该信号量,那么这个信号量的值就是仅仅简单的+1.则此时信号量大于0有效。

如果有任务等待该信号量,那么就会有另一个任务进入就绪态,此时信号量就不+1了,因为刚刚的信号量就用出去了。

4、信号量的有效与无效问题

信号量有效:信号量的计算器非0,信号量有效表示任务对资源可用。

信号量无效:信号量的计算器为0,信号量无效表示任务对目前的资源不可用,需要等待其他的另一个任务或者中断服务子程序发出该信号量。

5信号量的值OSEventCnt大小表示什么?

二值信号量Mutext,表示任务可以独占共享资源

计数型的信号量Semaphore,用于某资源可同时为N个任务所用。

二值信号量与互斥型信号量的区别:

互斥型信号量必须是同一个任务申请,同一个任务释放,其他的任务释放无效。

二值信号量,一个任务申请成功后,可以由另一个任务释放,也可以由本任务释放。

6、信号量是如何实现任务之间的通信的?

(1)信号量的建立必须在任务级中建立。

(2)信号量类型为OS_EVENT,信号量值可以为1和0(二值信号量),0~65536计数型的信号量,不同的值代表不同的意义。

(3)对于互斥型信号量来说,就两个操作,请求和释放。

(4)一个任务请求信号量的时候,如果被其他的任务占用,则任务等待,同时导致任务切换。 如果没有被其他任务占用,则获得,继续执行。

(5)释放信号量的时候,如果其他高优先级任务正在请求并且等待该信号量的时候,则导致任务切换。

(6)OSSemAccept(信号量)起到查询信号量的作用,返回信号量的值。

(7)OSSemPend(Sem,timeout,&err);
timeout代表等待timeout个信号量后还没有得到信号量,恢复到就绪的状态,如果timeout=0,标志等待无限信号量。

7、信号量的三个关键函数的代码分析

(1)OSSemCreate()创建一个信号量(由任务或者启动代码操作)
创建工作必须在任务级代码中或者多任务启动之前完成。功能只要是先获取一个事件控制块ECB,写入一些参数,其中调用了OS_EventWaitListInit()函数,对事件控制块的等待任务列表进行初始化。

就是将事件控制块定义为信号量,然后把该填写的量填写。最后设置信号量的初始值

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



    if (OSIntNesting > 0) {                                /* 中断服务不能创建信号量  */
        return ((OS_EVENT *)0);                           
    }
    OS_ENTER_CRITICAL();
    pevent = OSEventFreeList;                              /* 得到下一个空闲的事件空闲控制块 */
    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;                      /* 设置信号量的初始值                      */
        pevent->OSEventPtr     = (void *)0;                /* Unlink from ECB free list                */
#if OS_EVENT_NAME_SIZE > 1
        pevent->OSEventName[0] = '?';                      /* Unknown name                             */
        pevent->OSEventName[1] = OS_ASCII_NUL;
#endif
        OS_EventWaitListInit(pevent);                      /* Initialize to 'nobody waiting' on sem.   */
    }
    return (pevent);
}

(2)删除一个信号量
1、删除一个信号量的函数OSSemDel()的源代码,当文件OS_CFG.h中的OS_SEM_DEL_EN为1的时候,该代码才被编译。

2、总之,在删除信号量之前,必须首先删除操作该信号量的所有的任务。

3、进入switch中,当OPT为OS_DEL_NO_PEND,并且没有任务在等待该信号量的时候,OSSemDel()函数将事件控制块ECB标志为未使用,并将其退回到空闲事件控制链表中,此操作允许该事件用于创建另一个信号量。

当OPT为OS_DEL_ALWAYS时候,所有等待该信号的任务都将进入就绪态,每个任务都得到了该信号量,当然这样可能会导致致命的后果,因为采用信号量就是为了防止对资源的多重访问。

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

#if OS_ARG_CHK_EN > 0
    if (err == (INT8U *)0) {                               /* Validate 'err'                           */
        return (pevent);
    }
    if (pevent == (OS_EVENT *)0) {                         /* Validate 'pevent'                        */
        *err = OS_ERR_PEVENT_NULL;
        return (pevent);
    }
#endif
    if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {        /* 是否是指向某一个用于信号量的一个函数             */
        *err = OS_ERR_EVENT_TYPE;
        return (pevent);
    }
    if (OSIntNesting > 0) {                                /* See if called from ISR ...               */
        *err = OS_ERR_DEL_ISR;                             /* ... can't DELETE from an ISR             */
        return (pevent);
    }
    OS_ENTER_CRITICAL();
    if (pevent->OSEventGrp != 0) {                         /* 看是否有任务在等待信号量  */
        tasks_waiting = OS_TRUE;                           /* 是的                                     */
    } else {
        tasks_waiting = OS_FALSE;                          /* No                                       */
    }
    switch (opt) {
        case OS_DEL_NO_PEND:                               /* Delete semaphore only if no task waiting */
             if (tasks_waiting == OS_FALSE) {
#if OS_EVENT_NAME_SIZE > 1
                 pevent->OSEventName[0] = '?';             /* Unknown name                             */
                 pevent->OSEventName[1] = OS_ASCII_NUL;
#endif
                 pevent->OSEventType    = OS_EVENT_TYPE_UNUSED;
                 pevent->OSEventPtr     = OSEventFreeList; /* Return Event Control Block to free list  */
                 pevent->OSEventCnt     = 0;
                 OSEventFreeList        = pevent;          /* Get next free event control block        */
                 OS_EXIT_CRITICAL();
                 *err                   = OS_NO_ERR;
                 pevent_return          = (OS_EVENT *)0;   /* Semaphore has been deleted               */
             } else {
                 OS_EXIT_CRITICAL();
                 *err                   = OS_ERR_TASK_WAITING;
                 pevent_return          = pevent;
             }
             break;

        case OS_DEL_ALWAYS:                                /* Always delete the semaphore              */
             while (pevent->OSEventGrp != 0) {             /* Ready ALL tasks waiting for semaphore    */
                 (void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM);
             }
#if OS_EVENT_NAME_SIZE > 1
             pevent->OSEventName[0] = '?';                 /* Unknown name                             */
             pevent->OSEventName[1] = OS_ASCII_NUL;
#endif
             pevent->OSEventType    = OS_EVENT_TYPE_UNUSED;
             pevent->OSEventPtr     = OSEventFreeList;     /* Return Event Control Block to free list  */
             pevent->OSEventCnt     = 0;
             OSEventFreeList        = pevent;              /* Get next free event control block        */
             OS_EXIT_CRITICAL();
             if (tasks_waiting == OS_TRUE) {               /* Reschedule only if task(s) were waiting  */
                 OS_Sched();                               /* Find highest priority task ready to run  */
             }
             *err                   = OS_NO_ERR;
             pevent_return          = (OS_EVENT *)0;       /* Semaphore has been deleted               */
             break;

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

(3)等待一个信号量OSSEMPend()

等待一个信号量函数的源代码分析:

PS:设置该函数中的超时时间,真正实现的函数是在OSTimeTICK()函数中逐个的递减。这个地方要注意下,对每个任务的任务控制块TCB中的OSTCBDly做递减操作。

最后的参数:perr就是指向一个错误的标志。

void  OSSemPend (OS_EVENT  *pevent,
                 INT32U     timeout,
                 INT8U     *perr)
{

#ifdef OS_SAFETY_CRITICAL
    if (perr == (INT8U *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
    }
#endif

#if OS_ARG_CHK_EN > 0u
    if (pevent == (OS_EVENT *)0) {                    /* 判断这个指针是否为空指针                            */
        *perr = OS_ERR_PEVENT_NULL;
        return;
    }
#endif
    if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {   /* 如果这个事件控制块的类型不是信号量的话                 */
        *perr = OS_ERR_EVENT_TYPE;
        return;
    }
    if (OSIntNesting > 0u) {                          /* 不在中断中操作此函数  */
        *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;
    }
                                                      /* Otherwise, must wait until event occurs       */
    OSTCBCur->OSTCBStat     |= OS_STAT_SEM;           /* 资源没有异常的话,那么就把资源定义为信号量 */
    OSTCBCur->OSTCBStatPend  = OS_STAT_PEND_OK;
    OSTCBCur->OSTCBDly       = timeout;               /* 设置信号量的超时时间 */
    OS_EventTaskWait(pevent);                         /* 使一个任务进入等待某事件发生状态 */
    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                          */
#if (OS_EVENT_MULTI_EN > 0u)
    OSTCBCur->OSTCBEventMultiPtr = (OS_EVENT **)0;
#endif
    OS_EXIT_CRITICAL();
}

(4)发出一个信号量:OSSEMPOST()

当任务发出一个等待的信号量之后,那么它必须接收到任务或者中断服务子程序给他发出相应的信号量,那么他才能回到就绪态,不然的话,就会不断的存在在事件控制等待列表中。

OS_EventTaskRdy()函数把优先级最高的任务从等待任务列表中去除,并使它进入就绪态,然后,调用OSSched()任务调用函数,因为已经获得信号量的任务,并且检车一下是否是最高的优先级的就绪态任务,如果是优先级最高的就绪态任务,这时就要进行任务切换,准备执行该就绪态任务。如果得到这个信号量的任务不是最高优先级的任务,退回到这个函数,并继续执行。

INT8U  OSSemPost (OS_EVENT *pevent)
{

#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();
    if (pevent->OSEventGrp != 0u) {                   /* 查看是否是存在信号量 */
                                                      /* 检查一下是否有任务在等待该信号量                */
        (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);
    }
    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);
}

(5)无等待的请求一个信号量OSSemAccept()

有些书籍或者博主称为查询信号量。

解释:当一个任务请求一个信号量的时候,如果该信号量暂停无效,也可以让该任务简单的返回,而不是进入休眠的状态,这种情况下面的操作是由OSSEMAccept()函数完成的。

也就是别人家称为很牛逼的词语:非阻塞函数

有我就响应,没有那么我就继续执行。

这个函数十分的简单:从该信号量的事件控制块中取出当前的计数值,并检查该信号量是否有效(计数值是否为非0值)

如果信号量有效的话,则信号量的计数值减1,
如果信号量无效的话,那么就直接返回。

INT16U  OSSemAccept (OS_EVENT *pevent)
{
    INT16U     cnt;

#if OS_ARG_CHK_EN > 0u
    if (pevent == (OS_EVENT *)0) {                    /* Validate 'pevent'                             */
        return (0u);
    }
#endif
    if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {   /* Validate event block type                     */
        return (0u);
    }
    OS_ENTER_CRITICAL();
    cnt = pevent->OSEventCnt;
    if (cnt > 0u) {                                   /* See if resource is available                  */
        pevent->OSEventCnt--;                         /* Yes, decrement semaphore and notify caller    */
    }
    OS_EXIT_CRITICAL();
    return (cnt);                                     /* Return semaphore count                        */
}

(6)查询一个信号量的当前的状态OSSemQuery()

这里面使用的,内存拷贝的方法,不断把源地址的内容,也就是源数据结构的成员,赋值到现在地址的成员中。

这段代码可能使用到了很多高级应用,但是我觉得是不好的,因为我们可以设置一个结构体,来对应事件控制块的成员。

然后一一赋值,可读性大大的提高了,做过开发的都知道,代码又丑又长,可读性没有的话,维护的成本会大大的提高。我们不是追求省那么一丁点的内存。而是后面的人维护起来比较爽。

INT8U  OSSemQuery (OS_EVENT     *pevent,
                   OS_SEM_DATA  *p_sem_data)
{
    INT8U       i;
    OS_PRIO    *psrc;
    OS_PRIO    *pdest;
#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);
    }
    if (p_sem_data == (OS_SEM_DATA *)0) {                  /* Validate 'p_sem_data'                    */
        return (OS_ERR_PDATA_NULL);
    }
#endif
    if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {        /* Validate event block type                */
        return (OS_ERR_EVENT_TYPE);
    }
    OS_ENTER_CRITICAL();
    p_sem_data->OSEventGrp = pevent->OSEventGrp;           /* Copy message mailbox wait list           */
    psrc                   = &pevent->OSEventTbl[0];
    pdest                  = &p_sem_data->OSEventTbl[0];
    for (i = 0u; i < OS_EVENT_TBL_SIZE; i++) {
        *pdest++ = *psrc++;
    }
    p_sem_data->OSCnt = pevent->OSEventCnt;                /* Get semaphore count                      */
    OS_EXIT_CRITICAL();
    return (OS_ERR_NONE);
}
  • 7
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值