目录

临界区(critical sections)

互斥量(Mutex)

优先级继承性

code示例

递归互斥量 

code示例

二值信号量(Binary Semaphore)

 同步功能

code示例

计数信号量(Counting Semaphore)

相关接口

参考:


          FreeRTOS中常见的用于同步/互斥访问的锁机制:

临界区(critical sections)

        该方法适用于极短code段的保护,优先级高,响应快,但要注意保持时间尽可能短,否则会影响实时性(调用时,会关闭中断,因而会影响系统响应时间)

        调用taskENTER_CRITICAL() 会全局禁用中断(禁止上下文切换),保持在运行状态,直到退出临界区。适用于任务较短的临界区(task执行时间较大,会对中断时间产生不利影响)

void taskENTER_CRITICAL( void );
void taskEXIT_CRITICAL( void );

        可用于中断服务程序(ISR)的 taskENTER_CRITICAL() 版本,但需要注意保存进入临界区时的中断掩码状态。

UBaseType_t uxSavedInterruptStatus;
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
/* 临界区 */
taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );

互斥量(Mutex)

        该方式适用于互斥访问,保护需要较长时间访问的资源。但使用时需要确保configSUPPORT_DYNAMIC_ALLOCATION 和 configUSE_MUTEXES 在FreeRTOSConfig.h 中设置为 1。

优先级继承性

        互斥锁具有优先级继承机制,如果另一个更高优先级任务B尝试获取与相对低的任务A相同的互斥锁,则将暂时提高任务A的优先级(与任务B优先级相同)。该机制可以将优先级翻转危害降到最小。

  • 优先级翻转:由于低优先级任务正在占用临界资源,高优先级任务需要等低优先级任务使用完该资源后释放资源(高优先级无法运行,而低优先级任务可以运行的现象,称为优先级翻转)
    • 发生优先级翻转对操作系统是致命的危害,会导致系统高优先级任务阻塞时间过长。
      • 如:优先级为H > M > L的任务,如果H在等L的临界资源释放(优先级翻转)时M被唤醒,由于M会抢占L的CPU,导致比M更高优先级的H等待更长时间;
  • 互斥锁不能用在ISR中,其特有的优先级继承机制只对任务起作用,在中断的上下文环境中毫无意义。(如某个task正在持互斥锁,ISR中即使加了该锁,也并不会因锁被持有而等待锁释放,所以,互斥锁用在ISR中毫无意义);

code示例

SemaphoreHandle_t xSemaphore;

/* 创建 mutex type semaphore */
xSemaphore = xSemaphoreCreateMutex();

/* 正常task 中调用示例 */
if( xSemaphore != NULL )
{
        /* See if we can obtain the semaphore. If the semaphore is not
           available wait 10 ticks to see if it becomes free. */
        /* 超时等待 */
        if( xSemaphoreTake( xSemaphore, ( TickType_t ) 10 ) == pdTRUE )
        {
            /* We were able to obtain the semaphore and can now access the
               shared resource. */

            /* ... */

            /* We have finished accessing the shared resource. Release the
               semaphore. */
            xSemaphoreGive( xSemaphore );
        }
        else
        {
            /* We could not obtain the semaphore and can therefore not access
               the shared resource safely. */
        }
}


/* 删除互斥量 */
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

递归互斥量 

        递归互斥锁具有递归访问特性,持有该互斥量的任务能够再次获得这个锁而不被挂起,这个特性与一般的信号量有很大的不同(在信号量中,若已经持有该信号量再次获取信号量时,会发生主动挂起任务最终形成死锁)

code示例

/* 递归互斥量与普通互斥量使用方法类似 */
/* 创建mutex */
xMutex = xSemaphoreCreateRecursiveMutex();

if( xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 ) == pdTRUE )
{
    /* 递归互斥量可以被同一个task持有多次,但注意释放次数要与take次数相同 */
    xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );


    xSemaphoreGiveRecursive( xMutex );
    xSemaphoreGiveRecursive( xMutex );
}


/* 删除互斥量 */
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

二值信号量(Binary Semaphore)

        二值信号量是实现任务间通信的机制,可以实现任务间同步或临界资源的互斥访问。

  • 与互斥量相比,二值信号量没有优先级继承机制,因而信号量更偏向于同步功能(任务与任务间的同步或任务与中断的同步)
  • 二值信号量是基于队列机制创建的信号量,可将其看做一个消息队列,该队列只能为空或满, 即二值信号量只有0/1两种状态(因此,二值信号量没有累积性,若Task A 连续多次释放(最终信号量状态为1),Task B take时只会认为有一次释放,会错过多余的give);
    • 因此在使用二值信号量做同步时,尽可能避免上述情况,若无法避免,可使用计数信号量或 task notifications
    • 由于二值信号量take时是以队列空或满来判断是否阻塞,因此,若先give,后take,则take该信号量时,会因队列为满,立即take成功,而不会阻塞;

 同步功能

        二值信号量的同步功能有时可以使用直达任务通知(Task notifications)来代替,且比二进制信号量更快,更节省内存,与linux中的completion类似(wait & notify);

        二值信号量用作同步功能时的操作流程:信号量在创建后被置为空,task a 获取信号量进入阻塞,task b在某种条件发生后,释放信号量,task a从而得以获得信号量进入就绪态。

  • 如果task a 的优先级是最高的,那么就会立即切换任务,从而达到两个任务间的同步。
  • 同样的,在ISR中释放信号量,从而达到任务与中断间的同步。

    code示例

    #define LONG_TIME 0xffff
    #define TICKS_TO_WAIT 10
    
    SemaphoreHandle_t xSemaphore = NULL;
    
    /* Repetitive task. */
    void vATask( void * pvParameters )
    {
        /* We are using the semaphore for synchronisation so we create a binary
           semaphore rather than a mutex. We must make sure that the interrupt
           does not attempt to use the semaphore before it is created! */
        xSemaphore = xSemaphoreCreateBinary();
    
        for( ;; )
        {
            /* We want this task to run every 10 ticks of a timer. The semaphore
               was created before this task was started.
    
               Block waiting for the semaphore to become available. (等待ISR GIVE) */
            if( xSemaphoreTake( xSemaphore, LONG_TIME ) == pdTRUE )
            {
                /* It is time to execute. */
    
                 ...
    
                /* We have finished our task. Return to the top of the loop where
                   we will block on the semaphore until it is time to execute
                   again. Note when using the semaphore for synchronisation with an
                   ISR in this manner there is no need to 'give' the semaphore
                   back. */
            }
        }
    }
    
    /* Timer ISR */
    void vTimerISR( void * pvParameters )
    {
    static unsigned char ucLocalTickCount = 0;
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
        /* A timer tick has occurred. */
    
        ... Do other time functions.
    
        /* Is it time for vATask() to run? */
        xHigherPriorityTaskWoken = pdFALSE;
        ucLocalTickCount++;
        if( ucLocalTickCount >= TICKS_TO_WAIT )
        {
            /* Unblock the task by releasing the semaphore. */
            xSemaphoreGiveFromISR( xSemaphore, &xHigherPriorityTaskWoken );
    
            /* Reset the count so we release the semaphore again in 10 ticks
               time. */
            ucLocalTickCount = 0;
        }
    
        /* Yield if xHigherPriorityTaskWoken is true. The 
           actual macro used here is port specific. */
        portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
    }
    

    计数信号量(Counting Semaphore)

            计数信号量可被认为队列长度大于1的任务。与二值信号量一样,当任务或中断释放一个信号量,信号量的计数值会加1,该值表示还有多个事件没被处理,即可以不阻塞地take的次数(也可以理解为系统中可用的资源数目)。

    • 计数信号量可以用于资源管理,允许多个任务同时访问共享资源,但会限制任务的最大数目。

    相关接口

    /* 创建计数值为:uxMaxCount的计数信号量 */
    /* uxInitialCount: 创建信号量时分配给信号量的计数值 */
    SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
                                                UBaseType_t uxInitialCount);
    
    /* 返回信号量的计数值,该接口也可以用于二值信号量,但返回值只有0,1 */
    UBaseType_t uxSemaphoreGetCount( SemaphoreHandle_t xSemaphore );
    

    参考:

    FreeRTOS 文档 - FreeRTOS™

    FreeRTOS内核实现与应用开发实战

    Logo

    openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

    更多推荐