1.前言

访问一个被多任务共享,或是被任务与中断共享的资源时,需要采用”互斥”技术以保证数据在任何时候都保持一致性。这样做的目的是要确保任务从开始访问资源就具有排它性,直至这个资源又恢复到完整状态

FreeRTOS 提供了多种特性用以实现互斥,但是最好的互斥方法(如果可能的话,任何时候都当如此)还是通过精心设计应用程序,尽量不要共享资源,或者是每个资源都通过单任务访问。

2.本章内容

本章期望让读者了解以下内容:
 为什么,以及在什么时候有必要进行资源管理与控制。
 什么是临界区。
 互斥是什么意思。
 挂起调度器有什么意义。
 如何使用互斥量。
 如何创建与使用守护任务。
 什么是优先级反转,以及优先级继承是如何减小(但不是消除)其影响的

3.为何进行资源管理与控制

可参考freeRTOS学习4--资源管理概述一文中2.并发抢占导致错误的场景

4.基本临界区(方法一)

4.1 基本临界区示例

基本临界区是指宏taskENTER_CRITICAL()与taskEXIT_CRITICAL()之间的代码区间

使用基本临界区对寄存器的访问进行保护代码示例如下:

/* 为了保证对PORTA寄存器的访问不被中断,将访问操作放入临界区。
进入临界区 */
taskENTER_CRITICAL();
/* 在taskENTER_CRITICAL() 与 taskEXIT_CRITICAL()之间不会切换到其它任务。 中断可以执行,也允许
嵌套,但只是针对优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断 – 而且这些中断不允许访问
FreeRTOS API 函数. */
PORTA |= 0x01;
/* 我们已经完成了对PORTA的访问,因此可以安全地离开临界区了。 */
taskEXIT_CRITICAL();

4.2 基本临界区的几点说明

(1)基本临界区是提供互斥功能的一种非常原始的实现方法,临界区的工作仅仅是简单地把中断全部关掉

或是关掉优先级在configMAX_SYSCAL_INTERRUPT_PRIORITY 及以下的中断——依赖于具体使用的FreeRTOS 移植

优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断不允许访问 FreeRTOS API 函数

(2)抢占式上下文切换只可能在某个中断中完成

所以调用taskENTER_CRITICAL()的任务可以在中断关闭的时段一直保持运行态,直到退出临界区。

(3)临界区必须只具有很短的时间

否则会反过来影响中断响应时间,在每次调用taskENTER_CRITICAL()之后,必须尽快地配套调用一个taskEXIT_CRITICAL()

(4)临界区嵌套是安全的

因为内核有维护一个嵌套深度计数。临界区只会在嵌套深度为0 时才会真正退出——即在为每个之前调用的taskENTER_CRITICAL()都配套调用了taskEXIT_CRITICAL()之后。

注:基本临界区嵌套个人理解是当前某个任务正在操作临界资源,其它任务也进入临界区来操作临界资源,内核会检查一个任务退出临界区才允许另一个任务进入临界区

内核通过taskENTER_CRITICAL()和taskEXIT_CRITICAL()的计数次数来决定什么时候真正的使能中断

5.挂起调度器(方法二)

5.1 什么是挂起调度器

挂起调度器是通过锁定调度器来创建临界区。

挂起调度器有些时候也被称为锁定调度器,挂起调度器实现的临界区只可以保护一段代码区间不被其它任务打断

5.2 与基本临界区的区别

基本临界区保护一段代码区间不被其它任务或中断打断;

挂起临界区只可以保护一段代码区间不被其它任务打断,中断是使能的

5.3 采用挂起调度器or基本临界区

如果一个临界区太长而不适合简单地关中断来实现,可以考虑采用挂起调度器的方式。

但是唤醒(resuming, or un-suspending)调度器却是一个相对较长的操作。所以评估哪种是最佳方式需要结合实际情况。

5.4 挂起调度器主要API

名称 说明 参数 返回值
vTaskSuspendAll()

挂起调度器。挂起调度器可以停止上下文切换而不用关中断。如果某个中断在调度器挂起过程中要求进行上下文切换,则个这请求也会被挂起,直到调度器被唤醒后才会得到执行。

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

   
xTaskResumeAll()

在调度器挂起过程中,上下文切换请求也会被挂起,直到调度器被唤醒后才会得到执行。

  如果一个挂起的上下文切换请求在xTaskResumeAll()返回前得到执行,则函数返回pdTRUE。在其它情况下,xTaskResumeAll()返回pdFALSE

注:嵌套调用vTaskSuspendAll()和xTaskResumeAll()是安全的,因为内核有维护一个嵌套深度计数。

调度器只会在嵌套深度计数为0 时才会被唤醒——即在为每个之前调用的vTaskSuspendAll()都配套调用了xTaskResumAll()之后。

千万不要在调度器挂起时调用其它API 函数

5.5 挂起调度器示例

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();
}
}

6.互斥量(二值信号量)(方法三)

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

6.1 互斥量与二值信号量的区别

最大的区别在于信号量在被获得之后所发生的事情

  • 用于互斥的信号量必须归还。
  • 用于同步的信号量通常是完成同步之后便丢弃,不再归

6.2 使用互斥量的场景示例

6.3 主要API

名称 说明 参数 返回值
xSemaphoreCreateMutex()

互斥量是一种信号量。FreeRTOS 中所有种类的信号量句柄都保存在类型为xSemaphoreHandle 的变量中。
互斥量在使用前必须先创建。创建一个互斥量类型的信号量需要使用xSemaphoreCreateMutex() API 函

 

如果返回NULL 表示互斥量创建失败。原因是内存堆空间不足导致FreeRTOS 无法为互斥量分配结构数据空间。第五章提供更多关于内存管理方面的信息。
返回非NULL 值表示互斥量创建成功。返回值应当保存起来作为该互斥量的句柄。

7.  优先级反转

7.1 什么是优先级反转

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

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

7.2 优先级反转的最坏情况

当高优先级任务正等待信号量的时候,一个介于两个任务优先之间的中等优先级任务开始执行——这就会导致一个高优先级任务在等待一个低优先级任务,而低优先级任务却无法执行

7.3. 优先级继承

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

(2)持有互斥量的低优先级任务”继承”了等待互斥量的任务的优先级

(3)互斥量持有者在归还互斥量时,优先级会自动设置为其原来的优先级

注:由于最好是优先考虑避免优先级反转,并且因为FreeRTOS 本身是面向内存有限的微控制器,所以只实现了最基本的互斥量的优先级继承机制,这种实现假定一个任务在任意时刻只会持有一个互斥量

8. 死锁

当两个任务都在等待被对方持有的资源时,两个任务都无法再继续执行,这种情况就被称为死锁

9. 守护任务(方法四)

9.1 守护任务的特点

(1)守护任务提供了一种干净利落的方法来实现互斥功能,而不用担心会发生优先级反转和死锁。
(2)守护任务是对某个资源具有唯一所有权的任务。只有守护任务才可以直接访问其守护的资源——其它任务要访问该资源只能间接地通过守护任务提供的服务。

9.2 守护任务的示例

以中断和任务中都打印字符串到输出设备为例

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

(2)守护任务大部份时间都在阻塞态等待队列中有信息到来。当一个信息到达时,守护任务仅仅简单地将收到的信息写到标准输出上,然后又返回阻塞态,继续等待下一条信息地到来

(3)中断中可以写队列,所以中断服务例程也可以安全地使用守护任务提供的服务,从而把信息输出到终端

10.参考文档

[1] FreeRTOS中文实用教程

freeRTOS中文实用教程4--资源管理互斥的更多相关文章

  1. freeRTOS中文实用教程6--错误排查

    1.前言 本章主要是为刚接触FreeRTOS 的用户指出那些新手通常容易遇到的问题.这里把最主要的篇幅放在栈溢出以及栈溢出侦测上 2.printf-stdarg.c 当调用标准C 库函数时,栈空间使用 ...

  2. freeRTOS中文实用教程5--内存管理

    1.前言 不同的嵌入式系统具有不同的内存配置和时间要求.所以单一的内存分配算法只可能适合部分应用程序. FreeRTOS 将内存分配作为可移植层面(相对于基本的内核代码部分而言).这使得不同的应用程序 ...

  3. freeRTOS中文实用教程4--资源管理概述

    1.前言 多任务系统中存在一种潜在的风险.当一个任务在使用某个资源的过程中,即还没有完全结束对资源的访问时,便被切出运行态,使得资源处于非一致,不完整的状态 2.并发抢占导致错误的场景 (1)访问外设 ...

  4. freeRTOS中文实用教程3--中断管理之中断嵌套

    1.前言 最新的 FreeRTOS 移植中允许中断嵌套.中断嵌套需要在 FreeRTOSConfig.h 中设置configKERNEL_INTERRUPT_PRIORITY 和configMAX_S ...

  5. freeRTOS中文实用教程1--任务

    1.前言 FreeRTOS是小型多任务嵌入式操作系统,硬实时性.本章主要讲述任务相关特性及调度相关的知识. 2. 任务的总体特点 任务的状态 (1)任务有两个状态,运行态和非运行态 (2)任务由非运行 ...

  6. freeRTOS中文实用教程3--中断管理之计数信号量

    1.前言 在中断不频繁的系统中,使用二值信号量没有问题,但是中断频繁发生时,则会有中断丢失的问题. 因为中断发生时延迟任务执行,延迟任务执行的过程中,如果又来了两次中断,则只会处理第一次,第二次将会丢 ...

  7. freeRTOS中文实用教程2--队列

    1.前言 freeRTOS中所有任务的通信和同步机制都是基于队列来实现. 2.队列的特点 图 队列的读写操作 队列的数据存储 (1)队列可以保存有限个具有确定长度的数据单元,队列可以保存的最大单元数目 ...

  8. freeRTOS中文实用教程3--中断管理之中断服务例程中使用队列

    1.前言 消息队列不仅可以用于事件通信,还可以用来传递数据 2.实例说明消息队列的执行过程 3.主要API API名称 说明 参数 返回值 xQueueSendFromISR()完全等同于 xQueu ...

  9. freeRTOS中文实用教程3--中断管理之延迟中断处理

    1.前言 嵌入式实时操作系统需要对整个系统环境产生的事件作出响应.可以采用中断方式也可以采用轮询方式来进行处理.如果采用中断方式,则希望ISR(中断服务例程)的处理时间越短越好. 注:必须说明的是,只 ...

随机推荐

  1. CDQ分治总结(CDQ,树状数组,归并排序)

    闲话 CDQ是什么? 是一个巨佬,和莫队.HJT(不是我这个蒟蒻)一样,都发明出了在OI中越来越流行的算法/数据结构. CDQ分治思想 分治就是分治,"分而治之"的思想. 那为什么 ...

  2. 洛谷 P1450.硬币购物 解题报告

    P1450.硬币购物 题目描述 硬币购物一共有\(4\)种硬币.面值分别为\(c1,c2,c3,c4\).某人去商店买东西,去了\(tot\)次.每次带\(d_i\)枚\(c_i\)硬币,买\(s_i ...

  3. 【POJ3090】Visible Lattice Points

    题目大意:求 \[\sum\limits_{i=2}^n\phi(i)\] 题解:利用与埃筛类似的操作,可在 \(O(nlogn)\) 时间求出结果. 代码如下 #include <cstdio ...

  4. <? extends T>和<? super T>的理解

    背景:对泛型中使用super和extends关键字进行分析总结. 问题: public class TestExtend { public static void main(String[] args ...

  5. Java逐行写入字符串到文件

    下边是写东西到一个文件中的Java代码.运行后每一次,一个新的文件被创建,并且之前一个也将会被新的文件替代.这和给文件追加内容是不同的. 1. public static void writeFile ...

  6. MySQL5.5登录密码忘记了,怎嘛办?

    1.关闭正在运行的MySQL. 2.打开DOS窗口,转到mysql\bin目录. 3.输入mysqld --skip-grant-    tables回车.如果没有出现提示信息,那就对了. 4.再开一 ...

  7. java代码示例(2)

    /** * 目的:求各位之和 * @author chenyanlong * 时间:2017/10/14 */ package com.hp.test02; import java.util.Scan ...

  8. 编写前程贷投标loadrunner脚本及总结

    1.完成前程贷的(登录,投标) 2.所有的返回信息都用关联函数(web_reg_save_param_ex)进行关联 3.对返回信息用(strcmp)函数进行if判断 4.总结(web_reg_sav ...

  9. Eclipse鼠标点击变量高亮显示时好时坏的BUG

    Eclipse有一个BUG,就是鼠标点击某个变量会高亮显示所有这个变量 会有时高亮有时不高亮,修复这个BUG就是替换Eclipse 安装目录plugins目录下的org.eclipse.e4.ui.w ...

  10. .net视频教程代码之《提交注册内容》

    看我的视频之后感觉代码太多不好打或者容易打错的话可以来看我的这里的代码.我的视频地址是 https://www.bilibili.com/video/av12727717/ 类里面的代码: using ...