zoukankan      html  css  js  c++  java
  • FreeRTOS --(16)资源管理之临界区

    转载自 https://blog.csdn.net/zhoutaopower/article/details/107387427

    临界区的概念在任何的 SoC 都存在,比如,针对一个寄存器,基本操作为:读->改->写;在不带 OS 的系统下,普通代码希望对某个寄存器进行读->改->写,此刻,一个 IRQ 打断了这个操作,也同时对这个寄存器进行 读->改->写,中断返回,后,普通代码又继续进行,这样就会导致逻辑错误;

    在带 OS 的情况下,不光是有 IRQ,而且存在任务切换,这样,同一个资源在 ISR 和不同任务之间修改,这造成了临界区;临界区的资源需要保护起来,临界区保护的不是代码,而是数据;

    所以,在设计阶段尽早识别哪些是临界区,以及采取对应的策略,避免后续出现很难查的问题;

    FreeRTOS 针对临界区资源,存在几种保护的方式:

    1、taskENTER_CRITICAL() 和 taskEXIT_CRITICAL()

    2、vTaskSuspendAll() 和 xTaskResumeAll()

    3、Mutexes

    4、Gatekeeper Tasks

    接下来就看看他们的具体概念以及用法;

    1、taskENTER_CRITICAL

    这是个最强悍的临界区保护调用,它总是和 taskEXIT_CRITICAL 成对出现,即:

    taskENTER_CRITICAL();
    {
        .............// 临界区
    }
    taskEXIT_CRITICAL();

    为何称之为强悍,因为它直接屏蔽了中断,OS 调度靠中断,ISR 也靠中断;

    也就是说,在这之间的对数据的操作,是绝对安全!

    适用场景是,临界区可能存在于中断和任务中;

    使用 taskENTER_CRITICAL 的时候,尽量保证这个临界区很短小,因为它暂停了所有的活动,来满足这段临界区,外部其他的任何响应,都无法阻止他;

    它的实现是

    注意:

    1、在 ISR 中使用 taskENTER_CRITICAL_FROM_ISR() 和 taskEXIT_CRITICAL_FROM_ISR();

    2、taskENTER_CRITICAL 和 taskEXIT_CRITICAL 必须成对出现;

    taskENTER_CRITICAL 和 taskEXIT_CRITICAL  支持嵌套使用,因为里面维护了一个引用计数;

    2、vTaskSuspendAll

    上面那种关闭中断的方式,需要尽快退出临界区,以免引起中断延时处理,任务被延时处理;

    FreeRTOS 还提供了一种挂起调度器的方式的临界区,它通过调用 vTaskSuspendAll 和 xTaskResumeAll 来建立临界区:

    vTaskSuspendAll();
    {
        .............// 临界区
    }
    xTaskResumeAll();

    这种方式和 taskENTER_CRITICAL 不一样的地方在于,它仅仅是挂起了调度器,而没有去关闭中断;换言之,资源争夺的场景中,它仅仅是防止了任务之间的资源争夺,中断照样可以直接响应;

    所以,挂起调度器的方式,适用于,临界区位于任务与任务之间;既不用去延时中断,又可以做到临界区的安全;

    3、Mutexes

    3.1、Usage

    互斥量是二值信号量的特殊形式 (它也是通过 Queue 实现),与二值信号量不同,互斥量用于控制多个任务之间共享资源的访问,也就是互锁;

    不同于上面两种,互斥量不但开放了中断,同时也不挂起调度器;

    使用互斥量,需要定义 configUSE_MUTEXES 为 1

    用于互锁的互斥量可以充当保护资源的令牌。当一个任务希望访问某个资源时,它必须先获取令牌。当任务使用完资源后,必须还回令牌,以便其它任务可以访问同一资源。

    互斥量和信号量使用相同的API函数,因此互斥量也允许指定一个阻塞时间。阻塞时间单位为系统节拍周期时间,数目表示获取互斥量无效时最多处于阻塞状态的系统节拍周期个数。

    互斥量与二进制信号量最大的不同是:互斥量具有优先级继承机制。也就是说,如果一个互斥量(令牌)正在被一个低优先级任务使用,此时一个高优先级企图获取这个互斥量,高优先级任务会因为得不到互斥量而进入阻塞状态,正在使用互斥量的低优先级任务会临时将自己的优先级提升,提升后的优先级与与进入阻塞状态的高优先级任务相同。这个优先级提升的过程叫做优先级继承。这个机制用于确保高优先级任务进入阻塞状态的时间尽可能短,以及将已经出现的“优先级翻转”影响降低到最小。

    在很多场合中,某个硬件资源只有一个,当低优先级任务占用该资源的时候,即便高优先级任务也只能乖乖的等待低优先级任务释放资源。这里高优先级任务无法运行而低优先级任务可以运行的现象称为“优先级翻转”。

          为什么优先级继承能够降低优先级翻转的影响呢?举个例子,现在有任务A、任务B和任务C,三个任务的优先级顺序为任务C>任务B>任务A。任务A和任务C都要使用某一个硬件资源,并且当前任务A占有该资源。

          先看没有优先级继承的情况:任务C也要使用该资源,但是此时任务A正在使用这个资源,因此任务C进入阻塞,此时三个任务的优先级顺序没有发生变化。在任务C进入阻塞之后,某硬件产生了一次中断,唤醒了一个事件,该事件可以解除任务B的阻塞状态。在中断结束后,因为任务B的优先级是大于任务A的,所以任务B抢占任务A的CPU权限。那么任务C的阻塞时间就至少为:中断处理时间+任务B的运行时间+任务A的运行时间。

          再看有优先级继承的情况:任务C也要使用该资源,但是此时任务A正在使用这个资源,因此任务C进入阻塞,此时由于优先级A会继承任务C的优先级,三个任务的优先级顺序发生了变化,新的优先级顺序为:任务C=任务A>任务B。在任务C进入阻塞之后,某硬件产生了一次中断,唤醒了一个事件,该事件可以解除任务B的阻塞状态。在中断结束后,因为任务A的优先级临时被提高,大于任务B的优先级,所以任务A继续获得CPU权限。任务A完成后,处于高优先级的任务C会接管CPU。所以任务C的阻塞时间为:中断处理时间+任务A的运行时间。看,任务C的阻塞时间变小了,这就是优先级继承的优势。

    优先级继承不能解决优先级反转,只能将这种情况的影响降低到最小。硬实时系统在一开始设计时就要避免优先级反转发生。

    典型的,两个 Task A、Task B 都要访问同一个资源,他们通过互斥量来做互斥,

    首先 A 获取到资源,进入临界区,

    此刻 B 去获取资源,由于 A 还没用完资源,所有获取不到,进入阻塞;

     A 执行完毕后,释放资源;

    资源到位,B 解除阻塞,获取资源;

    B 执行完毕,释放资源;

    3.2、APIs

    创建一个互斥量,使用 xSemaphoreCreateMutex

    SemaphoreHandle_t xSemaphoreCreateMutex( void );

    有一个返回值,如果成功创建,返回句柄,否则返回 NULL;

    进入/ 退出 互斥量的临界区使用和信号量一样的接口 xSemaphoreTake 和 xSemaphoreGive,

    注意:ISR 中使用带 _FromISR 版本的 API

    Example:

    static void prvNewPrintString( const char *pcString )
    {
        /* The mutex is created before the scheduler is started, so already exists by the 
        time this task executes.
        
        Attempt to take the mutex, blocking indefinitely to wait for the mutex if it is 
        not available straight away. The call to xSemaphoreTake() will only return when 
        the mutex has been successfully obtained, so there is no need to check the
        function return value. If any other delay period was used then the code must 
        check that xSemaphoreTake() returns pdTRUE before accessing the shared resource 
        (which in this case is standard out). As noted earlier in this book, indefinite 
        time outs are not recommended for production code. */
        xSemaphoreTake( xMutex, portMAX_DELAY );
        {
            /* The following line will only execute once the mutex has been successfully 
            obtained. Standard out can be accessed freely now as only one task can have 
            the mutex at any one time. */
            printf( "%s", pcString );
            fflush( stdout );
        }
        /* The mutex MUST be given back! */
        xSemaphoreGive( xMutex );
    }
  • 相关阅读:
    2018.4.23 深入理解java虚拟机(转)
    2018.4.23 git常用操作命令收集(转)
    2018.4.23 设计模式的一些总结
    2018.4.23 pip使用
    2018.4.23 git命令总结
    2018.4.23 git删除已经add的文件
    2018.4.17 VFS
    记北京第一次跳槽
    RocketMQ存储机制01-存储文件组织与内存映射
    将博客搬至CSDN
  • 原文地址:https://www.cnblogs.com/FZLGYZ/p/13785758.html
Copyright © 2011-2022 走看看