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

news2024/9/25 7:19:55

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

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,它就会发起一次调度。

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CDGA|企业的不同阶段如何做数据治理?

随着数字化时代的到来&#xff0c;数据已经成为企业运营和决策的核心要素。因此&#xff0c;数据治理成为企业在不同阶段必须面对的重要课题。 本文将探讨企业在初创期、成长期、成熟期和转型期四个阶段应如何进行数据治理。 初创期&#xff1a;建立数据治理基础 在初创期&…

电子电器架构 --- 主机厂产线的两种刷写方法

电子电器架构 — 主机厂产线的两种刷写方法 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证…

Linux——基础IO2

引入 之前在Linux——基础IO(1)中我们讲的都是(进程打开的文件)被打开的文件 那些未被打开的文件呢&#xff1f; 大部分的文件都是没有被打开的文件&#xff0c;这些文件在哪保存&#xff1f;磁盘(SSD) OS要不要管理磁盘上的文件&#xff1f;(如何让OS快速定位一个文件) 要…

数据库基本概念Day01--基本概念

目录 一. 数据库的基本概念 1. 数据 2. 数据库 3. 数据库管理系统 4. 数据库应用程序 5. 数据库管理员 6. 最终用户 7. 数据库系统 一. 数据库的基本概念 1. 数据 数据&#xff08;Data&#xff09;是指&#xff1a;对客观事物进行描述并乐意鉴别的符号。这些符号可识别的、抽…

PN结击穿与电容效应分析

PN结是半导体器件中的一个基本结构&#xff0c;它由P型半导体和N型半导体紧密接触并相互结合在一起形成。P型半导体富含空穴&#xff08;正电荷载体&#xff09;&#xff0c;是通过掺入受主杂质原子得到的&#xff1b;而N型半导体富含自由电子&#xff08;负电荷载体&#xff0…

计算机嵌入式实习一定要掌握这些知识

在进行计算机嵌入式实习时&#xff0c;掌握以下这些知识是至关重要的&#xff01; 当你踏入嵌入式领域的大门&#xff0c;首先需要扎实掌握嵌入式系统基础&#xff0c;这是整个嵌入式开发的根基。同时&#xff0c;C 语言和 C编程也是必不可少的技能。C 语言以其高效的性能和与…

上传文件到 linux

一、mac 法一&#xff1a;scp 先进入mac的 Node_exporter文件&#xff08;要上传的文件&#xff09;目录下 输入scp -P 端口号 文件名 rootIP:/存放路径 scp -P 22 node_exporter-1.8.0.linux-amd64.tar.gz root192.***.2:/root 法二、 rz mac 安装 lrzsz&#xff0c;然后…

《OriginBot V2.0.2新功能体验::Froxglove》

0x00 官方通知&#xff1b; 最新消息&#xff1a;OriginBot V2.0.2版本正式发布&#xff0c;新增Webviz可视化&#xff0c;点击查看 0x01 Webviz基本概念&#xff1b; Webviz和rviz一样都是ROS开发中的可视化利器。区别在于rviz只能运行在ros环境中&#xff0c;而Webviz可能多…

《罪与罚》读后感

陀思妥耶夫斯基和列夫托尔斯泰是公认的俄国文学黄金时代的两座高峰&#xff0c;分别代表着俄国文学的“深度”和“广度”。列夫托尔斯泰的鸿篇巨著《复活》《安娜卡列尼娜》等等都已经拜读过&#xff0c;但陀思妥耶夫斯基的作品却一本也没有看过&#xff0c;实在是有点遗憾。这…

输电线路防鸟挡板,保电更护鸟

随着电力需求的不断增长&#xff0c;输电线路的安全和稳定运行变得愈发重要。然而&#xff0c;鸟类的活动却时常给输电线路带来隐患。他们经常在输电线路上筑巢或停歇&#xff0c;导致线路短路、绝缘子闪络等故障频发&#xff0c;而且一不小心触电的话就一命呜呼了~ 为了能够让…

初学python记录:力扣1652. 拆炸弹

题目&#xff1a; 你有一个炸弹需要拆除&#xff0c;时间紧迫&#xff01;你的情报员会给你一个长度为 n 的 循环 数组 code 以及一个密钥 k 。 为了获得正确的密码&#xff0c;你需要替换掉每一个数字。所有数字会 同时 被替换。 如果 k > 0 &#xff0c;将第 i 个数字用…

根据Docker部署nginx并且实现https

一、根据Docker部署nginx并且实现https 1.1、Docker中启用HTTPS有几个重要的原因 安全性&#xff1a;HTTPS通过加密数据传输&#xff0c;可以确保数据在传输过程中不被窃听或篡改。这对于保护敏感信息&#xff08;如用户凭据、支付信息等&#xff09;的传输至关重要。 数据完…

VGA项目:联合精简帧+双fifo+sobel算法 实现VGA显示(未完)

前言&#xff1a;该项目实际上是在很多基础的小练习上合成起来的&#xff0c;例如涉及到uart&#xff08;rs232&#xff09;的数据传输、双fifo流水线操作、VGA图像显示&#xff0c;本次内容在此基础上又增添了sobel算法&#xff0c;能实现图像的边沿监测并VGA显示。 文章目录…

使用第三方接口,查询实时快递单号

开通接口服务 全国快递物流查询-快递查询接口【最新版】_自动识别接口_快递查询_数据API-云市场-阿里云 RestTemplate: RestTemplate是Spring框架中的一个用于发送HTTP请求的客户端工具类。它提供了丰富的方法来发送GET、POST、PUT、DELETE等类型的HTTP请求&#xff0c;并支持…

Java内存区域详解

一、Java内存区域划分 1、分为运行时数据区域和本地内存 运行时数据区域是指 Java 虚拟机在运行 Java 程序时使用的不同内存区域&#xff0c;由虚拟机动态管理。本地内存是指由操作系统分配和管理的内存区域&#xff0c;它与虚拟机无关。在 Java 中&#xff0c;本地内存通常用…

生信网络学院|05月10日《全新DWG/DXF文件编辑工具——DraftSight Enterprise》

课程主题&#xff1a;全新DWG/DXF文件编辑工具——DraftSight Enterprise 课程时间&#xff1a;2024年05月10日 14:00-14:30 主讲人&#xff1a;任宇泽 生信科技 解决方案顾问 DraftSight介绍图形用户界面通用命令自定义块尺寸标注导入、导出和附加图纸集管理器Power Tools机…

大学生租房|基于Springboot+vue的大学生租房平台(源码+数据库+文档)

大学生租房平台 目录 基于Springbootvue的大学生租房平台 一、前言 二、系统设计 三、系统功能设计 1 管理员功能实现 5.1.1 房东管理 2 房东功能实现 5.2.1 信息审批管理 3 用户功能实现 5.3.1 房源信息 四、数据库设计 五、核心代码 六、论文参考 七、最新计…

半导体制冷片厂商正元泰达授权世强硬创代理,产品制冷率高寿命长

近日&#xff0c;为扩充旗下产品创新及供应平台世强硬创的半导体制冷片产品&#xff0c;满足硬科技企业不同层次的选择&#xff0c;世强先进&#xff08;深圳&#xff09;科技股份有限公司&#xff08;下称“世强先进”&#xff09;与正元泰达科技&#xff08;深圳&#xff09;…

PG 全页写

1.什么是全页写 修改一个块的时候&#xff0c;把块读到内存中&#xff0c;commit后,WAL写进程会触发写&#xff0c;把修改的块写到WAL日志文件&#xff0c;如果再往这个块中插入一条数据&#xff0c;数据缓冲区里面的块有两条数据了&#xff0c;再次commit后&#xff0c;PG会把…

最新AI实景自动无人直播软件招商加盟:引领直播行业的智能化未来;商家最新拓客工具

随着直播行业的迅速发展&#xff0c;AI实景自动无人直播软件正日益成为商家推广的新宠。该软件具备智能讲解、一键开播和智能回复功能&#xff0c;同时支持手机拍摄真实场景或自行搭建虚拟场景&#xff0c;给观众带来更好的观赏体验。在不断变化的市场竞争中&#xff0c;AI实景…