FreeRTOS学习 信号量

信号量

FreeRTOS 学习仓库:https://gitee.com/killerp/free-rtos_-study

在深入理解了消息队列后,信号量也就很容易学习了。因为信号量就是使用消息队列实现的。

信号量是特殊的消息队列,其只部分利用了队列结构体,信号量没有队列存储区域,所以信号量不能用来传递任务间的数据。但可以利用消息队列的其他特点如:对共享数据的保护,阻塞等待机制等,实现任务之间的同步,对共享数据的互斥访问。

一、信号量的分类

不同类型的信号量有各自的用途。

二值信号量

二值信号量只有 0 和 1 两个状态,通常用于任务之间、任务和中断之间的同步。二值信号量初始化时为0,当任务or中断释放二值信号量,信号量值为1,接收任务解除阻塞进入就绪。通常是一方固定释放信号量,一方固定读取信号量,实现双方的同步。

互斥信号量

互斥信号量主要用于对共享数据的互斥访问,也就是保证任务对资源的独占性,在同一时刻,一个共享数据只能被一个任务访问。

所以互斥信号量在初始化时,需要初始化为1,表示资源的存在。

任务获取互斥信号信号成功后,就能放心地操作资源。其他任务获取互斥信号就会失败,而进入阻塞。通常情况下,多个任务访问一个共享资源,每个任务都需要先获取互斥信号量,在处理完数据后,释放互斥信号量。这与二值信号量不一样。

递归互斥信号量

递归互斥信号量是在互斥信号的基础上,允许拥有信号量的任务递归地获取/释放信号量。

计数信号量

计数信号量类似于互斥信号量,用于对资源的保护,通常该资源可被若干个任务同时拥有。

二、信号量的创建、释放、获取

信号量的实现依赖于队列,在FreeRTOS中,仅用一个semphr.h头文件,通过宏定义来实现信号量的创建,发送,接收。

2.1、创建信号量

创建二值信号量 实际上就是创建一个长度为1,大小为0的队列 ,此时信号量的值为0

#define xSemaphoreCreateBinary()    xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
2.2、优先级反转

首先了解简单的优先级反转:

A的优先级为1,B为10,C为5,A获取到信号量,正在操作资源。此时中断导致任务调度,切换到B执行,B因为请求信号量失败而进入阻塞。此时发生任务调度,C运行知道让出CPU,A继续运行释放信号量,最后B才能继续运行。

假设没有C任务,那么A、B的运行是正常的。A首先获取资源,把资源处理完成后再由B下一步处理,这种情况下,A、B执行的时间是可确定的,符合实时操作系统的要求。

但是第三者C的加入,使得任务A、B的执行时间不确定了。C有时会抢占A的CPU,使A、B的执行时间变得长;有时又不止一个C、可能有其他D、E、F、G来抢占A,所以A、B的执行时间变得不可确定,这对实时操作系统是灾难性的。

解决的办法是使用优先级继承:即把B的优先级暂时复制给A,这样C就无法插足A、B之间的感情了。需要注意的是,任务可能发生多次的优先级反转,所以优先级继承一定要继承最高优先级。

2.3、获取信号量

由于互斥信号量的优先级继承是发生在信号量获取的过程中,所以获取信号量的实现需要关注优先级继承逻辑。代码如下

BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue,
                                TickType_t xTicksToWait )
{
    BaseType_t xEntryTimeSet = pdFALSE;
    TimeOut_t xTimeOut;
    Queue_t * const pxQueue = xQueue;

    BaseType_t xInheritanceOccurred = pdFALSE;  //优先级继承是否发送的标志

    configASSERT( ( pxQueue ) );

    //uxItemSize为0时,队列才表示为信号量
    configASSERT( pxQueue->uxItemSize == 0 );

    //进入循环
    for( ; ; )
    {
        //进入临界区
        taskENTER_CRITICAL();
        {

            //uxMessagesWaiting 就是信号量的值
            const UBaseType_t uxSemaphoreCount = pxQueue->uxMessagesWaiting;


            //检查信号量是否有效
            if( uxSemaphoreCount > ( UBaseType_t ) 0 )
            {
                traceQUEUE_RECEIVE( pxQueue );

                //信号量有效、数值减一
                pxQueue->uxMessagesWaiting = uxSemaphoreCount - ( UBaseType_t ) 1;
                
                    {
                        //若是互斥信号量,还需要做优先级继承
                        if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
                        {
                            //记录当前信号量的拥有者
                            pxQueue->u.xSemaphore.xMutexHolder = pvTaskIncrementMutexHeldCount();
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                    }
                

                //检测是否有任务阻塞在信号量释放队列,若有则恢复任务
                if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
                {
                    if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
                    {
                        //恢复的任务优先级更高,发送抢占
                        queueYIELD_IF_USING_PREEMPTION();
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
                //成功获取 退出函数
                taskEXIT_CRITICAL();
                return pdPASS;
            }
            else    //信号量被使用完了
            {
                //不需要阻塞等待信号量
                if( xTicksToWait == ( TickType_t ) 0 )
                {
                                 
                        {
                            //若优先级反转已经发生,那么调用者必须有一个阻塞时间,故此时xInheritanceOccurred必须为false
                            configASSERT( xInheritanceOccurred == pdFALSE );
                        }
                    
                    //不需要等待直接退出
                    taskEXIT_CRITICAL();
                    traceQUEUE_RECEIVE_FAILED( pxQueue );
                    return errQUEUE_EMPTY;
                }
                else if( xEntryTimeSet == pdFALSE )
                {
                    //初始化超时结构体
                    vTaskInternalSetTimeOutState( &xTimeOut );
                    xEntryTimeSet = pdTRUE;
                }
                else
                {
                    /* Entry time was already set. */
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        }
        taskEXIT_CRITICAL();
        
        vTaskSuspendAll();
        prvLockQueue( pxQueue );    //锁定队列,因为需要操作链表

        //超时时间未到
        if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
        {

            //信号量为空
            if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
            {
                traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
                
                    {
                        //若是互斥信号量、在进入阻塞前、需要做一次优先级继承
                        if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
                        {
                            taskENTER_CRITICAL();
                            {
                                xInheritanceOccurred = xTaskPriorityInherit( pxQueue->u.xSemaphore.xMutexHolder );
                            }
                            taskEXIT_CRITICAL();
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                    }
                
                //将当前任务挂起到等待链表
                vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
                prvUnlockQueue( pxQueue );  //解锁队列

                //当前任务进入等待、恢复任务调度,切换下一个任务运行
                if( xTaskResumeAll() == pdFALSE )
                {
                    portYIELD_WITHIN_API();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                //超时时间未到且信号量有效了,重新进入循环获取信号量,这种情况可能是其他任务释放了信号量从而唤醒当前任务
                prvUnlockQueue( pxQueue );
                ( void ) xTaskResumeAll();
            }
        }
        else    //阻塞时间到了 此时任务已被内核移出等待队列
        {
            /* Timed out. */
            prvUnlockQueue( pxQueue );
            ( void ) xTaskResumeAll();

            //若信号量仍然为空,则退出阻塞
            if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
            {                
                    {

                        //若发生了优先级反转,需要恢复任务的优先级
                        if( xInheritanceOccurred != pdFALSE )
                        {
                            taskENTER_CRITICAL();
                            {
                                UBaseType_t uxHighestWaitingPriority;

                                //获取队列等待链表中的最高优先级、
                                uxHighestWaitingPriority = prvGetDisinheritPriorityAfterTimeout( pxQueue );
                                //恢复队列的优先级为该最高优先级or任务自身优先级
                                vTaskPriorityDisinheritAfterTimeout( pxQueue->u.xSemaphore.xMutexHolder, uxHighestWaitingPriority );
                            }
                            taskEXIT_CRITICAL();
                        }
                    }
                

                traceQUEUE_RECEIVE_FAILED( pxQueue );
                //返回获取失败
                return errQUEUE_EMPTY;
            }
            else    //阻塞时间到了,但同时信号量也有效,重新进入循环获取信号量
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    } /*lint -restore */
}
2.4、释放信号量

释放信号量就是向队列发送一个空消息、且不需要进入阻塞,队列满说明信号量有效,所以不需要阻塞。同时若是互斥信号量,则任务会恢复到原来的优先级。

#define xSemaphoreGive( xSemaphore )    xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )

在中断中释放信号量与任务中的区别是:进入临界区需要保存中断状态,以及在中断中不能进入阻塞,且队列锁定时不能操作队列的链表。

BaseType_t xQueueGiveFromISR( QueueHandle_t xQueue,
                              BaseType_t * const pxHigherPriorityTaskWoken )
{
    BaseType_t xReturn;
    UBaseType_t uxSavedInterruptStatus; //保存中断状态
    Queue_t * const pxQueue = xQueue;

    configASSERT( pxQueue );

    //用于信号量的释放,故为0
    configASSERT( pxQueue->uxItemSize == 0 );

    //通常一个互斥信号量不会在中断中释放、而是是任务中。因为互斥信号量是资源的锁,在中断中一般不会访问共享的资源。
    configASSERT( !( ( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) && ( pxQueue->u.xSemaphore.xMutexHolder != NULL ) ) );

    portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
    //进入临界区,保存中断状态
    uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
    {
        const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;

        //检测是否能继续释放信号量
        if( uxMessagesWaiting < pxQueue->uxLength )
        {
            const int8_t cTxLock = pxQueue->cTxLock;

            traceQUEUE_SEND_FROM_ISR( pxQueue );

            //信号量值+1
            pxQueue->uxMessagesWaiting = uxMessagesWaiting + ( UBaseType_t ) 1;

            //队列未锁,可以操作链表
            if( cTxLock == queueUNLOCKED )
            {
                    {
                        //将等待信号量的任务恢复就绪
                        if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
                        {
                            if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
                            {
                                //需要退出中断后进行任务调度
                                if( pxHigherPriorityTaskWoken != NULL )
                                {
                                    *pxHigherPriorityTaskWoken = pdTRUE;
                                }
                                else
                                {
                                    mtCOVERAGE_TEST_MARKER();
                                }
                            }
                            else
                            {
                                mtCOVERAGE_TEST_MARKER();
                            }
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                    }
            }
            else
            {
                configASSERT( cTxLock != queueINT8_MAX );
                //队列锁定 标记
                pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );
            }

            xReturn = pdPASS;
        }
        else    //队列满,无法释放信号量
        {
            traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );
            xReturn = errQUEUE_FULL;
        }
    }
    //退出临界区 恢复中断状态
    portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );

    return xReturn;
}
2.5、优先级继承相关函数

a、优先级继承

任务获取互斥信号量时,进入阻塞,需要优先级继承,信号量的拥有者继承当前任务的优先级(若当前任务优先级较高)。

BaseType_t xTaskPriorityInherit(TaskHandle_t const pxMutexHolder)
{
    TCB_t *const pxMutexHolderTCB = pxMutexHolder;  //获取锁的拥有者
    BaseType_t xReturn = pdFALSE;   //返回值表示是否发送继承

    if (pxMutexHolder != NULL)
    {

        //拥有锁的任务优先级低于当前(请求获取信号量)任务的优先级 需要继承优先级
        if (pxMutexHolderTCB->uxPriority < pxCurrentTCB->uxPriority)
        {
            //若拥有锁的任务 所在的事件链表是按优先级排序的,则需要修改事件节点的值
            if ((listGET_LIST_ITEM_VALUE(&(pxMutexHolderTCB->xEventListItem)) & taskEVENT_LIST_ITEM_VALUE_IN_USE) == 0UL)
            {
                //设置节点的值为新的优先级
                listSET_LIST_ITEM_VALUE(&(pxMutexHolderTCB->xEventListItem), (TickType_t)configMAX_PRIORITIES - (TickType_t)pxCurrentTCB->uxPriority); 
            }
            else
            {
                //否则什么也不做
                mtCOVERAGE_TEST_MARKER();
            }

            //若拥有锁的任务是在就绪链表中,则需要将其移动到新的优先级的就绪链表
            if (listIS_CONTAINED_WITHIN(&(pxReadyTasksLists[pxMutexHolderTCB->uxPriority]), &(pxMutexHolderTCB->xStateListItem)) != pdFALSE)
            {
                if (uxListRemove(&(pxMutexHolderTCB->xStateListItem)) == (UBaseType_t)0)
                {
                    portRESET_READY_PRIORITY(pxMutexHolderTCB->uxPriority, uxTopReadyPriority);
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }

                //继承当前任务优先级 加入就绪链表
                pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
                prvAddTaskToReadyList(pxMutexHolderTCB);
            }
            else
            {
                //拥有锁的任务不在就绪链表中,只需要继承当前任务优先级
                pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
            }

            traceTASK_PRIORITY_INHERIT(pxMutexHolderTCB, pxCurrentTCB->uxPriority);

            //优先级继承发送
            xReturn = pdTRUE;
        }
        else
        {
            //拥有锁的任务高于当前任务的优先级,但上一次优先级小于当前任务,说明该任务之前被提升了一次优先级
            if (pxMutexHolderTCB->uxBasePriority < pxCurrentTCB->uxPriority)
            {
                //标记优先级反转
                xReturn = pdTRUE;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    return xReturn;
}

b、重置优先级

当等待获取互斥信号的任务阻塞时间到达,需要退出等待时,调用以下函数,重新设置信号量拥有者的优先级。

void vTaskPriorityDisinheritAfterTimeout(TaskHandle_t const pxMutexHolder,
                                         UBaseType_t uxHighestPriorityWaitingTask)
{
    TCB_t *const pxTCB = pxMutexHolder;
    UBaseType_t uxPriorityUsedOnEntry, uxPriorityToUse; 
    const UBaseType_t uxOnlyOneMutexHeld = (UBaseType_t)1;

    if (pxMutexHolder != NULL)
    {
        configASSERT(pxTCB->uxMutexesHeld); //必须拥有锁

        //uxPriorityToUse是新的优先级。新的优先级应该是等待锁的任务中优先级最高的那个,这样才能保证最高优先级的任务能在锁释放后第一时间得到运行
        if (pxTCB->uxBasePriority < uxHighestPriorityWaitingTask)
        {
            uxPriorityToUse = uxHighestPriorityWaitingTask;
        }
        else
        {
            uxPriorityToUse = pxTCB->uxBasePriority;
        }

        /* Does the priority need to change? */
        //优先级需要修改
        if (pxTCB->uxPriority != uxPriorityToUse)
        {

            if (pxTCB->uxMutexesHeld == uxOnlyOneMutexHeld)
            {

                configASSERT(pxTCB != pxCurrentTCB);


                traceTASK_PRIORITY_DISINHERIT(pxTCB, uxPriorityToUse);
                //保存旧优先级
                uxPriorityUsedOnEntry = pxTCB->uxPriority;
                //设置新的优先级
                pxTCB->uxPriority = uxPriorityToUse;    


                //设置事件节点值
                if ((listGET_LIST_ITEM_VALUE(&(pxTCB->xEventListItem)) & taskEVENT_LIST_ITEM_VALUE_IN_USE) == 0UL)
                {
                    listSET_LIST_ITEM_VALUE(&(pxTCB->xEventListItem), (TickType_t)configMAX_PRIORITIES - (TickType_t)uxPriorityToUse);
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }

                //若锁的拥有者处于就绪链表,由于改变了优先级,所以需要修改其优先级链表
                if (listIS_CONTAINED_WITHIN(&(pxReadyTasksLists[uxPriorityUsedOnEntry]), &(pxTCB->xStateListItem)) != pdFALSE)
                {
                    if (uxListRemove(&(pxTCB->xStateListItem)) == (UBaseType_t)0)
                    {
                        portRESET_READY_PRIORITY(pxTCB->uxPriority, uxTopReadyPriority);
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                    //插入新的就绪链表
                    prvAddTaskToReadyList(pxTCB);
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

c、恢复优先级

任务释放互斥信号,自己将恢复到原始的任务优先级。

BaseType_t xTaskPriorityDisinherit(TaskHandle_t const pxMutexHolder)
{
    TCB_t *const pxTCB = pxMutexHolder;
    BaseType_t xReturn = pdFALSE;   //返回真,需要任务调度

    if (pxMutexHolder != NULL)
    {
		//检查输入参数
        configASSERT(pxTCB == pxCurrentTCB);
        configASSERT(pxTCB->uxMutexesHeld); //必须拥有锁
        (pxTCB->uxMutexesHeld)--;   //拥有互斥信号的数量-1


        //优先级不同则出现了优先级反转
        if (pxTCB->uxPriority != pxTCB->uxBasePriority)
        {
            //当不持有互斥信号时才能恢复任务优先级
            if (pxTCB->uxMutexesHeld == (UBaseType_t)0)
            {

                //移出就绪链表
                if (uxListRemove(&(pxTCB->xStateListItem)) == (UBaseType_t)0)
                {
                    portRESET_READY_PRIORITY(pxTCB->uxPriority, uxTopReadyPriority);
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }

                traceTASK_PRIORITY_DISINHERIT(pxTCB, pxTCB->uxBasePriority);
                //恢复优先级
                pxTCB->uxPriority = pxTCB->uxBasePriority;

                //设置事件节点的值
                listSET_LIST_ITEM_VALUE(&(pxTCB->xEventListItem), (TickType_t)configMAX_PRIORITIES - (TickType_t)pxTCB->uxPriority); 
                //添加到新的就绪表
                prvAddTaskToReadyList(pxTCB);

                /* Return true to indicate that a context switch is required.
                     * This is only actually required in the corner case whereby
                     * multiple mutexes were held and the mutexes were given back
                     * in an order different to that in which they were taken.
                     * If a context switch did not occur when the first mutex was
                     * returned, even if a task was waiting on it, then a context
                     * switch should occur when the last mutex is returned whether
                     * a task is waiting on it or not. */
                //todo 返回真使任务调度
                xReturn = pdTRUE;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    return xReturn;
}

你可能感兴趣的:(FreeRTOS,FreeRTOS,RTOS,信号量,源码,互斥访问)