操作系统——FreeRTOS
文档教程
https://rtos.100ask.net/zh/FreeRTOS/DShanMCU-F103/chapter8.html
移植
创建任务
在FreeRTOS中,动态创建任务和静态创建任务是两种不同的任务创建方式,它们有一些重要的区别。
动态创建任务:
- 内存动态分配: 动态创建任务会在运行时动态分配内存。这意味着任务的堆栈和控制块等资源将在运行时从内存中分配,因此需要在运行时调用
xTaskCreate()
函数,并提供足够的堆栈和控制块内存。- 灵活性: 动态创建任务允许在运行时根据需要动态创建和删除任务。这使得可以根据系统的实际需求来动态调整任务的数量。
- 返回值:
xTaskCreate()
返回一个TaskHandle_t
类型的句柄,可以用于后续对任务的操作。静态创建任务:
- 预先分配内存: 静态创建任务需要在编译时就为任务分配足够的内存,这可以在任务创建之前通过定义一个
StaticTask_t
类型的变量和一个StackType_t
类型的数组来实现。- 效率: 静态创建任务相对于动态创建任务来说,效率可能会更高,因为它不需要在运行时进行内存分配和释放操作。
- 固定数量: 静态创建任务的数量是固定的,因为内存是在编译时分配的。这也意味着你需要提前知道系统中可能存在的任务数量。
如何选择:
- 如果你知道在编译时就可以确定系统中的任务数量,并且希望在运行时减少动态内存分配的开销,可以考虑使用静态创建任务。
- 如果你的系统需要在运行时动态地创建和删除任务,或者你无法提前确定任务的数量,那么动态创建任务可能是更好的选择。
任务优先级相同
在FreeRTOS中,如果有多个任务具有相同的优先级,任务的运行顺序将取决于FreeRTOS的任务调度策略。FreeRTOS支持两种任务调度策略:抢占式调度(Preemptive Scheduling)和协作式调度(Cooperative Scheduling)。这两种策略决定了当具有相同优先级的多个任务都准备好运行时,哪个任务将获得CPU执行的机会。
- 抢占式调度(Preemptive Scheduling): 在抢占式调度下,具有相同优先级的多个任务中,具有更高运行时间的任务将首先获得CPU执行的机会。如果一个任务具有更高的优先级,它可以随时抢占具有较低优先级的任务,并立即执行。这种策略确保了任务按照其优先级的重要性进行执行,高优先级任务可以随时中断低优先级任务。
- 协作式调度(Cooperative Scheduling): 在协作式调度下,具有相同优先级的多个任务必须自行让出CPU执行的机会。如果一个任务不主动让出CPU,那么其他具有相同优先级的任务可能无法执行,导致系统死锁或任务饥饿。在协作式调度中,任务必须显示调用任务切换函数,例如
taskYIELD()
,以便让出CPU。因此,如果您的FreeRTOS应用程序中有多个具有相同优先级的任务,并且您正在使用抢占式调度策略,那么具有更高运行时间的任务将首先获得CPU执行的机会。如果您使用协作式调度策略,则需要确保任务主动调用任务切换函数以让出CPU。在任务之间进行合适的任务切换是确保系统正常工作的关键。如果没有明确的任务切换,那么可能会导致任务无法正常执行或产生不可预测的行为。
同步与互斥
概念
什么叫同步?就是:哎哎哎,我正在用厕所,你等会。 什么叫互斥?就是:哎哎哎,我正在用厕所,你不能进来。
同步与互斥经常放在一起讲,是因为它们之的关系很大,“互斥”操作可以使用“同步”来实现。我“等”你用完厕所,我再用厕所。这不就是用“同步”来实现“互斥”吗?
再举一个例子。在团队活动里,同事A先写完报表,经理B才能拿去向领导汇报。经理B必须等同事A完成报表,AB之间有依赖,B必须放慢脚步,被称为同步。在团队活动中,同事A已经使用会议室了,经理B也想使用,即使经理B是领导,他也得等着,这就叫互斥。经理B跟同事A说:你用完会议室就提醒我。这就是使用"同步"来实现"互斥"。
实现同步、互斥的内核方法
任务通知(task notification)、队列(queue)、事件组(event group)、信号量(semaphoe)、互斥量(mutex)
内核对象 生产者 消费者 数据/状态 说明 队列 ALL ALL 数据:若干个数据 谁都可以往队列里扔数据, 谁都可以从队列里读数据 用来传递数据, 发送者、接收者无限制, 一个数据只能唤醒一个接收者 事件组 ALL ALL 多个位:或、与 谁都可以设置(生产)多个位, 谁都可以等待某个位、若干个位 用来传递事件, 可以是N个事件, 发送者、接受者无限制, 可以唤醒多个接收者:像广播 信号量 ALL ALL 数量:0~n 谁都可以增加一个数量, 谁都可消耗一个数量 用来维持资源的个数, 生产者、消费者无限制, 1个资源只能唤醒1个接收者 任务通知 ALL 只有我 数据、状态都可以传输, 使用任务通知时, 必须指定接受者 N对1的关系: 发送者无限制, 接收者只能是这个任务 互斥量 只能A开锁 A上锁 位:0、1 我上锁:1变为0, 只能由我开锁:0变为1 就像一个空厕所, 谁使用谁上锁, 也只能由他开锁
消息队列
在FreeRTOS中,队列是一种常用的通信和同步机制,用于在任务之间传递数据。队列实现了一个先进先出(FIFO)的数据结构,任务可以向队列发送数据或从队列接收数据。以下是FreeRTOS队列的基本原理:
队列的数据结构:
- 队列控制块(Queue Control Block): FreeRTOS中的每个队列都由一个队列控制块(
Queue_t
)来管理。队列控制块包含了队列的属性(如队列的长度、每个元素的大小等)以及指向队列中存储数据的缓冲区的指针。- 缓冲区: 队列的实际数据存储在一个缓冲区中。当任务发送数据到队列时,数据会被复制到缓冲区。当任务从队列接收数据时,数据会从缓冲区复制到任务的接收变量中。
队列的基本操作:
创建队列: 首先,使用
xQueueCreate()
函数创建一个队列。这个函数接受两个参数:队列的长度(能够容纳的元素数量)和每个元素的大小(以字节为单位)。
1
QueueHandle_t xQueue = xQueueCreate(queueLength, itemSize);
发送数据到队列: 使用
xQueueSend()
函数将数据发送到队列。这个函数接受队列句柄、待发送的数据指针和阻塞时间(如果队列已满)作为参数。
1
xQueueSend(xQueue, &data, portMAX_DELAY); // 阻塞直到队列可用
从队列接收数据: 使用
xQueueReceive()
函数从队列接收数据。这个函数接受队列句柄、接收数据的指针和阻塞时间(如果队列为空)作为参数。
1
xQueueReceive(xQueue, &receivedData, portMAX_DELAY); // 阻塞直到队列中有数据
队列的阻塞和非阻塞操作:
- 阻塞操作: 在阻塞模式下,如果队列已满(对于发送操作)或者队列为空(对于接收操作),任务将被阻塞,直到队列可用或者超时。
- 非阻塞操作: 如果在非阻塞模式下,如果队列已满或者队列为空,
xQueueSend()
和xQueueReceive()
函数会立即返回,而不是等待。队列的使用可以实现任务间的同步和通信,使得任务能够安全地交换数据而不需要使用全局变量或者其他共享资源。请根据您的应用需求选择合适的队列操作函数,并在发送和接收数据时考虑阻塞和非阻塞的需求。