FreeRTOS和RT-Thread的资源管理

文章目录

  • 资源管理
    • 一、简述
      • 个人总结
    • 二、保证原子操作的二种方式
      • 1. 临界区
          • API
      • 2. 挂起调度器
      • 个人总结
    • 三、互斥量(mutex)
        • 优先级反转
        • 优先级继承
        • 死锁
    • 四、守护任务
    • 零: 线程间同步的名字解释
      • 1. 信号量
        • **互斥量和信号量**

资源管理

一、简述

  • 什么是资源管理

    防止不同的任务,对同一资源在并发访问的时候出现脏读,藏写现象。对此进行管理的操作叫资源管理。在单线程多任务的嵌入式领域,资源管理又分为线程间同步和线程间通信二个模块的知识。

  • 资源管理的具体实现方式

    1. 关闭中断: 保证 a 在访问资源的时候,为了不被 b 中断,直接关闭中断功能,导致即使 b 无法产生中断来打断 a 的操作。
    2. 关闭资源资源调度器: a 在访问资源的时候,直接将资源调度器挂起,即使 b 的中断产生了,但是调度器休眠, 也无法打断 a 的操作。
    3. 守护线程: 有些资源只有 c 才可以访问, a 和 b 都想访问的时候,必须通过给 c 发送消息,让 c 来直接操作资源,a 和 b 同时发消息给 c, 让 c 来决定先执行谁,在执行谁。

个人总结

就是通过一些操作,防止并行访问资源的时候,出现脏读藏写的管理称之为资源管理。

二、保证原子操作的二种方式

  • 原子性

    • 通俗解释

    物理上认为原子是最小的粒子,不能在进行分割。计算机的原子性讲的就是操作不能被打断,不能先执行一部分,过一会子执行另一部分,必须一次性完全执行完成。

    • 专业解释

    指事务的不可分割性,一个事务的所有操作要么不间断地全部被执行,要么一个也没有执行。

1. 临界区

  • 概念

临界区指的是一个访问共用资源(例如:共用设备或是共用存储器)的程序片段,而这些共用资源又无法同时被多个线程访问的特性。

  • 简介

每个进程中访问临界资源的那段代码称为临界区(Critical Section)(临界资源是一次仅允许一个进程使用的共享资源)。每次只准许一个进程进入临界区,进入后不允许其他进程进入。不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它进行访问。

  • 简述

把某优先级以下的中断全部关闭。进行原子操作。

  • 原理

把中断全部关掉,或是关掉优先级在 、configMAX_SYSCAL_INTERRUPT_PRIORITY 及以下的中断——依赖于具体使用的 FreeRTOS 移植。抢占式上下文切换只可能在某个中断中完成,所以调用 taskENTER_CRITICAL() 的任务可以在中断关闭的时段一直保持运行态,直到退出临界区.

API

临界区必须只具有很短的时间,否则会反过来影响中断响应时间。在每次调用 taskENTER_CRITICAL() 之后,必须尽快地配套调用一个 taskEXIT_CRITICAL(), 读写这种比较耗费时间的操作,尽量不要使用临界区这种保护机制。

  • FreeRTOS
void vPrintString( const portCHAR *pcString )
{
    /* 往stdout中写字符串,使用临界区这种原始的方法实现互斥。 */
    taskENTER_CRITICAL();
    {
        printf( "%s", pcString );
        fflush( stdout );
    }
    taskEXIT_CRITICAL();
    /* 允许按任意键停止应用程序运行。实际的应用程序如果有使用到键值,还需要对键盘输入进行保护。 */
    if( kbhit() )
    {
    	vTaskEndScheduler();
    }
}
  • RT-Thread
1)调用 rt_hw_interrupt_disable() 进入临界区,调用 rt_hw_interrupt_enable() 退出临界区;
2)调用 rt_enter_critical() 进入临界区,调用 rt_exit_critical() 退出临界区。

2. 挂起调度器

直接把任务调度器给挂起锁死,这个时候外部中断虽然使能,但是无法操作。

需要创建临界区除了 FreeRTOS 操作系统提供的函数外,还可以通过挂起调度器的操作来创建一个临界区。但是重新唤醒(resuming, or un-suspending)调度器是一个费时的操作。所以评估使用临界区函数来实现原子操作还是挂起调度器实现原子操作,需要结合实际情况。

在调度器处于挂起状态时,不能调用 FreeRTOS API 函数。

  • API
void vPrintString( const portCHAR *pcString )
{
    /* Write the string to stdout, suspending the scheduler as a method
    of mutual exclusion. */
    vTaskSuspendScheduler();
    {
        printf( "%s", pcString );
        fflush( stdout );
    }
    xTaskResumeScheduler();
    /* Allow any key to stop the application running. A real application that
    actually used the key value should protect access to the keyboard input too. */
    if( kbhit() )
    {
        vTaskEndScheduler();
    }
}

个人总结

就是将外部中断给屏蔽,原本的操作就不会被打断,这样操作起来就具有原子性了。

三、互斥量(mutex)

互斥量是一种特殊的二值信号量,用于控制在两个或多个任务间访问共享资源。

  • 互斥量的特点

    用于互斥的信号量必须归还。

    用于同步的信号量通常是完成同步之后便丢弃,不再归还

  • API

static void prvNewPrintString( const portCHAR *pcString )
{
    /* 互斥量在调度器启动之前就已创建,所以在此任务运行时信号量就已经存在了。
    试图获得互斥量。如果互斥量无效,则将阻塞,进入无超时等待。xSemaphoreTake()只可能在成功获得互
    斥量后返回,所以无需检测返回值。如果指定了等待超时时间,则代码必须检测到xSemaphoreTake()返回
    pdTRUE后,才能访问共享资源(此处是指标准输出)。 */
    xSemaphoreTake( xMutex, portMAX_DELAY );
    {
        /* 程序执行到这里表示已经成功持有互斥量。现在可以自由访问标准输出,因为任意时刻只会有一个任
        务能持有互斥量。 */
        printf( "%s", pcString );
        fflush( stdout );
    }
    /* 互斥量必须归还! */
    xSemaphoreGive( xMutex );
    
    /* Allow any key to stop the application running. A real application that
    actually used the key value should protect access to the keyboard too. A
    real application is very unlikely to have more than one task processing
    key presses though! */
    if( kbhit() )
    {
    	vTaskEndScheduler();
    }
}


int main( void )
{
    /* 信号量使用前必须先创建。本例创建了一个互斥量类型的信号量。 */
    xMutex = xSemaphoreCreateMutex();
    /* 本例中的任务会使用一个随机延迟时间,这里给随机数发生器生成种子。 */
    srand( 567 );
    /* Check the semaphore was created successfully before creating the tasks. */
    if( xMutex != NULL )
    {
        /* Create two instances of the tasks that write to stdout. The string
        they write is passed in as the task parameter. The tasks are created
        at different priorities so some pre-emption will occur. */
        xTaskCreate( prvPrintTask, "Print1", 1000,
        "Task 1 ******************************************\r\n", 1, NULL );
        xTaskCreate( prvPrintTask, "Print2", 1000,
        "Task 2 ------------------------------------------\r\n", 2, NULL );
        /* Start the scheduler so the created tasks start executing. */
        vTaskStartScheduler();
    }
    /* 如果一切正常,main()函数不会执行到这里,因为调度器已经开始运行任务。但如果程序运行到了这里,
    很可能是由于系统内存不足而无法创建空闲任务。 */
    for( ;; );
}

FreeRTOS和RT-Thread的资源管理_第1张图片

FreeRTOS和RT-Thread的资源管理_第2张图片

优先级反转

高优先级任务被低优先级任务阻塞推迟的行为被称为”优先级反转”。

优先级反转可能会产生重大问题。但是在一个小型的嵌入式系统中,通常可以在设计阶段就通过规划好资源的访问方式避免出现这个问题。’

优先级继承

优先级继承是最小化优先级反转负面影响的一种方案-其并不能修正优先级反转带来的问题,仅仅是减小优先级反转的影响。优先级继承
使得系统行为的数学分析更为复杂,所以如果可以避免的话,并不建议系统实现对优先
级继承有所依赖。

优先级继承暂时地将互斥量持有者的优先级提升至所有等待此互斥量的任务所具有的最高优先级。

FreeRTOS和RT-Thread的资源管理_第3张图片

死锁

  1. 任务 A 执行,并成功获得了互斥量 X。
  2. 任务 A 被任务 B 抢占。
  3. 任务 B 成功获得了互斥量 Y,之后又试图获取互斥量 X——但互斥量 X 已经被任务 A 持有,所以对任务 B 无效。任务 B 选择进入阻塞态以等待互斥量 X 被释放。
  4. 任务 A 得以继续执行。其试图获取互斥量 Y——但互斥量 Y 已经被任务 B持有而对任务 A 无效。任务 A 也选择进入阻塞态以等待互斥量 Y 被释放。

任务 A 在等待一个被任务 B 持有的互斥量,而任务 B 也在等待一个被任务 A 持有的互斥量。死锁于是发生,因为两个任务都不可能再执行下
去了。

四、守护任务

  1. 守护任务是对某个资源具有唯一所有权的任务。只有守护任务才可以直接访问其守护的资源——其它任务要访问该资源只能间接地通过守护任务提供的服务

  2. 守护任务提供了一种干净利落的方法来实现互斥功能,而不用担心会发生优先级反转和死锁。

守护任务使用了一个 FreeRTOS 队列来对终端实现串行化访问。该任务内部实现不必考虑互斥,因为它是唯一能够直接访问终端的任务。

  • 总结

有些资源只有守护线程才能访问,使用的时候发消息给守护线程,让守护线程去访问资源,从而避免死锁和优先级反转的问题

零: 线程间同步的名字解释

1. 信号量

信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。

信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量 引用自: 百度百科-信号量

FreeRTOS和RT-Thread的资源管理_第4张图片

互斥量和信号量

  1. 互斥量用于线程的互斥,信号量用于线程的同步。

    这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。

2.互斥量无法保证线程对资源的有序访问,信号量可以。

二值信号量和互斥锁的区别

你可能感兴趣的:(实时操作系统,嵌入式,freertos,实时操作系统)