首页 > 其他分享 >FreeRTOS 信号量

FreeRTOS 信号量

时间:2023-05-25 13:55:50浏览次数:28  
标签:优先级 FreeRTOS pxQueue else 信号量 互斥 任务

二值信号量

  二值信号量通常用于互斥访问或同步,二值信号量和互斥信号量非常类似,但是还是有一些细微的差别,互斥信号量拥有优先级继承机制,二值信号量没有优先级继承。

  和队列一样,信号量 API 函数允许设置一个阻塞时间,阻塞时间是当任务获取信号量的时候由于信号量无效从而导致任务进入阻塞态的最大时钟节拍数。如果多个任务同时阻塞在同一个信号量上的话那么优先级最高的哪个任务优先获得信号量,这样当信号量有效的时候高优先级的任务就会解除阻塞状态。 

  二值信号量其实就是一个只有一个队列项的队列,这个特殊的队列要么是满的,要么是空的。任务和中断使用这个特殊队列不用在乎队列中存的是什么消息,只需要知道这个队列是满的还是空的。可以利用这个机制来完成任务与中断之间的同步。

 

计数型信号量

1、事件计数

  在这个场合中,每次事件发生的时候就在事件处理函数中释放信号量(增加信号量的计数值),其他任务会获取信号量(信号量计数值减一,信号量值就是队列结构体成员变量uxMessagesWaiting)来处理事件。在这种场合中创建的计数型信号量初始计数值为 0。 

2、资源管理

  在这个场合中,信号量值代表当前资源的可用数量,比如停车场当前剩余的停车位数量。一个任务要想获得资源的使用权,首先必须获取信号量,信号量获取成功以后信号量值就会减一。当信号量值为 0 的时候说明没有资源了。当一个任务使用完资源以后一定要释放信号量,释放信号量以后信号量值会加一。在这个场合中创建的计数型信号量初始值应该是资源的数量,比如停车场一共有 100 个停车位,那么创建信号量的时候信号量值就应该初始化为 100。 

 

函数 xQueueCreateCountingSemaphore()

QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount,
                                                const UBaseType_t uxInitialCount )
{
    QueueHandle_t xHandle;
    configASSERT( uxMaxCount != 0 );
    configASSERT( uxInitialCount <= uxMaxCount );
    xHandle = xQueueGenericCreate( uxMaxCount,\ //创建一个队列,长度为uxMaxCount
                                    queueSEMAPHORE_QUEUE_ITEM_LENGTH, \
                                    queueQUEUE_TYPE_COUNTING_SEMAPHORE );
    if( xHandle != NULL )
    {
        ( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount; //计数型信号量的计数
        traceCREATE_COUNTING_SEMAPHORE();
    }
    else
    {
        traceCREATE_COUNTING_SEMAPHORE_FAILED();
    }
    return xHandle;
}

 

 

优先级反转

在使用二值信号量的时候会遇到很常见的一个问题——优先级翻转,优先级翻转在可剥夺内核中是非常常见的,在实时系统中不允许出现这种现象,这样会破坏任务的预期顺序,可能会导致严重的后果.

(1) 任务 H 和任务 M 处于挂起状态,等待某一事件的发生,任务 L 正在运行。
(2) 某一时刻任务 L 想要访问共享资源,在此之前它必须先获得对应该资源的信号量。
(3) 任务 L 获得信号量并开始使用该共享资源。
(4) 由于任务 H 优先级高,它等待的事件发生后便剥夺了任务 L 的 CPU 使用权。
(5) 任务 H 开始运行。
(6) 任务 H 运行过程中也要使用任务 L 正在使用着的资源,由于该资源的信号量还被任务L 占用着,任务 H 只能进入挂起状态,等待任务 L 释放该信号量。
(7) 任务 L 继续运行。
(8) 由于任务 M 的优先级高于任务 L,当任务 M 等待的事件发生后,任务 M 剥夺了任务L 的 CPU 使用权。
(9) 任务 M 处理该处理的事。
(10) 任务 M 执行完毕后,将 CPU 使用权归还给任务 L。
(11) 任务 L 继续运行。
(12) 最终任务 L 完成所有的工作并释放了信号量,到此为止,由于实时内核知道有个高优先级的任务在等待这个信号量,故内核做任务切换。
(13) 任务 H 得到该信号量并接着运行。

  在这种情况下,任务 H 的优先级实际上降到了任务 L 的优先级水平。因为任务 H 要一直等待直到任务 L 释放其占用的那个共享资源。由于任务 M 剥夺了任务 L 的 CPU 使用权,使得任务 H 的情况更加恶化,这样就相当于任务 M 的优先级高于任务 H,导致优先级翻转。 

 

 

 

互斥信号量

  互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中(任务与任务或中断与任务之间的同步)二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中。在互斥访问中互斥信号量相当于一个钥匙,当任务想要使用资源的时候就必须先获得这个钥匙,当使用完资源以后就必须归还这个钥匙,这样其他的任务就可以拿着这个钥匙去使用资源。

  当一个互斥信号量正在被一个低优先级的任务使用,而此时有个高优先级的任务也尝试获取这个互斥信号量的话就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级,这个过程就是优先级继承。优先级继承尽可能的降低了高优先级任务处于阻塞态的时间,并且将已经出现的“优先级翻转”的影响降到最低。 

  优先级继承并不能完全的消除优先级翻转,它只是尽可能的降低优先级翻转带来的影响。硬实时应用应该在设计之初就要避免优先级翻转的发生。互斥信号量不能用于中断服务函数中,原因如下:
  ● 互斥信号量有优先级继承的机制,所以只能用在任务中,不能用于中断服务函数。
  ● 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。 

 

  释 放 互 斥 信 号 量 的 时 候 和 二 值 信 号 量 、 计 数 型 信 号 量 一 样 , 都 是 用xSemaphoreGive()(实际上完成信号量释放的是函数 xQueueGenericSend())。不过由于互斥信号量涉及到优先级继承的问题,所以具体处理过程会有点区别。使用函数 xSemaphoreGive()释放信号 量 最 重 要 的 一 步 就 是 将 uxMessagesWaiting 加 一 , 而 这 一 步 就 是 通 过 函 数prvCopyDataToQueue() 来完成的,释放信号量的函数 xQueueGenericSend() 会调用prvCopyDataToQueue()。互斥信号量的优先级继承也是在函数 prvCopyDataToQueue()中完成的,此函数中有如下一段代码: 

static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue,
const void * pvItemToQueue,
const BaseType_t xPosition )
{
    BaseType_t xReturn = pdFALSE;
    UBaseType_t uxMessagesWaiting;
    uxMessagesWaiting = pxQueue->uxMessagesWaiting;
    if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )
    {
        #if ( configUSE_MUTEXES == 1 ) //互斥信号量
        {
            if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) //当前操作的是互斥信号量
            {
                xReturn = xTaskPriorityDisinherit( ( void * ) pxQueue->pxMutexHolder );
                //调用函数xTaskPriorityDisinherit()处理互斥信号量的优先级继承问题。
                pxQueue->pxMutexHolder = NULL; 
                //互斥信号量释放以后,互斥信号量就不属于任何任务了,所以 pxMutexHolder 要指向NULL。
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        #endif /* configUSE_MUTEXES */
    }
    /*********************************************************************/
    /*************************省略掉其他处理代码**************************/
    /*********************************************************************/
    pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1;
    return xReturn;
}

 

在 来 看 一 下 函 数 xTaskPriorityDisinherit() 是怎么具体的处理优先级继承的,xTaskPriorityDisinherit()代码如下: 

BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )
{
    TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
    BaseType_t xReturn = pdFALSE;
    if( pxMutexHolder != NULL )
    {
        //当一个任务获取到互斥信号量以后就会涉及到优先级继承的问题,正在释放互斥
        //信号量的任务肯定是当前正在运行的任务 pxCurrentTCB。
        configASSERT( pxTCB == pxCurrentTCB );
        configASSERT( pxTCB->uxMutexesHeld );
        ( pxTCB->uxMutexesHeld )--; //可能获取多个互斥信号量,没事放一次减一次
        //是否存在优先级继承?如果存在的话任务当前优先级肯定和任务基优先级不同。
        if( pxTCB->uxPriority != pxTCB->uxBasePriority ) 
            {
                //当前任务只获取到了一个互斥信号量
                if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 ) 
                {
                    if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) //当前任务恢复为原来的优先级以后再加入到就绪表中
                    { 
                        taskRESET_READY_PRIORITY( pxTCB->uxPriority ); 
                        //如果任务继承来的这个优先级对应的就绪表中没有其他任务的话就将取消这个优先级的就绪态
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                    //使用新的优先级将任务重新添加到就绪列表中
                    traceTASK_PRIORITY_DISINHERIT( pxTCB, pxTCB->uxBasePriority );
                    pxTCB->uxPriority = pxTCB->uxBasePriority; //重新设置任务的基优先级
                    /* Reset the event list item value. It cannot be in use for
                    any other purpose if this task is running, and it must be
                    running to give back the mutex. */
                    listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), \ //复位任务的事件列表项
                    ( TickType_t ) configMAX_PRIORITIES - \
                    ( TickType_t ) pxTCB->uxPriority );
                    prvAddTaskToReadyList( pxTCB ); //将优先级恢复后的任务重新添加到任务就绪表中
                    xReturn = pdTRUE; //返回pdTRUE,表示需要进行任务调度
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
    return xReturn;
}

 

获取互斥信号量xSemaphoreTake()(实际执行信号量获取的函数是 xQueueGenericReceive()),

BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t
xTicksToWait, const BaseType_t xJustPeeking )
{
    BaseType_t xEntryTimeSet = pdFALSE;
    TimeOut_t xTimeOut;
    int8_t *pcOriginalReadPosition;
    Queue_t * const pxQueue = ( Queue_t * ) xQueue;
    for( ;; )
    {
        taskENTER_CRITICAL();
        {
            const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
            //判断队列是否有消息
            if( uxMessagesWaiting > ( UBaseType_t ) 0 ) (1){
            pcOriginalReadPosition = pxQueue->u.pcReadFrom;
            prvCopyDataFromQueue( pxQueue, pvBuffer ); (2)
            if( xJustPeeking == pdFALSE ) (3)
            {
                traceQUEUE_RECEIVE( pxQueue );
                //移除消息
                pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1; (4)
                #if ( configUSE_MUTEXES == 1 ) //获取互斥信号量
                {
                    if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
                    {
                        pxQueue->pxMutexHolder = //成功,标记互斥信号量所有者
                         ( int8_t * ) pvTaskIncrementMutexHeldCount();
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                #endif /* configUSE_MUTEXES */
                //查看是否有任务因为入队而阻塞,如果有的话就需要解除阻塞态。
                if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == (7)
                pdFALSE )
                {
                    if( xTaskRemoveFromEventList( &
                    ( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
                    {
                        //如果解除阻塞的任务优先级比当前任务优先级高的话就需要
                        //进行一次任务切换
                        queueYIELD_IF_USING_PREEMPTION();
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else (8)
            {
            traceQUEUE_PEEK( pxQueue );
            //读取队列中的消息以后需要删除消息
            pxQueue->u.pcReadFrom = pcOriginalReadPosition;
            //如果有任务因为出队而阻塞的话就解除任务的阻塞态。
            if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == (9)
            pdFALSE )
            {
                if( xTaskRemoveFromEventList( &
                ( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
                {
                    //如果解除阻塞的任务优先级比当前任务优先级高的话就需要
                    //进行一次任务切换
                    queueYIELD_IF_USING_PREEMPTION();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
            }
            taskEXIT_CRITICAL();
            return pdPASS;
        }
        else //队列为空 (10)
        {
            if( xTicksToWait == ( TickType_t ) 0 )
            {
                //队列为空,如果阻塞时间为 0 的话就直接返回 errQUEUE_EMPTY
                taskEXIT_CRITICAL();
                traceQUEUE_RECEIVE_FAILED( pxQueue );
                return errQUEUE_EMPTY;
            }
            else if( xEntryTimeSet == pdFALSE )
            {
                //队列为空并且设置了阻塞时间,需要初始化时间状态结构体。
                vTaskSetTimeOutState( &xTimeOut );
                xEntryTimeSet = pdTRUE;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
            }
            }
            taskEXIT_CRITICAL();
            vTaskSuspendAll();
            prvLockQueue( pxQueue );
            //更新时间状态结构体,并且检查超时是否发生
            if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ) (11)
            {
                if( prvIsQueueEmpty( pxQueue ) != pdFALSE ) (12)
                {
                    traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
                    #if ( configUSE_MUTEXES == 1 )
                    {
                        if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) (13)
                        {
                            taskENTER_CRITICAL();
                        {
                        vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );
                        //此函数会判断当前任务的任务优先级是否比正在拥有互斥信号量的那个任务的任务优先级高,如果是的话就
                        //会把拥有互斥信号量的那个低优先级任务的优先级调整为与当前任务相同的优先级
                    }
                    taskEXIT_CRITICAL();
                }
                else
                {
                mtCOVERAGE_TEST_MARKER();
                }
            }
            #endif
            vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), (15)
            xTicksToWait );
            prvUnlockQueue( pxQueue );
            if( xTaskResumeAll() == pdFALSE )
            {
                portYIELD_WITHIN_API();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
            }
            else
            {
            //重试一次
            prvUnlockQueue( pxQueue );
            ( void ) xTaskResumeAll();
            }
            }
            else
            {
                prvUnlockQueue( pxQueue );
                ( void ) xTaskResumeAll();
                if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
                {
                    traceQUEUE_RECEIVE_FAILED( pxQueue );
                    return errQUEUE_EMPTY;
                }
            else
            {
            mtCOVERAGE_TEST_MARKER();
            }
        }
    }
}

 我们举个例子来简单的演示一下这个过程,假设现在有两个任务 HighTask 和 LowTask,HighTask 的任务优先级为 4,LowTask 的任务优先级为 2。这两个任务都会操同一个互斥信号量 Mutex,LowTask 先获取到互斥信号量 Mutex。此时任务 HighTask 也要获取互斥信号量 Mutex,任务 HighTask 调用xSemaphoreTake()尝试获取互斥信号量 Mutex,发现此互斥信号量正在被任务 LowTask 使用,并LowTask 的任务优先级为 2,比自己的任务优先级小,因为任务 HighTask 就会将 LowTask的任务优先级调整为与自己相同的优先级,即 4,然后任务 HighTask 进入阻塞态等待互斥信号量有效。 

 

 

标签:优先级,FreeRTOS,pxQueue,else,信号量,互斥,任务
From: https://www.cnblogs.com/wxk1213/p/17430572.html

相关文章

  • FreeRTOS 任务
    使用RTOS时,一个实时任务可以作为一个独立的任务,任何一个时间点只有一个任务运行,具体由RTOS调度器决定。RTOS调度器的职责是确保当一个任务开始执行的时候上下文环境与上一次推出的时候相同,每个任务都有堆栈,任务切换的时候将上下文保存在堆栈中。任务特性:1、简单2、没有使用限......
  • FreeRTOS应用基础(一)
      本系列主要作为自己第一次系统学习RTOS的记录,以正点原子的STM32F103战舰,keil环境编程为例。想要达到以下目标:  1:初步熟悉FreeRTOS的移植和使用,并迁移完成一个小型项目;  2:以FreeRTOS为入门,了解RTOS的本质,并提升阅读源码的能力;  本系列文章主要参考以下资料,本文仅作为......
  • iOS GCD 和信号量 实现 生产者和消费者模式
    GCD提供两种方式支持dispatch队列同步,即dispatch组和信号量。一、dispatch组(dispatchgroup)1.创建dispatch组dispatch_group_tgroup=dispatch_group_create();2.启动dispatch队列中的block关联到group中dispatch_group_async(group,queue,^{//。。。});3.......
  • 信号量和互斥锁详解 非MarkDown版
    [参考链接1](http://blog.chinaunix.net/uid-24612247-id-2305050.html)详细说明:```信号量强调的是线程(或进程)间的同步:“信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在sem_wait的时候......
  • 信号量和互斥锁详解 MarkDown版
    信号量和互斥锁详解参考链接1参考链接2参考链接3参考链接4详细说明:信号量强调的是线程(或进程)间的同步:“信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在sem_wait的时候,就阻塞在那里)。当信号量为单值信号量是,也......
  • FreeRTOS移植
    一、 二、1.在项目新建文件夹FreeRTOS,把FreeRTOSv202112.00\FreeRTOS\Source所有文件拷贝到新建的文件夹。 2.STM32F40x_FreeRTOS_Test\FreeRTOS\portable,protable中保留如下三个文件,其它删除掉 ......
  • 锁机制和信号量机制实现水果问题,同步机制
    使用Semaphore类实现packagecom.huo.HelperClass.demo;importsun.security.krb5.internal.TGSRep;importjava.util.concurrent.Semaphore;/***@version1.0*@Author作者名*@Date2022/9/1311:26*///使用Semaphore实现水果同步问题publicclassFruitDem......
  • Java Semaphore 信号量详解
    Semaphore基本使用场景Semaphore的基本使用场景是限制一定数量的线程能够去执行.举个简单的例子:一个单向隧道能同时容纳10个小汽车或5个卡车通过(1个卡车等效与2个小汽车),而隧道入口记录着当前已经在隧道内的汽车等效比重.比如1个小汽车和1个卡车,则隧道入口显示3.若隧道......
  • python高级技术(死锁、递归锁、信号量、Event时间、进程池、线程池、协程)
    一死锁和递归锁(了解)进程也有死锁与递归锁,使用方法类似所谓死锁:是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。当你知......
  • 《asyncio 系列》11. asyncio 的并发原语(锁、信号量、事件、条件)
    楔子使用多线程和多进程编写应用程序时,需要考虑非原子操作时的竞态条件,因为即使是并发增加整数这样简单的操作也可能导致微妙的、难以重现的bug。而asyncio是单线程的(除非与多线程和multiprocessing进行交互),这是否意味着我们就可以不必考虑竞争条件呢?事实证明,事情并非那么简......