ESP32 FreeRTOS学习总结

news/2024/10/30 19:34:47/

2023.5.11

1.Task

创建任务常用API:

任务函数描述
xTaskCreate()使用动态的方法创建一个任务
xTaskCreatePinnedToCore指定任务的运行核心(最后一个参数)
vTaskDelete(NULL)删除当前任务
BaseType_t xTaskCreate(TaskFunction_t pxTaskCode,                 // 任务函数名const char *const pcName,                  // 任务备注const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小void *const pvParameters,                  // 传入的参数UBaseType_t uxPriority,                    // 任务优先级TaskHandle_t *const pxCreatedTask);        // 任务句柄

任务间传参

任务间传参可以使用多种方式,常见的为:

  1. 使用全局变量:需要注意并发读写的问题,当有两个任务及以上对全局变量进行读写时,需要使用信号量或互斥量进行保护。
  2. 使用队列:需要注意队列的大小和数据类型的一致性,不需要使用信号量或互斥量进行保护队列的读写效率相比全局变量慢一些

使用全局变量进行传参时:

  • 传入参数:传递的为指针,且必须进行强制类型转换为空指针(void *)pt
  • 接收参数:把传递过来的空指针进行强制类型转换,转换为对应传输的类型指针
传递整数
#include <Arduino.h>int a = 1;void mytask(void *pt)
{int *b = (int *)pt;Serial.println(*b);while (1){}
}void setup()
{Serial.begin(115200);xTaskCreatePinnedToCore(mytask, "", 1024 * 3, (void *)&a, 1, NULL, 1);
}void loop() {}

输出结果为1

传递数组
#include <Arduino.h>int arr[] = {1, 2, 3};void mytask(void *pt)
{int *b = (int *)pt;int len = sizeof(arr) / sizeof(int); // 数组的长度,注意这里指针占4个字节,要用原数组名Serial.println(len);for (int i = 0; i < len; i++){Serial.print(*(b + i)); // 输出数组元素Serial.print(",");}while (1){}
}void setup()
{Serial.begin(115200);// 数组名代表数组元素的首地址,所以不需要&xTaskCreatePinnedToCore(mytask, "", 1024 * 3, (void *)arr, 1, NULL, 1);vTaskDelete(NULL);
}void loop() {}
传递结构体
#include <Arduino.h>typedef struct
{int a;int b;
} Mystruct;Mystruct test1 = {1, 2};void mytask(void *pt)
{Mystruct *test2 = (Mystruct *)pt; // 强制类型转换为结构体指针Serial.println(test2->a);Serial.println(test2->b);while (1) {}
}void setup()
{Serial.begin(115200);xTaskCreatePinnedToCore(mytask, "", 1024 * 3, (void *)&test1, 1, NULL, 1);vTaskDelete(NULL);
}void loop() {}
传递字符串
#include <Arduino.h>const char *str = "hello,world!";void mytask(void *pt)
{char *pstr = (char *)pt;Serial.println(pstr);  // 输出hello,worldvTaskDelete(NULL);
}void setup()
{Serial.begin(115200);xTaskCreatePinnedToCore(mytask, "", 1024 * 3, (void *)str, 1, NULL, 1);vTaskDelete(NULL);
}void loop() {}

任务的优先级

注意:中断任务的优先级永远高于任何任务的优先级。
在ESP32中,默认一共有25个优先级别,最低为0,最高为24。(可修改相关的配置函数进行修改优先级的数目超过25,但是不建议,级别越高,越占内存)。

  • 同优先级的任务:FreeRTOS将采用循环调度算法来运行他们,也就是交替执行同优先级的任务。每个任务执行一个时间片,然后将CPU时间片分配给另一个任务。
  • 优先级别高的任务先被创建和运行。
    任务的调度:
  • 在FreeRTOS中,vTaskDelay()vTaskDelayUntil()函数可以暂停当前任务的执行,等待一段时间后再继续执行。(让其他任务有机会执行)
  • taskYIELD()函数:立即将CPU时间片退让给同等级或更高优先级的任务,如果没有其他任务等待执行,则当前任务会立即继续执行。(简单的说,就是让其他任务执行)

任务的挂起和恢复

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VvEfLKT9-1683944157100)(images/1.png)]
任务的状态:running、ready、blocked、suspended(挂起,暂停)

  • running:运行状态,如果MCU只有一个内核,那么在任何给定时间内只能有一个任务处于运行状态。
  • ready:准备状态(任务刚被创建时,准备执行),不处于堵塞或挂起状态(没有获得CPU执行权限,等待执行状态),因为同等级或更高优先级的任务正在执行
  • blocked:使用了vTaskDelay()或delay()函数
  • suspended:挂起状态,挂起之后,任务被恢复才能继续执行
// API:
TaskHandle_t pxtask = NULL; // 创建任务的句柄
xTaskCreatePinnedToCore(task1, "", 1024 * 2, NULL, 1, &pxtask, 1);vTaskSuspend(pxtask);  // 挂起任务,任务不再执行
vTaskResume(pxtask);  // 恢复被挂起的任务,继续执行
vTaskSuspendAll();    // 挂起所有函数,挂起后不可以执行
vTaskResumeAll();     // 恢复所有挂起函数

任务的堆栈设置和调试

创建任务时,如果给任务分配的内存空间过小,会导致程序不断重启。如果分配的内存空间过多,会造成资源浪费。

// API:
ESP.getHeapSize() //  本程序Heap最大尺寸(空间总大小)
ESP.getFreeHeap() //  当前Free Heap最大尺寸(当前可用剩余空间大小)
uxTaskGetStackHighWaterMark(taskHandle) // 计算当前任务剩余多少内存

示例程序:

TaskHandle_t taskHandle; // 创建任务的句柄
void setup()
{Serial.begin(115200);xTaskCreatePinnedToCore(mytask, "", 1024*3 , NULL, 1, &taskHandle, 1);int waterMark = uxTaskGetStackHighWaterMark(taskHandle);Serial.print("Task Free Memory: "); // 任务剩余空间Serial.print(waterMark);vTaskDelete(NULL);
}

vTaskDelay()和delay()

一个tick的时间是由FreeRTOS的时钟节拍周期和时钟频率决定的,可以通过配置文件进行设置。默认情况下1 tick = 1ms

  • vTaskDelay()函数:以系统时钟节拍(tick)为单位进行延时,例如vTaskDelay(100)表示让任务暂停100个系统时钟节拍的时间。
  • delay()函数:是一个简单的延时函数,它通常在不需要多任务处理和系统保护的应用中使用。使用后会后边的程序都会被延迟执行。

vTaskDelayUntil()

vTaskDelayUntil函数比vTaskDelay函数定时精准。

// API
TickType_t xLastWakeTime = xTaskGetTickCount();  // 获取当前时间
const TickType_t xFrequency = 3000;   // 需要的时间间隔
vTaskDelayUntil(&xLastWakeTime, xFrequency);while(1){vTaskDelayUntil(&xLastWakeTime, xFrequency);// 下边为需要运行的函数
}
// 

示例程序:

#include <Arduino.h>
void mytask(void *pt)
{TickType_t xLastWakeTime = xTaskGetTickCount(); // 获取当前时间const TickType_t xFrequency = 1000;             // 需要的时间间隔while (1){vTaskDelayUntil(&xLastWakeTime, xFrequency);Serial.println(xTaskGetTickCount()); // 输出当前时间进行验证}
}void setup()
{Serial.begin(115200);xTaskCreatePinnedToCore(mytask, "", 1024 * 3, NULL, 1, NULL, 1);vTaskDelete(NULL);
}void loop() {}

2.Queue

2023.5.12

队列:先入先出(FIFO,first in first out)
使用方法:

  1. 创建队列:长度,尺寸(每个内存空间存储的数据大小)
  2. 发送数据到队列中
  3. 从队列中取数据
// portMAX_DELAY - 无限Block
// TickType_t timeOut = portMAX_DELAY;  // 无限等待,直到队列中有数据,或者等待数据有空位置可以存储新数据
TickType_t timeOut = 10;
xStatus = xQueueSend(Qhandle, &i,  timeOut);  // 往队列里发送数据,如果队列里内容是满的就等待10ms再次尝试发送
API描述
xQueueCreate()创建一个队列
xQueueSend()往队列里写数据
xQueueReceive从队列里读数据
uxQueueMessagesWaiting(队列句柄)返回值为队列中参数的个数,可用于接收数据时,先判断一下队列里是否有数据
// 创建一个队列
QueueHandle_t Qhandle = xQueueCreate(5, sizeof(int)); // 创建一个队列,长度为5,每个空间的大小为int

队列存储int数据

#include <Arduino.h>// 创建队列的句柄
QueueHandle_t Qhandle = xQueueCreate(5, sizeof(int));void send(void *pt)
{int i = 0;while (1){if (xQueueSend(Qhandle, &i, portMAX_DELAY) != pdPASS){Serial.println(F("队列数据发送失败"));}else{Serial.print(F("发送成功:"));Serial.println(i);}i++;if (i == 8)i = 0;vTaskDelay(1000);}
}void receive(void *pt)
{int j = 0; // 存储接收的队列数据while (1){if (xQueueReceive(Qhandle, &j, portMAX_DELAY) != pdPASS){Serial.println(F("接收失败"));}else{Serial.print(F("接收成功:"));Serial.println(j);}}
}void setup()
{Serial.begin(115200);xTaskCreatePinnedToCore(send, "", 1024 * 5, NULL, 1, NULL, 1);    // 发送数据xTaskCreatePinnedToCore(receive, "", 1024 * 5, NULL, 1, NULL, 1); // 接收数据vTaskDelete(NULL);
}void loop() {}

运行结果:

发送成功:0
接收成功:0
发送成功:1
接收成功:1
发送成功:2
接收成功:2
发送成功:3
接收成功:3

队列传递结构体(重点)

跟上面的案例类似,只是队列中每个元素类型为struct,并且发送和接收的数据存储也要设置为struct类型

#include <Arduino.h>// 创建一个结构体
typedef struct
{int a;int b;
} Mystruct;// 创建队列的句柄
QueueHandle_t Qhandle = xQueueCreate(5, sizeof(Mystruct));void send(void *pt)
{Mystruct struct1 = {1, 2};while (1){if (xQueueSend(Qhandle, &struct1, portMAX_DELAY) != pdPASS){Serial.println(F("队列数据发送失败"));}else{Serial.print(F("发送成功:"));struct1.a++;Serial.println(struct1.a);}vTaskDelay(1000);}
}void receive(void *pt)
{Mystruct struct2; // 接收结构体数据while (1){if (xQueueReceive(Qhandle, &struct2, portMAX_DELAY) != pdPASS){Serial.println(F("接收失败"));}else{Serial.print(F("接收成功:"));Serial.println(struct2.a);}}
}void setup()
{Serial.begin(115200);xTaskCreatePinnedToCore(send, "", 1024 * 5, NULL, 1, NULL, 1);    // 发送数据xTaskCreatePinnedToCore(receive, "", 1024 * 5, NULL, 1, NULL, 1); // 接收数据vTaskDelete(NULL);
}void loop() {}

运行结果:按照FIFO的规则进行数据的发送和接收

发送成功:2
接收成功:1
发送成功:3
接收成功:2
发送成功:4
接收成功:3

队列传递大型数据时

例如传递字符串。传递大型数据时,把指针对应的数据进行传递。

  • malloc()函数:在使用malloc开辟空间时,使用完一定要释放空间,如果不释放会造成内存泄漏。malloc()函数返回的实际是一个无类型指针,必须在其前面加上指针类型强制转换才可以使用。指针自身 = (指针类型*)malloc(sizeof(指针类型)*数据数量)
int *p = NULL;
p = (int *)malloc(sizeof(int)*10);// 使用完之后采用free()进行释放
free(p);
p = NULL; // 让其重新指向NULL

队列的多进单出:多个任务写,一个任务读


多个任务把数据写入一个队列,一个任务进行读。设置写入的任务级别为同级别,读任务的优先级别要比写任务高一级别。

  • 不推荐这种方式:容易造成系统工作混乱。最好的工作方式是一个队列只有一个写操作,可以有多个读操作,但是写操作只能有一个

队列集合(常用):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vphoMIcK-1683944157102)(images/2.png)]
多个队列,但是每个队列只有一个写操作,一个读操作(读取所有队列)
实现步骤:

  1. 创建队列集合的句柄:同时指定队列集合的总长度
  2. 将已创建的队列添加到集合中
  3. 创建一个句柄:从队列集合中获取有数据的队列
QueueHandle_t Qhandle1 = xQueueCreate(5, sizeof(int)); // 队列1
QueueHandle_t Qhandle2 = xQueueCreate(5, sizeof(int)); // 队列2QueueSetHandle_t QueueSet = xQueueCreateSet(10); // 队列集合句柄,10为队列的总长度xQueueAddToSet(Qhandle1, QueueSet); // 把队列1加入到队列集合中
xQueueAddToSet(Qhandle2, QueueSet); // 把队列2加入到队列集合中QueueSetMemberHandle_t QueueData = xQueueSelectFromSet(QueueSet, portMAX_DELAY); // 从队列集合中获取有数据的队列, QueueData为句柄

示例程序:这个程序编译不成功,还没有解决

#include <Arduino.h>QueueHandle_t Qhandle1 = xQueueCreate(5, sizeof(int)); // 队列1
QueueHandle_t Qhandle2 = xQueueCreate(5, sizeof(int)); // 队列2QueueSetHandle_t QueueSet = xQueueCreateSet(10); // 队列集合句柄xQueueAddToSet(Qhandle1, QueueSet); // 把队列1加入到队列集合中
xQueueAddToSet(Qhandle2, QueueSet); // 把队列2加入到队列集合中QueueSetMemberHandle_t QueueData = xQueueSelectFromSet(QueueSet, portMAX_DELAY); // 从队列集合中获取有数据的队列void send1(void *pt)
{int i = 1; // 任务1要发送的数据while (1){if (xQueueSend(Qhandle1, &i, portMAX_DELAY) != pdPASS){Serial.println("发送失败");}else{Serial.println("发送成功");}vTaskDelay(1000);}
}void send2(void *pt)
{int i = 2; // 任务2要发送的数据while (1){if (xQueueSend(Qhandle2, &i, portMAX_DELAY) != pdPASS){Serial.println("发送失败");}else{Serial.println("发送成功");}vTaskDelay(1000);}
}void receive(void *pt)
{int i; // 存储接收数据while (1){if (xQueueReceive(QueueData, &i, portMAX_DELAY) != pdPASS) // portMAX_DELAY,一直等待,直到队列中有数据{Serial.println("接收失败");}else{Serial.print("接收成功:");Serial.println(i);}// vTaskDelay(1000); // 采用了portMAX_DELAY,这里就不需要delay了}
}void setup()
{Serial.begin(9600);Serial.println("队列创建成功");xTaskCreatePinnedToCore(send1, "", 1024 * 5, NULL, 1, NULL, 1); // 两个相同的优先级别,轮流发送数据xTaskCreatePinnedToCore(send2, "", 1024 * 5, NULL, 1, NULL, 1);xTaskCreatePinnedToCore(receive, "", 1024 * 5, NULL, 2, NULL, 1); // 优先级别2,只要队列中有数据,就读
}void loop()
{
}

队列邮箱(常用):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4qyks8JB-1683944157103)(images/3.png)]
只有一个队列,一个任务写,多个任务读

// API
QueueHandle_t Mailbox = xQueueCreate(5, sizeof(int));  // 创建一个队列邮箱
xQueueOverwrite(); // 往队列中写数据
xQueuePeek(); // 从队列中读数据

示例程序:运行不成功

#include <Arduino.h>QueueHandle_t Mailbox = xQueueCreate(5, sizeof(int));void send(void *pt)
{int i = 1; // 任务1要发送的数据while (1){if (xQueueOverwrite(Mailbox, &i) != pdPASS){Serial.println("发送失败");}else{Serial.println("发送成功");i++;}vTaskDelay(1000);}
}void receive1(void *pt)
{int i; // 存储接收数据while (1){if (xQueuePeek(Mailbox, &i, 1000) != pdPASS) // portMAX_DELAY,一直等待,直到队列中有数据{Serial.println("接收失败");}else{Serial.print("接收成功:");Serial.println(i);}}
}
void receive2(void *pt)
{int i; // 存储接收数据while (1){if (xQueuePeek(Mailbox, &i,1000) != pdPASS) // portMAX_DELAY,一直等待,直到队列中有数据{Serial.println("接收失败");}else{Serial.print("接收成功:");Serial.println(i);}}
}void setup()
{Serial.begin(9600);Serial.println("队列创建成功");xTaskCreatePinnedToCore(send, "", 1024 * 5, NULL, 2, NULL, 1);xTaskCreatePinnedToCore(receive1, "", 1024 * 5, NULL, 2, NULL, 1);xTaskCreatePinnedToCore(receive2, "", 1024 * 5, NULL, 2, NULL, 1);
}void loop()
{
}

信号量

信号量分类:二进制信号量、计数信号量、互斥信号量。
信号量就像红绿灯一样,控制车辆的通行。
信号量常用于控制对共享资源的访问和任务同步。信号量对于控制共享资源访问的场景相当于一个上锁机制,代码只有获得这个锁的钥匙才能执行。

二进制信号量(常用)

二值信号量常用于互斥访问或同步,二值信号量和互斥信号量非常类似,但是互斥信号量拥有优先级继承机制,二值信号量没有优先级继承。

  • 二进制信号量可以用于一个任务控制另一个任务的运行与堵塞。
  • 二进制信号量表示只有两个值:0和1
  • 二值信号量相当于长度为1的队列
  • 二进制信号量只有两种状态:已触发和未触发,类似于一个开关。当一个任务等待一个已经触发的二进制信号量是,它会立即获得信号量,如果信号量未被触发,任务将被堵塞直到信号量被触发。
  • 可以避免资源冲突和死锁问题,提高系统的可靠性和效率
// API
SemaphoreHandle_t xHandler = xSemaphoreCreateBinary();  // 创建二进制信号量
xSemaphoreGive(xHandler); // 释放信号量
xSemaphoreTake(xHanlder, timeout); // 在指定时间内获取信号量,返回值为pdPASS, 或者pdFAIL

示例程序1:按键控制LED的亮灭(已验证)

#include <Arduino.h>SemaphoreHandle_t xHandler = xSemaphoreCreateBinary(); // 创建二进制信号量
TickType_t timeOut = 1000;void task1(void *pt)
{pinMode(23, OUTPUT);while (1){if (xSemaphoreTake(xHandler, timeOut) == pdTRUE){digitalWrite(23, !digitalRead(23));}}
}void task2(void *pt)
{pinMode(22, INPUT_PULLUP);while (1){if (digitalRead(22) == LOW){xSemaphoreGive(xHandler);vTaskDelay(120); // button debounce}}
}void setup()
{Serial.begin(9600);xTaskCreatePinnedToCore(task1, "", 1024 * 5, NULL, 1, NULL, 1); // 两个相同的优先级别,轮流发送数据xTaskCreatePinnedToCore(task2, "", 1024 * 5, NULL, 1, NULL, 1);
}void loop()
{
}

示例2:采用二进制信号量对任务进行管理,对全局变量进行读写

  • 该示例验证了,使用二进制信号量可以很好的控制任务的执行顺序。
#include <Arduino.h>
int a = 0;
SemaphoreHandle_t xHandler = xSemaphoreCreateBinary(); // 创建二进制信号量void task1(void *pt)
{while (1){xSemaphoreTake(xHandler, portMAX_DELAY); // 无限等待,直到获取信号量for (int i = 0; i < 10; i++){a++;printf("mytask1 a = %d\n", a);}xSemaphoreGive(xHandler); // 执行完之后,需要再次释放信号量vTaskDelay(1000);}
}void task2(void *pt)
{while (1){xSemaphoreTake(xHandler, portMAX_DELAY); // 无限等待,直到获取信号量for (int i = 0; i < 10; i++){a++;printf("mytask2 a = %d\n", a);}xSemaphoreGive(xHandler); // 执行完之后,需要再次释放信号量vTaskDelay(1000);}
}void setup()
{Serial.begin(115200);xSemaphoreGive(xHandler);                                       // 首先释放一次信号量,不然运行不了xTaskCreatePinnedToCore(task1, "", 1024 * 5, NULL, 1, NULL, 1); // task1先获取信号量,执行一次xTaskCreatePinnedToCore(task2, "", 1024 * 5, NULL, 1, NULL, 1); // 然后task2获取信号量,执行一次,task11再执行
}void loop()
{
}

运行结果:

  • 采用二进制信号量:

mytask1 a = 1
mytask1 a = 2
mytask1 a = 3
mytask1 a = 4
mytask1 a = 5
mytask1 a = 6
mytask1 a = 7
mytask1 a = 8
mytask1 a = 9
mytask1 a = 10
mytask2 a = 11
mytask2 a = 12
mytask2 a = 13
mytask2 a = 14
mytask2 a = 15
mytask2 a = 16
mytask2 a = 17
mytask2 a = 18
mytask2 a = 19
mytask2 a = 20
mytask1 a = 21

  • 不采用二进制信号量

ytask1 a = 1
mytask1 a = 2
mytask1 a = 3
mytask1 a = 4
mytask1 a = 6
mytask1 a = 7
mytask1 a = 8
mytask1 a = 9
mytask1 a = 10
mytask1 a = 11
mytask2 a = 5
mytask2 a = 12
mytask2 a = 13

计数信号量

与二进制不同的是,计数信号量可以有更多的状态。

  • 计数信号量相当于长度大于1的队列,同二值信号量一样,不需要关系队列中存储了什么数据,只需要关心队列是否为空即可。
  • 例如在一个停车场中有10个车位,车辆进入时,车位被占用(计数器减1),车开出去后(计数器加1),为0时表示没有可用的车位。
// API
uxSemaphoreGetCount( semphrHandle); // 获得计数型信号量的值
SemaphoreHandle_t semphrHandle = xSemaphoreCreateCounting(10,0);// 创建计数型信号量,参数1:最大值,参数2:初始值
xSemaphoreGive(semphrHandle); // 释放信号量
xSemaphoreTake(semphrHandle); // 获取信号量

使用场合:事件计数、资源管理

  1. 共享资源的访问控制:当多个任务需要共享同一个资源时,可以使用计数信号量来控制资源的访问。每个任务需要访问资源时,都需要获取一个计数信号量,如果计数信号量的值为0,则任务会被堵塞,直到其他任务释放资源并增加计数信号量的值。这种方式可以避免资源冲突和死锁等问题。
  2. 控制任务的执行顺序:有些情况下,需要控制任务的执行顺序,例如任务A必须在任务B执行完成之后才能执行。可以使用计数信号量来实现这种控制。任务B执行完成后,可以增加计数信号量的值,任务A等待计数信号量的值为1时,可以获取信号量并开始执行。
    示例1:模拟停车场的停车位
#include <Arduino.h>// 创建计数型信号量,参数1:最大值,参数2:初始值
SemaphoreHandle_t semphrHandle = xSemaphoreCreateCounting(5, 5); // 初值为5,代表初始有5个空车位void carintask(void *pt)
{int emptySpace = 0; // 空的停车位BaseType_t iResult;while (1){emptySpace = uxSemaphoreGetCount(semphrHandle);printf("emptySpace = %d\n", emptySpace);iResult = xSemaphoreTake(semphrHandle, 0); // 获取信号量if (iResult == pdPASS)printf("One car in\n");elseprintf("No Space\n");vTaskDelay(1000);}
}void caroutTask(void *pt)
{while (1){vTaskDelay(6000);xSemaphoreGive(semphrHandle); // 释放信号量printf("One car out\n");}
}void setup()
{Serial.begin(115200);xTaskCreatePinnedToCore(carintask, "", 1024 * 5, NULL, 1, NULL, 1);xTaskCreatePinnedToCore(caroutTask, "", 1024 * 5, NULL, 1, NULL, 1);
}void loop()
{
}

互斥信号量(常用)

与二进制信号量十分相似。Mutex的工作原理可以想象成共享的资源被锁在一个箱子里,只有一把钥匙,有钥匙的任务才能对共享资源进行访问。

  • 互斥量与二进制信号量的区别:优先级继承,在FreeRTOS中,当一个任务持有一个互斥量时,该任务对共享资源的访问是独占的,其他试图获取该互斥量的任务将被堵塞。当一个优先级更高的任务试图获取已经被持有的互斥量时,FreeRTOS会自动暂时提高持有互斥量任务的优先级别,使其具有与试图获取互斥量的任务相同的优先级别。这样可以确保高优先级别的任务在获取共享资源时能够及时执行,并避免低优先级别任务长时间持有共享资源。当持有互斥量的任务释放互斥量时,其优先级别将恢复到原始值,而不是保持被继承的优先级。这样可以确保任务在不需要共享资源时恢复其原始优先级,以避免低优先级任务一直持有高优先级任务的优先级,导致高优先级任务无法及时执行其他任务。
  • 注意:使用完立即释放钥匙
SemaphoreHandle_t xHandler = xSemaphoreCreateMutex();  // 创建一个Mutex互斥量
xSemaphoreGive(xHandler); // 释放信号量
xSemaphoreTake(xHanlder, timeout); // 在指定时间内获取信号量,返回值为pdPASS, 或者pdFAIL

http://www.ppmy.cn/news/65828.html

相关文章

day(2,3)-内核模块

内核模块上 主要内容 向内核添加新功能 内核模块基础代码讲解 内核模块多源文件编程 内核模块信息宏 一、向内核添加新功能 1.1 静态加载法&#xff1a; 即新功能源码与内核其它代码一起编译进uImage文件内 Kconfig是make menuconfig的界面配置文件 1.2动态加载法&am…

18 KVM管理虚拟机-虚拟机生命周期总体介绍

文章目录 18 KVM管理虚拟机-虚拟机生命周期总体介绍18.1 概述18.2 虚拟机状态18.3 状态转换18.4 虚拟机标识 18 KVM管理虚拟机-虚拟机生命周期总体介绍 18.1 概述 为了更好地利用硬件资源&#xff0c;降低成本&#xff0c;用户需要合理地管理虚拟机。本节介绍虚拟机生命周期过…

tpm2-tools源码分析之tpm2_unseal.c(2)

接前一篇文章&#xff1a;tpm2-tools源码分析之tpm2_unseal.c&#xff08;1&#xff09; 本文对tpm2_unseal.c中的tpm2_tool_onstart函数进行详细解析。 先再次贴出该函数源码&#xff1a; static bool tpm2_tool_onstart(tpm2_options **opts) {static const struct option …

算法修炼之练气篇——练气十六层

博主&#xff1a;命运之光 专栏&#xff1a;算法修炼之练气篇 前言&#xff1a;每天练习五道题&#xff0c;炼气篇大概会练习200道题左右&#xff0c;题目有C语言网上的题&#xff0c;也有洛谷上面的题&#xff0c;题目简单适合新手入门。&#xff08;代码都是命运之光自己写的…

《花雕学AI》新版必应 Bing 登场:轻松注册,一站式搜索、聊天与绘画应有尽有

引言&#xff1a; 你是否曾经在网上搜索信息时感到困惑或沮丧&#xff1f;你是否曾经想要在网上创造一些有趣或有用的内容&#xff0c;却不知道从何开始&#xff1f;你是否曾经想要用文字描述一个图像&#xff0c;却无法找到合适的图片&#xff1f;如果你的答案是肯定的&#x…

【JS】1680- 重学 JavaScript API - Beacon API

❝ 前期回顾&#xff1a;1.Page Visibility API 2.Broadcast Channel API ❞ 1. 什么是 Beacon API 1.1 概念介绍 Beacon API 是 HTML5 提供的一种新的浏览器 API&#xff0c;可以用于在浏览器后台异步地发送数据&#xff0c;而不影响当前页面的加载和性能。通过 Beacon API&am…

安全访问服务边缘 (SASE) 技术的优缺点及工作原理

随着企业向云迁移&#xff0c;移动性成为常态&#xff0c;网络和安全解决方案必须相应地发展。安全访问服务边缘 &#xff08;SASE&#xff09; 在此处进入图片。SASE 是一个新兴的技术类别&#xff0c;旨在提供特定的网络安全功能。 安全访问服务边缘 &#xff08;SASE&#…

qemu-基础篇——ARM 链接过程分析(六)

文章目录 ARM 链接过程分析源文件global_bss_file.cglobal_data_fle.cglobal_function_file.cglobal_rodata_file.cmain.c 链接文件 link.lds编译命令及反汇编命令解析 .o 文件global_bss_file.oglobal_data_fle.oglobal_function_file.oglobal_rodata_file.omain.o 链接观察链…