【FreeRTOS】FreeRTOS学习笔记 ---- 堆和栈,第1个FreeRTOS程序,创建任务函数及任务管理

news2024/11/17 5:41:18

🍀作者阿润菜菜


目录

  • 一、通过故事介绍FreeRTOS
    • 1.什么是FreeRTOS?
    • 2.FreeRTOS能做什么?
  • 二、如何使用FreeRTOS? --- 第1个FreeRTOS程序
  • 三、FreeRTOS的堆和栈
    • 1.堆和栈的概念
    • 2.堆和栈的分配方式
    • 3.堆和栈的溢出检测
  • 四、创建任务函数及任务管理


一、通过故事介绍FreeRTOS

假如你是一位母亲是不是会经常遇到这样的情况:你要一边给小孩喂饭,一边加班跟同事微信交流,但是你无法一心多用,只能不停地切换注意力,导致效率低下,还容易出错?
在这里插入图片描述

如果你是一个软件开发者,你可能会想:有没有什么办法可以让我像电脑一样,可以同时运行多个任务,而不影响彼此的执行呢?

答案是:有!那就是使用操作系统(OS)。

操作系统是一种软件,它可以管理和调度多个程序或者任务(task)的运行,让它们看起来像是同时执行一样。操作系统可以根据任务的优先级、时间片、事件等因素来决定哪个任务应该先运行,哪个任务应该后运行,或者哪个任务应该暂停或者继续运行。

操作系统有很多种类,例如我们常用的Windows、Linux、Mac OS等,它们被称为通用操作系统(general-purpose OS),因为它们可以运行在各种类型的计算机上,并且支持各种类型的应用程序。

但是在一些专用的电子设备中,例如电梯、汽车、飞机、医疗仪器等,通用操作系统就不太适合了。因为这些设备通常使用的是微控制器(microcontroller)或者小型微处理器(microprocessor),它们的内存和处理能力都很有限。而且这些设备对于实时性(real-time)有很高的要求,也就是说它们必须在规定的时间内完成指定的任务,否则就会造成严重的后果。

例如,在电梯系统中,你按住开门键时如果没有即刻反应,即使只是慢个1秒,也会夹住人。
在这里插入图片描述

为了满足这些设备的需求,就出现了一种特殊的操作系统:实时操作系统(real-time operating system,RTOS)。RTOS是一种为实时应用设计的操作系统,它可以在有限的资源下保证任务的及时响应和正确执行。

今天我们要介绍的就是一种开源的、免费的、广泛使用的RTOS:FreeRTOS

1.什么是FreeRTOS?

FreeRTOS是一个实时操作系统内核(kernel),它可以在多种微控制器和处理器上运行,提供了丰富的任务调度、同步、通信、内存管理等功能。FreeRTOS是开源的,可以免费使用,也可以根据需要进行修改和定制。
在这里插入图片描述

那FreeRTOS和Linux的区别是什么?

1.FreeRTOS中没有进程和线程的区分:
FreeRTOS中的任务(Task)和线程(Thread)是相同的概念,每个任务就是一个线程,有着自己的一个程序函数。FreeRTOS可以创建、删除、挂起、恢复、优先级设置等多个任务,任务之间可以通过任务调度器根据优先级进行切换。

2.FreeRTOS和Linux都是操作系统,但是有很多区别,主要有以下几点:

  • FreeRTOS是一个实时操作系统(RTOS),它要求快速地处理任务,保证实时性和可靠性。Linux是一个通用操作系统(GPOS),它要求提供丰富的功能和服务,保证用户体验和兼容性。
  • FreeRTOS是一个迷你的操作系统内核,只包含了基本的功能,如任务管理、时间管理、信号量、消息队列、内存管理等。Linux是一个完整的操作系统,包含了内核和用户空间,有很多组件和模块,如文件系统、网络协议栈、设备驱动、图形界面、shell等。
  • FreeRTOS可以在资源有限的微控制器中运行,占用的内存和存储空间很小。Linux需要较多的资源,一般运行在处理器性能较强的平台上。
  • FreeRTOS主要用于嵌入式领域,如工业控制、物联网、智能家居等。Linux主要用于桌面、服务器、移动设备等领域

2.FreeRTOS能做什么?

  1. FreeRTOS可以让你在微控制器或者小型微处理器上实现多任务的并发执行,从而提高你的系统的性能和效率。

  2. FreeRTOS可以让你根据任务的优先级、时间片、事件等因素来灵活地调度任务的运行,从而保证你的系统的实时性和正确性。

  3. FreeRTOS可以让你使用队列、信号量、互斥锁、事件组等机制来实现任务之间的同步和通信,从而保证你的系统的稳定性和可靠性。

  4. FreeRTOS可以让你使用静态或者动态的方式来分配任务的内存空间,从而保证你的系统的灵活性和可扩展性。

  5. FreeRTOS可以让你使用各种工具和方法来检测和调试你的系统,例如栈溢出检测、断言、跟踪分析等,从而保证你的系统的质量和安全性。

二、如何使用FreeRTOS? — 第1个FreeRTOS程序

要使用FreeRTOS,首先你需要选择一个合适的硬件平台和软件环境,例如微控制器型号、编译器类型、开发板规格等。然后你需要下载FreeRTOS的源代码,并根据你的平台选择相应的移植文件(port file)。移植文件是一些针对不同平台的特殊代码,用来实现一些基本的功能,例如时钟配置、中断处理、任务切换等。接着你需要配置FreeRTOSConfig.h文件,这个文件是一个头文件,用来设置一些FreeRTOS相关的宏定义,例如任务数量、堆大小、调试选项等。最后你就可以开始编写你自己的应用程序了。

要创建一个FreeRTOS程序,首先需要包含FreeRTOS的头文件,并定义一些必要的宏和变量。例如:

#include "FreeRTOS.h"
#include "task.h"

#define mainDELAY_LOOP_COUNT 100000UL
static void prvTask1(void *pvParameters);
static void prvTask2(void *pvParameters);

然后需要创建任务,并启动调度器。例如:

int main(void)
{
    xTaskCreate(prvTask1, "Task 1", 1000, NULL, 1, NULL);
    xTaskCreate(prvTask2, "Task 2", 1000, NULL, 1, NULL);
    vTaskStartScheduler();
    return 0;
}

这里创建了两个任务,分别执行prvTask1和prvTask2函数,每个任务分配了1000个字节的栈空间,优先级都为1,没有传递任何参数。然后调用vTaskStartScheduler函数启动调度器,这个函数会创建一个空闲任务,并开始按照优先级和时间片轮转调度各个就绪状态的任务。

每个任务都是一个无限循环的函数,可以在其中执行一些操作,并调用一些FreeRTOS提供的API函数。例如:

static void prvTask1(void *pvParameters)
{
    unsigned long ul;
    for(;;)
    {
        for(ul = 0; ul < mainDELAY_LOOP_COUNT; ul++)
        {
            // do something
        }
        vTaskDelay(250 / portTICK_PERIOD_MS);
        // toggle LED 1
    }
}

static void prvTask2(void *pvParameters)
{
    unsigned long ul;
    for(;;)
    {
        for(ul = 0; ul < mainDELAY_LOOP_COUNT; ul++)
        {
            // do something
        }
        vTaskDelay(250 / portTICK_PERIOD_MS);
        // toggle LED 2
    }
}

这里每个任务都执行了一个延时循环,并在每次循环后调用vTaskDelay函数挂起自己一段时间,然后切换到另一个任务。vTaskDelay函数的参数是以系统时钟节拍为单位的延时时间,portTICK_PERIOD_MS是一个宏,表示每个节拍的毫秒数,这个值取决于系统时钟的频率和配置。在每次延时结束后,每个任务都会切换一个LED的状态,以此来观察任务的运行情况。

这个程序可以在不同的硬件平台上运行,只需要根据不同的平台选择合适的FreeRTOS移植文件,并编写相应的硬件初始化和LED控制代码。FreeRTOS提供了多种平台的移植文件,可以在官网或者GitHub上找到


三、FreeRTOS的堆和栈

1.堆和栈的概念

  • 堆(heap)是一块动态分配的内存区域,可以在程序运行时根据需要申请和释放。
  • 栈(stack)是一块静态分配的内存区域,用来存储函数调用时的局部变量、参数、返回地址等。
  • FreeRTOS中每个任务都有自己的栈空间,用来保存任务的上下文(context),即任务运行时的寄存器值、状态等。
  • FreeRTOS中还有一个任务控制块(TCB),用来保存任务的基本信息,例如任务名、优先级、状态、堆栈指针等。

2.堆和栈的分配方式

  • FreeRTOS提供了多种堆管理方案,可以在FreeRTOSConfig.h文件中选择使用哪种方案。
  • heap_1:只支持静态分配,即在程序开始时就分配好所有任务的TCB和栈空间,不支持动态创建和删除任务。
  • heap_2:支持动态分配,即在程序运行时可以创建和删除任务,但不支持内存回收,即删除任务后不会释放其占用的内存空间。
  • heap_3:支持动态分配和内存回收,使用标准C库的malloc和free函数来管理堆空间,但可能存在内存碎片问题,即堆空间被分割成很多小块,导致无法分配足够大的连续空间。
  • heap_4:支持动态分配和内存回收,并且可以合并相邻的空闲块,减少内存碎片问题,但需要更多的代码空间和执行时间。
  • heap_5:在heap_4的基础上增加了多个堆区域的支持,可以将不同大小或者不同属性的内存区域作为堆来使用,提高了内存利用率。

3.堆和栈的溢出检测

  • 堆溢出(heap overflow)是指申请的堆空间超过了可用的堆空间,导致无法分配成功。
  • 栈溢出(stack overflow)是指任务的栈空间不足以存储所需的数据,导致覆盖了其他内存区域。
  • FreeRTOS提供了多种栈溢出检测方法,可以在FreeRTOSConfig.h文件中选择使用哪种方法。
  • method 1:在栈顶和栈底设置哨兵值(sentinel value),并在每次任务切换时检查哨兵值是否被破坏。这种方法简单易用,但可能存在误报或者漏报的情况。
  • method 2:使用MPU(内存保护单元)来保护栈区域不被非法访问。这种方法需要硬件支持,并且需要使用特殊的函数来创建任务。这种方法可以精确地检测到栈溢出,并且可以防止栈溢出造成的系统崩溃。
  • method 3:结合method 1和method 2,既使用哨兵值又使用MPU来检测栈溢出。这种方法可以提高检测的准确性和安全性,但也增加了复杂度和开销。

四、创建任务函数及任务管理

FreeRTOS提供了多种创建任务的函数,除了xTaskCreate之外,还有xTaskCreateStatic、xTaskCreateRestricted、xTaskCreateRestrictedStatic等。这些函数的区别主要在于是否使用静态分配或者是否使用MPU。静态分配意味着TCB和栈空间都是由用户提供的,而不是从堆中分配的。MPU意味着可以为每个任务设置不同的内存访问权限,以提高系统的安全性和稳定性。

创建任务的函数都有一些共同的参数,例如:

  • pvTaskCode:指向任务函数的指针。
  • pcName:指向任务名字的指针。
  • usStackDepth:指定任务栈的大小,以字为单位。
  • pvParameters:指向传递给任务函数的参数的指针。
  • uxPriority:指定任务的优先级,数值越大优先级越高。
  • pxCreatedTask:指向接收任务句柄的变量的指针。

例如,下面的代码使用xTaskCreate函数创建了一个名为"Hello",优先级为1,栈大小为1000字节,没有传递任何参数的任务,并将其句柄存储在xHandle变量中。

TaskHandle_t xHandle = NULL;
xTaskCreate(vTaskHello, "Hello", 1000, NULL, 1, &xHandle);

如果要使用静态分配,可以使用xTaskCreateStatic函数,并提供两个额外的参数:

  • puxStackBuffer:指向分配给任务栈的内存区域的指针。
  • pxTaskBuffer:指向分配给TCB的内存区域的指针。

例如,下面的代码使用xTaskCreateStatic函数创建了一个名为"World",优先级为2,栈大小为1000字节,没有传递任何参数的任务,并将其句柄存储在xHandle变量中。同时,它使用了两个静态数组来分配任务栈和TCB所需的内存空间。

#define STACK_SIZE 1000
static StackType_t xStack[STACK_SIZE];
static StaticTask_t xTaskBuffer;
TaskHandle_t xHandle = NULL;
xHandle = xTaskCreateStatic(vTaskWorld, "World", STACK_SIZE, NULL, 2, xStack, &xTaskBuffer);

如果要使用MPU,可以使用xTaskCreateRestricted或者xTaskCreateRestrictedStatic函数,并提供一个结构体类型的参数:

  • xTaskParameters:包含了创建任务所需的所有信息和MPU设置。

例如,下面的代码使用xTaskCreateRestricted函数创建了一个名为"MPU",优先级为3,栈大小为1000字节,没有传递任何参数的任务,并将其句柄存储在xHandle变量中。同时,它使用了一个结构体变量来设置任务的MPU属性,例如允许访问RAM区域和FLASH区域等。

#define STACK_SIZE 1000
static const TaskParameters_t xTaskParameters = {
    .pvTaskCode = vTaskMPU,
    .pcName = "MPU",
    .usStackDepth = STACK_SIZE,
    .pvParameters = NULL,
    .uxPriority = 3,
    .puxStackBuffer = NULL,
    .xRegions = {
        {RAM_START_ADDRESS, RAM_LENGTH, portMPU_REGION_READ_WRITE},
        {FLASH_START_ADDRESS, FLASH_LENGTH, portMPU_REGION_READ_ONLY},
        {0, 0, 0}
    }
};
TaskHandle_t xHandle = NULL;
xHandle = xTaskCreateRestricted(&xTaskParameters, NULL);

创建任务的函数都会返回一个句柄(handle),这是一个指向TCB的指针,可以用来对任务进行管理。例如,可以使用vTaskDelete函数删除一个任务,可以使用vTaskSuspend和vTaskResume函数挂起和恢复一个任务,可以使用vTaskPrioritySet函数改变一个任务的优先级,可以使用xTaskNotify和xTaskNotifyWait函数给一个任务发送和接收通知等。

例如,下面的代码使用vTaskDelete函数删除了之前创建的"Hello"任务,并使用vTaskPrioritySet函数将"World"任务的优先级提高到4。

vTaskDelete(xHandle);
vTaskPrioritySet(xHandle, 4);

除了使用句柄来管理任务之外,还可以使用任务名或者任务标识符(task number)。每个任务都有一个唯一的标识符,可以使用uxTaskGetTaskNumber和vTaskSetTaskNumber函数获取和设置。每个任务也可以有一个最多8个字符的名字,可以使用pcTaskGetName函数获取。这些信息可以用来在调试或者跟踪时识别不同的任务。

例如,下面的代码使用uxTaskGetTaskNumber函数获取了当前任务的标识符,并使用pcTaskGetName函数获取了当前任务的名字,并打印出来。

UBaseType_t xTaskNumber = uxTaskGetTaskNumber(NULL);
char *pcTaskName = pcTaskGetName(NULL);
printf("The task number is %d, the task name is %s\n", xTaskNumber, pcTaskName);

另外我们还可以使用一些其他的任务管理功能,比如:
我们可以使用vTaskDelayUntil函数实现固定频率的周期性任务。这个函数需要传递一个指向上次唤醒时间的变量的指针,和一个以系统时钟节拍为单位的周期时间。这个函数会根据上次唤醒时间和周期时间计算出下次唤醒时间,并挂起当前任务直到下次唤醒时间到达。这样可以避免累积误差,保证任务按照固定频率执行。


本节完

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

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

相关文章

【创建一个螺旋状的相机轨迹并可视化该轨迹以及每个点的姿态】

文章目录 焦点的作用static const Eigen::Vector3d b_cam_z(0, 0, 1);static const Eigen::Matrix3d R_w_base = (Eigen::Matrix3d() << 0, 0, -1, 1, 0, 0, 0, -1, 0).finished()import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import …

canvas详解03-绘制图像和视频

canvas 更有意思的一项特性就是图像操作能力。可以用于动态的图像合成或者作为图形的背景,以及游戏界面(Sprites)等等。浏览器支持的任意格式的外部图片都可以使用,比如 PNG、GIF 或者 JPEG。你甚至可以将同一个页面中其他 canvas 元素生成的图片作为图片源。 引入图像到 …

canvas详解04-绘制文字

绘制文本 canvas 提供了两种方法来渲染文本: fillText(text, x, y [, maxWidth]) 在指定的 (x,y) 位置填充指定的文本,绘制的最大宽度是可选的。 strokeText(text, x, y [, maxWidth]) 在指定的 (x,y) 位置绘制文本边框,绘制的最大宽度是可选的。 #一个填充文本的示例…

Vue电商项目--购物车操作

购物车动态展示数据 但是这个数据的格式不完美 一层套一层 重新对vuex进行存储处理 这里接口写错 这一块&#xff0c;我们通过计算属性加工一下&#xff0c;重新渲染到页面上 在这里我们考虑一个问题&#xff0c;那就是将计算出来的总值计算到页面上 这里还有一个问题&#x…

qt读写xml文件

Qt使用XML模块&#xff0c;在.pro文件中添加 QT xml Qt 提供了三种读写 XML 文档的方法&#xff1a; QXmlStreamReader/QXmlStreamWriter&#xff1a; 一种快速的基于流的方式访问良格式 XML 文档&#xff0c;特别适合于实现一次解析器&#xff08;所谓“一次解析器”&…

前端vue入门(纯代码)13

【13.Vue的消息订阅与发布】 备注&#xff1a;全局事件总线用的更多些&#xff0c;消息订阅与发布只需了解即可。【注意点1】&#xff1a;由于“消息订阅与发布”可依赖的第三方库太多了&#xff0c;这里使用pubsub-js 问题&#xff1a;“全局事件总线”和“消息订阅与发布”都…

看完这篇 教你玩转渗透测试靶机vulnhub—Emplre: Breakout

Vulnhub靶机Emplre: Breakout渗透测试详解 Vulnhub靶机介绍&#xff1a;Vulnhub靶机下载&#xff1a;Vulnhub靶机安装&#xff1a;Vulnhub靶机漏洞详解&#xff1a;①&#xff1a;信息收集&#xff1a;②&#xff1a;登入后台&#xff1a;③&#xff1a;GetShell&#xff1a;④…

oracle操作xml格式数据

新建一张用来测试的表 -- Create table create table XMLTEST (id NUMBER,content VARCHAR2(4000) );往表中插入数据 insert into XMLTEST (id, content) values (1, <root><app><id>1</id><name>张三</name><age>18</age…

《网络安全0-100》经典访问控制策略

1经典访问控制策略 1.1自主访问控制 允许用户自己对客体将已有的权限赋予给其他主体&#xff0c;也可以撤销自己赋予给其他主体的权限。 矩阵结构分为三个主要的表&#xff1a; 访问控制矩阵 访问控制列表 权能表 矩阵的局限性&#xff1a; 大小为主体数量客体数量&…

【FPGA】Verilog:时序电路设计 | 自循环移位寄存器 | 环形计数 | 扭环计数 | 约翰逊计数器

前言&#xff1a;本章内容主要是演示Vivado下利用Verilog语言进行电路设计、仿真、综合和下载 示例&#xff1a;计数器 ​​ 功能特性&#xff1a; 采用 Xilinx Artix-7 XC7A35T芯片 配置方式&#xff1a;USB-JTAG/SPI Flash 高达100MHz 的内部时钟速度 存储器&#xff1a;2Mb…

Bresenham直线算法

文章目录 1.Bresenham直线算法1.1 算法流程1.2 Bresenham算法实现1.3matlab中应用1.4 算法优势1.5 对比以往方法的改进和优化1.6 算法改进和缺陷 2.国内外研究现状3.个人感想及算法改进 1.Bresenham直线算法 Bresenham直线算法是一种用于将两点之间的线段绘制在屏幕上的算法。…

什么是MLOps?为什么要使用MLOps进行机器学习实践

随着数字化和计算能力的发展&#xff0c;机器学习&#xff08;Machine Learning&#xff09;技术在提高企业生产力方面所涌现的潜力越来越被大家所重视&#xff0c;然而很多机器学习的模型及应用在实际的生产环境并未达到预期&#xff0c;大量的ML项目被证明是失败的。从机器学…

【Red Hat7.9安装Oracle11g】---调用图形化界面的几种方式

【Red Hat7.9安装Oracle11g】---调用图形化界面的几种方式 &#x1f53b; 一、续上一篇[【Red Hat 7.9---详细安装Oracle 11g---图形化界面方式】](https://blog.csdn.net/qq_41840843/article/details/131198718?spm1001.2014.3001.5501)⛳ 1.1 前言⛳ 1.2 方式一、使用Xmana…

MIT 6.S081 Lab Five

MIT 6.S081 Lab Five 引言xv6 lazy page allocationEliminate allocation from sbrk() (easy)代码解析 Lazy allocation (moderate)代码解析 Lazytests and Usertests (moderate)代码解析 可选的挑战练习 引言 本文为 MIT 6.S081 2020 操作系统 实验五解析。 MIT 6.S081课程前…

从C语言到C++_21(模板进阶+array)+相关笔试题

目录 1. 非类型模板参数 1.1 array 1.2 非类型模板参数的使用场景 1.3 注意事项 2. 模板的特化 2.1 函数模板的特化 2.2 类模板的特化 2.3 全特化和偏特化(半特化) 3. 模板关于分离编译 4. 模板优缺点 5. 模板相关笔试题 本章完。 1. 非类型模板参数 对于函数模板…

dhtmlx Event Calendar JavaScript new Crack

DHTMLX Event Calendar可帮助您开发类似 Google 的 JavaScript 事件日历&#xff0c;以高效地组织约会。用户可以通过拖放来管理事件&#xff0c;并以六种不同的模式显示它们。 JavaScript 事件日历功能 轻的 简单的 JavaScript API 六个默认视图&#xff1a;日、周、月、年、议…

Java并发编程学习16-线程池的使用(中)

线程池的使用&#xff08;中&#xff09; 引言1. 配置 ThreadPoolExecutor1.1 线程的创建与销毁1.2 管理队列任务1.3 饱和策略1.4 线程工厂1.5 定制 ThreadPoolExecutor 2. 扩展 ThreadPoolExecutor总结 引言 上篇分析了在使用任务执行框架时需要注意的各种情况&#xff0c;并…

死锁的成因以及解决方案(简析)

目录 一.为什么会产生死锁? 二.死锁产生的几个场景 一个线程一把锁的情况 关于可重入和不可重入锁的简单举例 两个线程两把锁的情况 多线程多把锁 如何解决死锁 一.为什么会产生死锁? 简单来说,就是进程加锁之后,没有被解锁而处于一直等待的状态 二.死锁产生的几个场景…

深入理解深度学习——BERT(Bidirectional Encoder Representations from Transformers):BERT的结构

分类目录&#xff1a;《深入理解深度学习》总目录 相关文章&#xff1a; BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;&#xff1a;基础知识 BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09…

软件架构模式—分层架构

这是软件架构模式博客系列第 2 章&#xff0c;我们将讨论分层架构模式。 分层架构模式是一种n层模式&#xff0c;其中组件按照水平层次进行组织。这是设计大多数软件的传统方法&#xff0c;旨在实现自我独立。这意味着所有组件之间相互连接&#xff0c;但彼此之间不相互依赖。…