FreeRTOS为什么要区分任务函数和中断函数?

在我们之前的学习中,队列,信号量,互斥量,事件组,任务通知,它们都有两套函数,在任务中使用或在中断中使用。

1.为什么要用两套函数?

情景1:

我们在写队列的时候等待100个Tick那这个任务就一定只执行了等待了100个Tick吗?并不一定!

我们假设一个场景,任务A写队列等待100个Tick,如果写成功就会唤醒任务B。假设任务B的优先级高于任务A,如果这个B不阻塞,那这个A就再也无法执行。

在任务里写了队列如果有高优先级任务会立马唤醒并切换!

因此在任务里写队列我们一定要考虑到众多影响程序运行的因素!

 情景2:

我们在中断中写队列,没有使用中断API函数使用任务函数,成功写队列后唤醒高优先级任务B,要不要马上切换任务呢?会发生什么事呢?

中断函数我们要求的是尽快执行完,唤醒任务很简单只是去移动到对应的链表里。而切换任务就比较慢,它要保存现场和恢复涉及到寄存器大量的读写。并且任务是不会去打断中断的。就算我们在中断处理过程中去切换(启用调度器)高优先级的任务也是无法执行的。因此我们会在中断结束前去切换任务。

因此在中断中我们写队列只是去唤醒任务并不会马上进行切换,我们会使用一个变量(xHigherPriorityTaskWoken)来记录是否有更高优先级的任务被唤醒,在中断结束前根据这个变量判断是否需要切换任务。

在中断里只去唤醒并不会马上切换。

总结:由于在中断中的情况和在任务中写队列的情况有所不同因此提供了两套函数供使用者使用。

情景2再分析

在情境2中我们在中断结束之前开启了调度,如果没有开启调度会怎样呢?

在中断中没有去开启调度器,那中断结束后高优先级任务B并不立刻执行而是等到下一个Tick中断,在Tick中断里去启用调度器切换高优先级任务B。

问题回顾:我们在之前讲了在高优先级任务就绪后会抢占低优先级任务,注意这里我们是当低优先级任务执行时创建高优先级任务,高优先级任务抢占了低优先级任务,为什么呢?因为在创建任务的函数结束之前会启用调度器,调度器遍历就绪链表有更高级的任务就绪就会进行切换。

我们发现高优先级的任务B无法快速执行,这不符合实时操作系统的目的!

1.2. 两套API函数列表

类型在任务中在ISR中
队列(queue)xQueueSendToBackxQueueSendToBackFromISR
xQueueSendToFrontxQueueSendToFrontFromISR
xQueueReceivexQueueReceiveFromISR
xQueueOverwritexQueueOverwriteFromISR
xQueuePeekxQueuePeekFromISR
信号量(semaphore)xSemaphoreGivexSemaphoreGiveFromISR
xSemaphoreTakexSemaphoreTakeFromISR
事件组(event group)xEventGroupSetBitsxEventGroupSetBitsFromISR
xEventGroupGetBitsxEventGroupGetBitsFromISR
任务通知(task notification)xTaskNotifyGivevTaskNotifyGiveFromISR
xTaskNotifyxTaskNotifyFromISR
软件定时器(software timer)xTimerStartxTimerStartFromISR
xTimerStopxTimerStopFromISR
xTimerResetxTimerResetFromISR
xTimerChangePeriodxTimerChangePeriodFromISR

1.3. xHigherPriorityTaskWoken参数

xHigherPriorityTaskWoken的含义是:是否有更高优先级的任务被唤醒了。如果为pdTRUE,则意味着后面要进行任务切换。

还是以写队列为例。

任务A调用 xQueueSendToBack() 写队列,有几种情况发生:

  • 队列满了,任务A阻塞等待,另一个任务B运行
  • 队列没满,任务A成功写入队列,但是它导致另一个任务B被唤醒,任务B的优先级更高:任务B先运行
  • 队列没满,任务A成功写入队列,即刻返回

可以看到,在任务中调用API函数可能导致任务阻塞、任务切换,这叫做"context switch",上下文切换。这个函数可能很长时间才返回,在函数的内部实现了任务切换。

xQueueSendToBackFromISR() 函数也可能导致任务切换,但是不会在函数内部进行切换,而是返回一个参数:表示是否需要切换,函数原型与用法如下:

/* 
 * 往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞
 */
BaseType_t xQueueSendToBackFromISR(
                                      QueueHandle_t xQueue,
                                      const void *pvItemToQueue,
                                      BaseType_t *pxHigherPriorityTaskWoken
                                   );

/* 用法示例 */

BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendToBackFromISR(xQueue, pvItemToQueue, &xHigherPriorityTaskWoken);

if (xHigherPriorityTaskWoken == pdTRUE)
{
    /* 任务切换 */    
}

pxHigherPriorityTaskWoken参数,就是用来保存函数的结果:是否需要切换

  • *pxHigherPriorityTaskWoken等于pdTRUE:函数的操作导致更高优先级的任务就绪了,ISR应该进行任务切换
  • *pxHigherPriorityTaskWoken等于pdFALSE:没有进行任务切换的必要

为什么不在"FromISR"函数内部进行任务切换,而只是标记一下而已呢?为了效率!示例代码如下:

void XXX_ISR()
{
    int i;
    for (i = 0; i < N; i++)
    {
        xQueueSendToBackFromISR(...); /* 被多次调用 */
    }
}

ISR中有可能多次调用"FromISR"函数,如果在"FromISR"内部进行任务切换,会浪费时间。解决方法是:

  • 在"FromISR"中标记是否需要切换
  • 在ISR返回之前再进行任务切换
  • 示例代码如下
void XXX_ISR()
{
    int i;
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    for (i = 0; i < N; i++)
    {
        xQueueSendToBackFromISR(..., &xHigherPriorityTaskWoken); /* 被多次调用 */
    }
	
    /* 最后再决定是否进行任务切换 */
    if (xHigherPriorityTaskWoken == pdTRUE)
	{
    	/* 任务切换 */    
	}
}

上述的例子很常见,比如UART中断:在UART的ISR中读取多个字符,发现收到回车符时才进行任务切换。

在ISR中调用API时不进行任务切换,而只是在"xHigherPriorityTaskWoken"中标记一下,除了效率,还有多种好处:

  • 效率高:避免不必要的任务切换
  • 让ISR更可控:中断随机产生,在API中进行任务切换的话,可能导致问题更复杂
  • 可移植性
  • 在Tick中断中,调用 vApplicationTickHook() :它运行与ISR,只能使用"FromISR"的函数

使用"FromISR"函数时,如果不想使用xHigherPriorityTaskWoken参数,可以设置为NULL。

1.4 怎么切换任务

FreeRTOS的ISR函数中,使用两个宏进行任务切换:

	portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
或
	portYIELD_FROM_ISR( xHigherPriorityTaskWoken );

这两个宏做的事情是完全一样的,在老版本的FreeRTOS中,

  • portEND_SWITCHING_ISR 使用汇编实现
  • portYIELD_FROM_ISR 使用C语言实现

新版本都统一使用portYIELD_FROM_ISR

使用示例如下:

void XXX_ISR()
{
    int i;
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    for (i = 0; i < N; i++)
    {
        xQueueSendToBackFromISR(..., &xHigherPriorityTaskWoken); /* 被多次调用 */
    }
	
    /* 最后再决定是否进行任务切换 
     * xHigherPriorityTaskWoken为pdTRUE时才切换
     */
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

2. 中断的延迟处理

前面讲过,ISR要尽量快,否则:

  • 其他低优先级的中断无法被处理:实时性无法保证
  • 用户任务无法被执行:系统显得很卡顿
  • 如果运行中断嵌套,这会更复杂,ISR越快执行越有助于中断嵌套

如果这个硬件中断的处理,就是非常耗费时间呢?对于这类中断的处理就要分为2部分:

  • ISR:尽快做些清理、记录工作,然后触发某个任务
  • 任务:更复杂的事情放在任务中处理

这种处理方式叫"中断的延迟处理"(Deferring interrupt processing),处理流程如下图所示:

  • t1:任务1运行,任务2阻塞
  • t2:发生中断,
  • 该中断的ISR函数被执行,任务1被打断
  • ISR函数要尽快能快速地运行,它做一些必要的操作(比如清除中断),然后唤醒任务2
  • t3:在创建任务时设置任务2的优先级比任务1高(这取决于设计者),所以ISR返回后,运行的是任务2,它要完成中断的处理。任务2就被称为"deferred processing task",中断的延迟处理任务。
  • t4:任务2处理完中断后,进入阻塞态以等待下一个中断,任务1重新运行

3 .中断与任务间的通信

前面讲解过的队列、信号量、互斥量、事件组、任务通知等等方法,都可使用。

要注意的是,在ISR中使用的函数要有"FromISR"后缀。

4 .示例: 优化实时性

本节代码为:29_fromisr_game,主要看DshanMCU-F103\driver_ir_receiver.c。

以前,在中断函数里写队列时,代码如下:

150 static void DispatchKey(struct ir_data *pidata)

151 {

152 #if 0

153   extern QueueHandle_t g_xQueueCar1;

154   extern QueueHandle_t g_xQueueCar2;

155   extern QueueHandle_t g_xQueueCar3;

156   xQueueSendFromISR(g_xQueueCar1, pidata, NULL);

157   xQueueSendFromISR(g_xQueueCar2, pidata, NULL);

158   xQueueSendFromISR(g_xQueueCar3, pidata, NULL);

159 #else

160   int i;

161    for (i = 0; i < g_queue_cnt; i++)

162    {

163        xQueueSendFromISR(g_xQueues[i], pidata, NULL);

164    }

165 #endif

166 }

假设当前运行的是任务A,它的优先级比较低,在它运行过程中发生了中断,中断函数调用了DispatchKey函数写了队列,使得任务B被唤醒了。任务B的优先级比较高,它应该在中断执行完后马上就能运行。但是上述代码无法实现这个目标,xQueueSendFromISR函数会把任务B调整为就绪态,但是不会发起一次调度。

需要如下修改代码:

150 static void DispatchKey(struct ir_data *pidata)

151 {

152 #if 0

153   extern QueueHandle_t g_xQueueCar1;

154   extern QueueHandle_t g_xQueueCar2;

155   extern QueueHandle_t g_xQueueCar3;

156   xQueueSendFromISR(g_xQueueCar1, pidata, NULL);

157    xQueueSendFromISR(g_xQueueCar2, pidata, NULL);

158    xQueueSendFromISR(g_xQueueCar3, pidata, NULL);

159 #else

160    int i;

161   BaseType_t xHigherPriorityTaskWoken = pdFALSE;
        //同一个数据写多个队列

162    for (i = 0; i < g_queue_cnt; i++)

163    {

164        xQueueSendFromISR(g_xQueues[i], pidata, &xHigherPriorityTaskWoken);

165    }

166   portYIELD_FROM_ISR(xHigherPriorityTaskWoken);

167 #endif

168 }

在第164行传入一个变量的地址:&xHigherPriorityTaskWoken,它的初始值是pdFALSE,表示无需发起调度。如果xQueueSendFromISR函数发现唤醒了更高优先级的任务,那么就会把这个变量设置为pdTRUE。

第166行,如果xHigherPriorityTaskWoken为pdTRUE,它就会发起一次调度。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/594857.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

图神经网络实战(9)——GraphSAGE详解与实现

图神经网络实战&#xff08;9&#xff09;——GraphSAGE详解与实现 0. 前言1. GraphSAGE 原理1.1 邻居采样1.2 聚合 2. 构建 GraphSAGE 模型执行节点分类2.1 数据集分析2.2 构建 GraphSAGE 模型 3. PinSAGE小结系列链接 0. 前言 GraphSAGE 是专为处理大规模图而设计的图神经网…

一般实现分布式锁都有哪些方式?使用 Redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高?

目录 1.Redis 分布式锁 &#xff08;1&#xff09;Redis 最普通的分布式锁 &#xff08;2&#xff09;RedLock 算法 2.zk 分布式锁 3.redis 分布式锁和zk分布式锁的对比 1.Redis 分布式锁 官方叫做 RedLock 算法&#xff0c;是 Redis 官方支持的分布式锁算法。 这个分布式…

C++ | Leetcode C++题解之第70题爬楼梯

题目&#xff1a; 题解&#xff1a; class Solution { public:int climbStairs(int n) {double sqrt5 sqrt(5);double fibn pow((1 sqrt5) / 2, n 1) - pow((1 - sqrt5) / 2, n 1);return (int)round(fibn / sqrt5);} };

从0到1:商场导览小程序开发笔记一

背景 购物中心与商场小程序&#xff1a;旨在提供便捷的购物、导航、活动报名、服务查询等功能&#xff0c;让用户更好地体验购物和享受服务。通过提供便捷的购物、信息查询和互动预约等功能&#xff0c;提升了商场的服务水平和用户体验&#xff0c;帮助商场与消费者建立更紧密…

mq发送消息之后,业务代码回滚,导致发了一条中奖消息给用户!!

背景是这样的&#xff1a;在一个名为"幸运大转盘"的线上活动中&#xff0c;用户可以通过消耗一定的积分来参与抽奖&#xff0c;有机会赢取各种奖品。这个活动的后台系统使用了消息队列&#xff08;MQ&#xff09;来处理用户的抽奖请求和发送中奖消息。 一天&#xf…

linux(ubuntu18.04.2) Qt编译 MySQL(8.0以上版本)链接库 Qt版本 5.12.12及以上 包含Mysql动态库缺失问题

整理这篇文档的意义在于&#xff1a;自己走了很多弯路&#xff0c;淋过雨所以想为别人撑伞&#xff0c;也方便回顾&#xff0c;仅供参考 一、搭建开发环境&#xff1a; 虚拟机&#xff08;ubuntu-20.04.6-desktop-amd64&#xff09;&#xff1a;Mysql数据库 8.0.36Workbench …

QtWindows任务栏

目录 引言任务栏进度右键菜单缩略图工具栏完整代码 引言 针对Windows系统的任务栏&#xff0c;Qt基于系统的原生接口封装有一些非常见类&#xff0c;如QWinTaskbarButton、QWinTaskbarButton、QWinThumbnailToolBar等&#xff0c;用于利用工具栏提供更多的信息&#xff0c;诸如…

开源电子邮件营销平台 listmonk 使用教程

做产品肯定要做电子邮件营销&#xff0c;特别是面向海外的产品&#xff0c;电子邮件营销已成为企业与客户沟通、建立品牌忠诚度和推动销售的重要工具&#xff0c;可以直接接触到目标受众&#xff0c;提供个性化内容&#xff0c;并以相对较低的成本获得可观的投资回报。你看&…

用HAL库改写江科大的stm32入门例子_1、按键控制led灯

1 如下图设置PB11 管脚 2 设置PB11为下降沿中断&#xff1a; 3 PA1 设置为推挽输出 4、NVIC 开启中断使能&#xff1a; 5、写中断事件&#xff1a; 完整代码如下&#xff1a; void EXTI15_10_IRQHandler(void) {/* USER CODE BEGIN EXTI15_10_IRQn 0 *///torning on the led…

母婴店运用商城小程序店铺的效果是什么

母婴市场规模高&#xff0c;还可与不少行业无缝衔接&#xff0c;尤其是以90后、00后为主的年轻人&#xff0c;在备孕生育和婴儿护理前后等整体流程往往不惜重金且时间长&#xff0c;母婴用品无疑是必需品&#xff0c;商家需要多方面拓展全面的客户及打通场景随时消费路径。 运…

24.5.5(离散化+树状数组,线段树)

星期一&#xff1a; dp题单 背包 第四题 混可乐 cf传送门 思路&#xff1a;条件可演化为每种可乐值为 ai-n&#xff0c;选最少的可乐使总和为0&#xff08;具体可看官方题解 到这会发现背包并不适合了&#xff0c;其实这是道bfs伪装的背包…

【Linux网络】网络文件共享

目录 一、存储类型 二、FTP文件传输协议 2.1 FTP工作原理 2.2 FTP用户类型 2.3 FTP软件使用 2.3.1 服务端软件vsftpd 2.3.2 客户端软件ftp 2.4 FTP的应用 2.4.1 修改端口号 2.4.2 匿名用户的权限 2.4.3 传输速率 三、NFS 3.1 工作原理 3.2 NFS软件介绍 3.3 NFS配…

数据结构===二叉树

文章目录 概要二叉树的概念分类存储遍历前序中序后序 小结 概要 简单写下二叉树都有哪些内容&#xff0c;这篇文章要写什么 二叉树的概念分类&#xff0c;都有哪些二叉树遍历 对一个数据结构&#xff0c;最先入手的都是定义&#xff0c;然后才会有哪些分类&#xff0c;对二叉…

C语言 | Leetcode C语言题解之第70题爬楼梯

题目&#xff1a; 题解&#xff1a; int climbStairs(int n) {double sqrt5 sqrt(5);double fibn pow((1 sqrt5) / 2, n 1) - pow((1 - sqrt5) / 2, n 1);return (int) round(fibn / sqrt5); }

机器人系统ros2-开发实践05-将静态坐标系广播到 tf2(Python)-定义机器人底座与其传感器或非移动部件之间的关系

发布静态变换对于定义机器人底座与其传感器或非移动部件之间的关系非常有用。例如&#xff0c;最容易推断激光扫描仪中心框架中的激光扫描测量结果。 1. 创建包 首先&#xff0c;我们将创建一个用于本教程和后续教程的包。调用的包learning_tf2_py将依赖于geometry_msgs、pyth…

【负载均衡式在线OJ项目day1】项目结构

一.功能 查看题目列表&#xff0c;在线编程&#xff0c;判题功能&#xff0c;即leetcode的部分功能 二.宏观结构 整个项目是BS模式&#xff0c;客户端是浏览器&#xff0c;和用户交互并向服务器发起请求。 服务端从功能上来说分为两个模块&#xff0c;第一个是OJServer&…

FFmpeg———encode_video(学习)

目录 前言源码函数最终效果 前言 encode_video:实现了对图片使用指定编码进行编码&#xff0c;生成可播放的视频流&#xff0c;编译时出现了一些错误&#xff0c;做了一些调整。 基本流程&#xff1a; 1、获取指定的编码器 2、编码器内存申请 3、编码器上下文内容参数设置 4、…

平平科技工作室-Python-超级玛丽

一.准备图片 放在文件夹取名为images 二.准备一些音频和文字格式 放在文件夹media 三.编写代码 import sys, os sys.path.append(os.getcwd()) # coding:UTF-8 import pygame,sys import os from pygame.locals import* import time pygame.init() # 设置一个长为1250,宽为…

03.配置监控一台服务器主机

配置监控一台服务器主机 安装zabbix-agent rpm -ivh https://mirror.tuna.tsinghua.edu.cn/zabbix/zabbix/4.0/rhel/7/x86_64/zabbix-agent-4.0.11-1.el7.x86_64.rpm配置zabbix-agent,配置的IP地址是zabbix-server的地址&#xff0c;因为要监控这台主机 vim /etc/zabbix/zab…

淘宝线上扭蛋机小程序:推动扭蛋机销量

扭蛋机作为一个新兴的娱乐消费模式&#xff0c;能够带给消费者“盲盒式”的消费乐趣&#xff0c;正在快速发展中。消费者通过投币、扫码支付等&#xff0c;在机器上扭下按钮就可以随机获得一个扭蛋商品&#xff0c;这些商品也包括动漫衍生周边、IP主题商品等&#xff0c;种类多…