ESP32 FreeRTOS学习总结

news2024/11/23 6:23:32

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,world
  vTaskDelete(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-uSkjjy2F-1683896646565)(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-YDqTMbew-1683896646567)(images/2.png)]
多个队列,但是每个队列只有一个写操作,一个读操作(读取所有队列)
实现步骤:

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

QueueSetHandle_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)); // 队列2

QueueSetHandle_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-yOpA8zwk-1683896646567)(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()
{
}

信号量

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

二进制信号量(常用)

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

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

示例程序:按键控制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()
{
}

计数信号量

互斥信号量(常用)

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

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

相关文章

【HBase】架构

文章目录 整体架构Master负载均衡器元数据管理器预写日志处理器 Region ServerZookeeperHDFS Master架构Meta 表格 RegionServer 架构MemStoreWALBlockCache 读写流程HFile结构写流程读操作 整体架构 Master 实现类为 HMaster。 负责监控集群中所有的 RegionServer 实例。 &…

在Fedora-Workstation-Live-x86_64-36-1.5中使用佳能喷墨打印机ip2780

在Fedora-Workstation-Live-x86_64-36-1.5中使用佳能喷墨打印机ip2780 操作系统是64位的Fedora-Workstation-Live-x86_64-36-1.5.iso&#xff0c;实物打印机是佳能ip2780&#xff0c;USB接口 应用程序——其它——设置——打印机——解锁——输入root密码——将打印机USB插入电…

Redis数据结构——动态字符串、Dict、ZipList

一、Redis数据结构-动态字符串 我们都知道Redis中保存的Key是字符串&#xff0c;value往往是字符串或者字符串的集合。可见字符串是Redis中最常用的一种数据结构。 不过Redis没有直接使用C语言中的字符串&#xff0c;因为C语言字符串存在很多问题&#xff1a; 获取字符串长度…

智慧产业城彰显中国智造魅力,中联重科踏出“走上去”的关键一步

5月11日至14日&#xff0c;中联重科在长沙举行了“科技献礼新时代”系列活动。 借着活动&#xff0c;松果财经实地探访了全球规模最大、品种最全的工程机械产业基地&#xff0c;领略了前沿技术赋能先进制造的魅力。 作为湖南省“一号工程”和“十大重点项目之首”&#xff0c…

好家伙,又一份牛逼笔记面世了...

最近网传的一些裁员的消息&#xff0c;搞的人心惶惶。已经拿到大厂offer的码友来问我&#xff1a;大厂还能去&#xff0c;去了会不会被裁。 还在学习的网友来问我&#xff1a;现在还要冲互联网么&#xff1f; 我是认为大家不用恐慌吧&#xff0c;该看啥看啥&#xff0c;该学啥…

你一定要知道的unittest自动化测试框架详解

目录 框架的概念 Unittest单元测试框架 常用的assert语句 unittest创建测试代码的方式&#xff1a; unittest构建测试套件&#xff08;测试用例集合&#xff09;&#xff1a; unittest忽略测试用例&#xff1a; 运行测试集 批量执行测试用例 生成HTMLTestRunner测试报告…

【运维知识进阶篇】集群架构-Nginx基础(安装+启动+配置+多业务实现+日志管理)

本篇文章介绍下Nginx有关内容&#xff0c;Nginx是一个开源且高性能、可靠的Http Web服务、代理服务。 开源&#xff1a;直接获取源代码&#xff0c;高性能&#xff1a;支持海量并发&#xff0c;可靠&#xff1a;服务稳定 Web服务有很多&#xff0c;选择Nginx是因为他的轻量化…

【软件测试】测试开发的一生之敌-BUG

文章目录 1.前言2.如何描述/创建一个BUG3.BUG的级别4.BUG的生命周期5.跟开发产生争执怎么办 1.前言 BUG相比大家都知道,程序运行出错或者与预期不符就是BUG.现在我们来用测试人员的角度来看待BUG. 2.如何描述/创建一个BUG 测试人员要测试开发人员的代码,找出开发人员可能忽略…

敏态开发在大兴机场数字化转型中的实践

一、最大事业是育人 大兴机场是一个年轻的企业&#xff0c;作为一个企业来讲&#xff0c;最宝贵的就是人才。我们在2017年开始社招大学生&#xff0c;到目前为止&#xff0c;公司有一半都是30岁左右的年轻人&#xff0c;并且每年都会招几十个。年轻人特别想做事&#xff0c;而…

C++之内存管理及函数模版

C中的内存管理机制和C语言是一样的&#xff0c;但在具体内存管理函数上&#xff0c;C语言的malloc已经无法满足C面向对象销毁的需求&#xff0c;于是祖师爷在C中新增了一系列内存管理函数&#xff0c;即 new 和 delete 著名段子&#xff1a;如果你还没没有对象&#xff0c;那就…

( 位运算 ) 342. 4的幂 ——【Leetcode每日一题】

❓342. 4的幂 难度&#xff1a;简单 给定一个整数&#xff0c;写一个函数来判断它是否是 4 的幂次方。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 整数 n 是 4 的幂次方需满足&#xff1a;存在整数 x 使得 n 4 x n 4^x n4x。 示例 1&…

src学习记录(二)

学习目标&#xff1a; Apache Shiro ThinkPHP struts2 Apache Log4j Fastjson Weblogic 学习内容&#xff1a; 1.Apache Shiro 字段内容指纹信息请求包中&#xff0c;在Cookie信息中给 rememberMe变量赋任意值&#xff0c;收到返回包的Set-Cookie 值存在 rememberMedeleteMe …

类和对象【C++】【下篇】

目录 一、初始化列表 二、隐式类型转换 三、静态成员 四、友元 1、友元函数 2、友元类 五、内部类 六、匿名对象 一、初始化列表 下面是日期类的一个构造函数&#xff0c;调用该构造函数后&#xff0c;对象中已经有了一个初始值&#xff0c;但并不能将它称为对对象成员变…

实时操作系统内存管理-TLSF算法

内存管理-TLSF算法 前言TLSF算法&#xff1a;为什么内存又叫内存“块”&#xff1a;O(1)查找空闲块&#xff1a;确定fl&#xff1a;确定sl&#xff1a;提级申请&#xff1a;分割块&#xff1a; 空闲块如何串成链表&#xff1f;减少外部碎片&#xff1a;查找上下块&#xff1a; …

Java补充之MySQL入门必备知识

文章和代码已经归档至【Github仓库&#xff1a;https://github.com/timerring/java-tutorial 】或者公众号【AIShareLab】回复 java 也可获取。 文章目录 零基础学MySQL解决之道文件、数据库MySQL 数据库的安装和配置使用命令行窗口连接MYSQL 数据库操作示意图 数据库三层结构数…

使用Intellij IDEA创建新项目时,maven路径总是默认的,一创建maven就卡死

目录 使用Intellij IDEA创建新项目时&#xff0c;maven路径总是默认的 弄了老半天&#xff0c;终于把这个破玩意给弄好了&#xff0c;真的没有意思&#xff0c;真的很恶心 我经历了两个过程&#xff0c;一个是 使用Intellij IDEA创建新项目时&#xff0c;maven路径总是默认的…

用免费蜜罐工具配置Modbus工控蜜罐

导语&#xff1a;本文将用DecoyMini免费蜜罐工具来配置自定义的ModbusTCP工控仿真模板&#xff0c;并介绍部署后的Modbus蜜罐的使用效果。 DecoyMini是一个免费的蜜罐工具&#xff0c;其特色是仿真能力采用与软件松耦合的仿真模板来进行管理。通过一键式导入云端仿真模板库里的…

【Linux】多线程 --- 线程同步与互斥+生产消费模型

人生总是那么痛苦吗&#xff1f;还是只有小时候是这样&#xff1f; —总是如此 文章目录 一、线程互斥1.多线程共享资源访问的不安全问题2.提出解决方案&#xff1a;加锁&#xff08;局部和静态锁的两种初始化/销毁方案&#xff09;2.1 对于锁的初步理解和实现2.2 局部和全局锁…

计算机中丢失msvcr120.dll怎么办,电脑找不到msvcr120.dll怎么办

电脑提示msvcr120.dll丢失是一个常见的问题&#xff0c;这个问题通常会在你尝试打开某些程序或游戏时出现。这个问题的原因是因为你的电脑缺少了一个名为msvcr120.dll的文件&#xff0c;这个文件是微软Visual C Redistrle for Visualv 2013的一部分。如果你遇到了这个问题&…