lwIP——3 内存管理

ops/2025/1/24 7:26:35/

目录

1.什么是内存管理

2.lwIP内存堆

3.lwIP内存堆程序代码解析 

3.1 mem_init程序解析

3.2 mem_malloc程序解析

 3.3 mem_free程序解析

4.lwIP内存池

5.lwIP内存池程序代码解析

5.1 实现lwIP内存池的文件

5.1.1 memp_priv.h

 5.1.2 memp_std.h

5.1.3 memp.h

 memp_t 枚举类型:

LWIP_MEMPOOL_DECLARE 宏定义:

 5.1.4 memp.c

LWIP_MEMPOOL 指向 LWIP_MEMPOOL_DECLARE 宏定义:

const memp_pools[MEMP_MAX]数组 :

 5.2 lwIP内存池函数

5.2.1 memp_init 函数和 memp_init_pool 函数 

5.2.2 memp_malloc 函数和 memp_malloc_pool 函数 

 5.2.3 memp_free 函数与 memp_free_pool 函数


1.什么是内存管理

内存分配:申请一个大数组,完成后返回内存地址

内存释放:传入内存地址让算法进行释放

lwIP内存堆和内存池应用:  

  1. 接收数据:MAC内核的数据(内存堆和内存池可适用)
  2. 发送数据:用户调用 lwIP 的 API 发起数据发送操作,需要存储要发送的数据以及相关的控制信息(lwIP一般选用内存堆申请内存)
  3. 用户调用:用户可主动调用lwIP的内存池和内存堆API接口申请内存
  4. 接口控制块:netconn、socket、raw接口,这些接口控制块通常具有固定的结构和大小(lwIP一般选用内存池申请内存)
  5. 构建消息:API消息、数据包消息,其大小通常相对固定或者在一定范围内(lwIP一般选用内存池申请内存)


 


2.lwIP内存堆

lwIP内存堆是一种可变长分配策略,可以随意申请任意大小的内存。

lwIP内存堆采用First Fit(首次拟合)内存算法 

First Fit算法:从低地址空间往高地址空间查找,从中切割出合适的块,并把剩余的部分返回到动态内存堆中。

优点:

● 内存浪费小、比较简单,适合小内存管理

● 确保高地址空间具有足够的内存

● 要求分配最小值及相邻的空闲块合并(有效防止内存碎片)

缺点:

● 分配与释放频繁,会造成内存碎片

● 分配与释放时,从低地址开始查找,导致效率慢(以时间换空间原理)


 


3.lwIP内存堆程序代码解析 

lwIP内存堆函数

描述

mem_init()

内存堆初始化

mem_malloc()

申请内存块

mem_free()

释放内存块

………………….

………………….

MEM_LIBC_MALLOC配置项必须设置为0 (这是C 标准库分配策略配置项,我们不用所以置0)

(1)内存堆控制块结构体:

struct mem { mem_size_t next; /* 保存下一个内存块的索引 */ mem_size_t prev; /* 保存前一个内存块的索引*/ u8_t used; /* 此内存快是否被用。1 使用、0 未使用 */ 
};

next、prev 变量用来保存下一个和前 一个内存块的索引,used 变量用来声明被管理的内存块是否可用

(2)内存堆的对齐及最小配置值

//② ********************** 最小分配内存 ******************
#ifndef MIN_SIZE
#define MIN_SIZE             12
#endif /* MIN_SIZE *///③ ********************** 对齐操作 ******************
#define MIN_SIZE_ALIGNED     LWIP_MEM_ALIGN_SIZE(MIN_SIZE)              // 最小分配内存大小对齐 --12字节
#define SIZEOF_STRUCT_MEM    LWIP_MEM_ALIGN_SIZE(sizeof(struct mem))    // 内存控制块对齐---8字节
#define MEM_SIZE_ALIGNED     LWIP_MEM_ALIGN_SIZE(MEM_SIZE)              // 内存堆对齐--lwipopts.h文件下MEM_SIZE配置项

内存对齐的作用:

1,平台原因:不是全部的硬件平台都能访问随意地址上的随意类型数据的;某些硬件平台仅仅能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2,性能原因:经过内存对齐后,CPU 的内存访问速度大大提升。  


(3)定义内存堆的空间

//********************** 内存堆定义--大数组 ******************
LWIP_DECLARE_MEMORY_ALIGNED(ram_heap, MEM_SIZE_ALIGNED + (2U * SIZEOF_STRUCT_MEM));#define LWIP_DECLARE_MEMORY_ALIGNED(variable_name, size) 
u8_t variable_name[LWIP_MEM_ALIGN_BUFFER(size)]

 无论是内存堆还是内存池,它们都是对一个大数组进行操作,上述的宏定义就是指向一个名为 ram_heap 数组,该数组的大小为 MEM_SIZE_ALIGNED + (2U*SIZEOF_STRUCT_MEM),lwIP 内存堆申请的内存就是从这个数组分配得来的。


(4)操作内存堆的变量 

/* 指向对齐后的内存堆的地址*/ 
static u8_t *ram; 
/* 指向对齐后的内存堆的最后一个内存块 */ 
static struct mem *ram_end; /* 指向已被释放的索引号最小的内存块(内存堆最前面的已被释放的)*/ 
static struct mem * LWIP_MEM_LFREE_VOLATILE lfree; 

ram 指针:指向对齐后的内存堆总空间首地址 

ram_end 指针:指向内存堆总空间尾地址(接近总空间的尾地址)

lfree 指针:指向最低内存地址的空闲内存块。

注:lwIP 内核就是根据 lfree 指针指向空闲内存块来分配内存,而 ram_end 指针用来检测该总 内存堆空间是否有空闲的内存。


 

3.1 mem_init程序解析

void mem_init(void) 
{struct mem *mem;// 步骤1: 对内存堆的地址进行对齐操作,得到指向对齐后内存堆起始位置的指针ram = (u8_t *)LWIP_MEM_ALIGN(LWIP_RAM_HEAP_POINTER);// 步骤2: 建立第一个内存块mem = (struct mem *)(void *)ram;// 步骤3: 初始化第一个内存块的相关信息// 下一个内存块不存在,因此指向内存堆的结束mem->next = MEM_SIZE_ALIGNED;// 前一个内存块就是它自己,因为这是第一个内存块mem->prev = 0;// 第一个内存块没有被使用mem->used = 0;// 步骤4: 初始化堆的末端ram_end = ptr_to_mem(MEM_SIZE_ALIGNED);// 步骤5: 初始化堆末端内存块的相关信息// 最后一个内存块被使用,因为其后面没有可用空间,必须标记为已被使用ram_end->used = 1;// 下一个不存在,因此指向内存堆的结束ram_end->next = MEM_SIZE_ALIGNED;// 前一个不存在,因此指向内存堆的结束ram_end->prev = MEM_SIZE_ALIGNED;// 步骤6: 设置已释放的索引最小的内存块lfree = (struct mem *)(void *)ram;// 步骤7: 建立互斥信号量,用于内存的申请和释放保护if (sys_mutex_new(&mem_mutex) != ERR_OK) {LWIP_ASSERT("failed to create mem_mutex", 0);}
}

索引:struct mem结构体的 next 和 prev 变量并不是指针类型,它们保存的是内存块的索引, 例如定义一个 a[10]数组,next 和 prev 保存的是 0~9 的索引号,lwIP 内核根据索引号获取 a 数组的索引地址(&a[0~9])。 


 

3.2 mem_malloc程序解析

void * 
mem_malloc(mem_size_t size_in)
{ mem_size_t ptr, ptr2, size; struct mem *mem, *mem2; /*******第一:检测用户申请的内存块释放满足 LWIP 的规则*******/ /*******第二:从内存堆中划分用户的内存块******/ /* 寻找足够大的空闲块,从最低的空闲块开始.*/ for (ptr = mem_to_ptr(lfree); ptr < MEM_SIZE_ALIGNED - size; ptr = ((struct mem *)(void *)&ram[ptr])->next) { mem = ptr_to_mem(ptr); /* 取它的地址 */ /* 空间大小必须排除内存块头大小 */if ((!mem->used) && (mem->next - (ptr + SIZEOF_STRUCT_MEM)) >= size) { /* 这个地方需要判断 剩余的内存块是否可以申请 size 内存块 */ if (mem->next - (ptr + SIZEOF_STRUCT_MEM) >= (size + SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED)) { /* 上面注释一大堆,主要就是说, 剩余内存可能连一个内存块的头都放不下了, 这个时候就没法新建空内存块。其索引也就不能移动 */ /* 指向申请后的位置,即: 建立下一个未使用的内存块的头部。 即:插入一个新空内存块 */ ptr2 = (mem_size_t)(ptr + SIZEOF_STRUCT_MEM + size); /*从 Ptr2 地址开始创建 mem2 的结构体 */ mem2 = ptr_to_mem(ptr2);/* 调用(struct mem *)(void *)&ram[ptr]; */ mem2->used = 0; /* 这个根据下面的 if(mem2->next != MEM_SIZE_ALIGNED)判定 */ mem2->next = mem->next; mem2->prev = ptr; /* 空闲内存块的前一个指向上面分配的内存块 */ /* 前一个内存块指向上面建立的空闲内存块 */ mem->next = ptr2; mem->used = 1;/* 将当前分配的内存块标记为 已使用 */ /* 如果 mem2 内存块的下一个内存块不是链表中最后一个内存块 (结束地址), 那就将它下一个的内存块的 prve 指向 mem2 */ if (mem2->next != MEM_SIZE_ALIGNED) { ((struct mem *)(void *)&ram[mem2->next])->prev = ptr2; }} else {/* 内存块太小了会产生的碎片 */ mem->used = 1; }/* 这里处理:当分配出去的内存正好是 lfree 时, 因为该内存块已经被分配出去了, 必须修改 lfree 的指向下一个最其前面的已释放的内存块*/ if (mem == lfree) { struct mem *cur = lfree;/* 只要内存块已使用且没到结尾,则继续往后找 */ while (cur->used && cur != ram_end) { cur = ptr_to_mem(cur->next);/* 下一个内存块 */ }/* 指向找到的 第一个已释放的内存块。如果上面没有找到,则 lfree = lfree 不变 */ lfree = cur; }/* 这里返回 内存块的空间的地址,排除内存块的头 */ return (u8_t *)mem + SIZEOF_STRUCT_MEM + MEM_SANITY_OFFSET; } } return NULL; 
} 

 3.3 mem_free程序解析

void 
mem_free(void *rmem)
{ struct mem *mem; /* 第一步:检查内存块的参数 */ /* 判断释放的内存块释放为空 */ if (rmem == NULL) { return;/* 为空则返回 */ }/* 除去指针就剩下内存块了,通过 mem_malloc 的到的地址是不含 struct mem 的 */ mem = (struct mem *)(void *)((u8_t *)rmem - (SIZEOF_STRUCT_MEM + MEM_SANITY_OFFSET)); /* 第二步:查找指定的内存块,标记为未使用 */ mem->used = 0; /* 第三步:需要移动全局的释放指针,因为 lfree 始终指向内存堆中最小索引的那个已经释放的内存块 */ if (mem < lfree) { /* 新释放的结构现在是最低的 */ lfree = mem; }
} 

lwIP 内存堆释放内存是非常简单的,它一共分为三个步骤:

  1. 检测传入的地址是否正确
  2. 对这个地址进行偏移,偏移大小为 struct mem,这样可以得到释放内存的控制块首地址,并且设置该控制块为未使用标志,
  3. 判断该控制块的地址是否小于 lfree 指针指 向的地址,若小于,则证明 mem 的内存块在 lfree 指向的内存块之前即更接近堆空间首地址, 系统会把 lfree 指针指向这个释放的内存块(控制块 + 可用内存),以后申请内存时会在 lfree 指针的内存块开始查找合适的内存。


4.lwIP内存池

lwIP内存池是把连续的内存分成多个大小相同的内存空间,以链表的形式链接起来

以空间换时间原理

内存分配:无需切割,直接分配内存空间
内存释放:释放便捷,无需与相邻合并

优点:

● 分配速度快 

● 防止内存碎片

● 回收便捷

缺点:

● 资源浪费

● 申请大型内存时,可能申请失败


5.lwIP内存池程序代码解析

5.1 实现lwIP内存池的文件

5.1.1 memp_priv.h

这个文件主要定义了两个结构体,分别为 memp memp_desc 结构体:

memp 结构体:把同一类型的内存池以链表的形式链接起来

memp_desc 结构体:管理和描述各类型的内存池,如数量、大小、内存池的起始地址和指向空闲内存池的指针。

struct memp
{ struct memp *next; /*指向下一个控制块节点*//*其他的成员变量一般用不到*/
};
/* 内存池描述符 */ 
struct memp_desc
{ 
u16_t size; 		  /* 每个内存块的大小 */ 
u16_t num; 		  /* 内存块的数量 */ 
u8_t *base; 		  /* 指向内存的基地址 */ 
struct memp **tab; /* 指向第一个空闲块 */ 
};

 5.1.2 memp_std.h

该文件定义了 lwIP 内核所需的内存池,由于 lwIP 内核的固定数据结构多种多样,所以它 们使用宏定义声明是否使用该类型的内存池,如 TCP、UDP、DHCP、ICMP 等协议。

#if LWIP_RAW 
LWIP_MEMPOOL(RAW_PCB, MEMP_NUM_RAW_PCB, sizeof(struct raw_pcb),"RAW_PCB") 
#endif /* LWIP_RAW */ #if LWIP_UDP 
LWIP_MEMPOOL(UDP_PCB, MEMP_NUM_UDP_PCB, sizeof(struct udp_pcb), "UDP_PCB") 
#endif /* LWIP_UDP */ #if LWIP_TCP 
LWIP_MEMPOOL(TCP_PCB, MEMP_NUM_TCP_PCB, sizeof(struct tcp_pcb), "TCP_PCB") 
LWIP_MEMPOOL(TCP_PCB_LISTEN, MEMP_NUM_TCP_PCB_LISTEN, 
sizeof(struct tcp_pcb_listen), "TCP_PCB_LISTEN") 
LWIP_MEMPOOL(TCP_SEG, MEMP_NUM_TCP_SEG, sizeof(struct tcp_seg), "TCP_SEG") 
#endif /* LWIP_TCP */ 
/* …………………………………………………………………………………忽略以下源码……………………………………………………………………………………… 
*/

注意:

1. 不同类型的内存池是由相应的宏定义声明启用

2. LWIP_MEMPOOL 宏定义用来初始化各类型的内存池 

5.1.3 memp.h

主要是memp_t 枚举类型LWIP_MEMPOOL_DECLARE 宏定义

 memp_t 枚举类型:
/* 获取描述符的数量 */ 
typedef enum 
{ 
#define LWIP_MEMPOOL(name,num,size,desc) MEMP_##name, 
#include "lwip/priv/memp_std.h" 
MEMP_MAX 
} memp_t;

##是连接符号,这里表示MEMP_后面接上 LWIP_MEMPOOL(name,num,size,desc) 中的参数 name :MEMP_name

举例:

如果 memp_std.h 文件只 启用了 LWIP_RAW 和 LWIP_UDP 类型的内存池,那么 MEMP_MAX 变量就等于 2

typedef enum { 
MEMP_RAW_PCB, 
MEMP_UDP_PCB, MEMP_MAX 
} memp_t; 

LWIP_MEMPOOL_DECLARE 宏定义:

此宏定义非常重要,各类型的内存池都使用这个宏定义声明,例如内存池的内存由来,各 类型内存池的数量、大小、内存由来的地址以及指向空闲的指针。

#define LWIP_MEMPOOL_DECLARE(name,num,size,desc) \// 声明内存对齐的数组,用于存储内存池的数据LWIP_DECLARE_MEMORY_ALIGNED(memp_memory_ ## name ## _base, \((num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size)))); \\// 声明内存池的统计实例LWIP_MEMPOOL_DECLARE_STATS_INSTANCE(memp_stats_ ## name) \\// 声明一个静态的 memp 指针,用于指向内存池的表头static struct memp *memp_tab_ ## name; \\// 定义一个常量的 memp_desc 结构体,用于描述内存池的信息const struct memp_desc memp_ ## name = { \// 声明内存池的描述信息DECLARE_LWIP_MEMPOOL_DESC(desc) \// 引用内存池的统计信息LWIP_MEMPOOL_DECLARE_STATS_REFERENCE(memp_stats_ ## name) \// 每个内存块的对齐后的大小LWIP_MEM_ALIGN_SIZE(size), \// 内存块的数量(num), \// 内存池数据存储的起始地址memp_memory_ ## name ## _base, \// 指向内存池表头指针的地址&memp_tab_ ## name \};

 这个宏定义展开后如下源码所示:

#define LWIP_MEMPOOL_DECLARE(name, num, size, desc) \/* 定义一个用于存储内存池数据的数组 */u8_t memp_memory_ ## name ## _base[((((((num) * (MEMP_SIZE + (((size) + \MEM_ALIGNMENT - 1U) & ~(MEM_ALIGNMENT - 1U))))) + MEM_ALIGNMENT - \1U)))]; \/* 声明一个静态指针,用于指向内存池的管理表 */static struct memp *memp_tab_ ## name; \/* 定义一个常量结构体,用于描述内存池的信息 */const struct memp_desc memp_ ## name = { \/* 每个内存块的对齐后的大小 */LWIP_MEM_ALIGN_SIZE(size), \/* 内存池中内存块的数量 */(num), \/* 内存池的起始地址 */memp_memory_ ## name ## _base, \/* 指向内存池管理表的指针 */&memp_tab_ ## name \};

 展开之后可以看出,各类型的内存池的内存由来和 lwIP 内存堆一样,都是由数组分配的。

 
5.1.4 memp.c

LWIP_MEMPOOL 指向 LWIP_MEMPOOL_DECLARE 宏定义:
#define LWIP_MEMPOOL(name,num,size,desc) \ LWIP_MEMPOOL_DECLARE(name,num,size,desc) 
#include "lwip/priv/memp_std.h" 

举例:

memp_std.h 只启用 LWIP_RAW 和 LWIP_UDP 类型的内存池,展开之后如下所示:

// 定义 RAW_PCB 内存池相关
// 定义存储 RAW_PCB 内存池数据的数组
u8_t memp_memory_RAW_PCB_base[((((((num) * (MEMP_SIZE + 
(((size) + MEM_ALIGNMENT - 1U) & ~(MEM_ALIGNMENT - 1U))))) + 
MEM_ALIGNMENT - 1U)))];
// 声明指向 RAW_PCB 内存池管理表的静态指针
static struct memp *memp_tab_RAW_PCB;
// 定义描述 RAW_PCB 内存池信息的常量结构体
const struct memp_desc memp_RAW_PCB = {// 每个内存块的对齐后的大小LWIP_MEM_ALIGN_SIZE(size),// 内存池中内存块的数量(num),// RAW_PCB 内存池的起始地址memp_memory_RAW_PCB_base,// 指向 RAW_PCB 内存池管理表的指针&memp_tab_RAW_PCB
};// 定义 UDP_PCB 内存池相关
// 定义存储 UDP_PCB 内存池数据的数组
u8_t memp_memory_UDP_PCB_base[((((((num) * (MEMP_SIZE + 
(((size) + MEM_ALIGNMENT - 1U) & ~(MEM_ALIGNMENT - 1U))))) + 
MEM_ALIGNMENT - 1U)))];
// 声明指向 UDP_PCB 内存池管理表的静态指针
static struct memp *memp_tab_UDP_PCB;
// 定义描述 UDP_PCB 内存池信息的常量结构体
const struct memp_desc memp_UDP_PCB = {// 每个内存块的对齐后的大小LWIP_MEM_ALIGN_SIZE(size),// 内存池中内存块的数量(num),// UDP_PCB 内存池的起始地址memp_memory_UDP_PCB_base,// 指向 UDP_PCB 内存池管理表的指针&memp_tab_UDP_PCB
};

const memp_pools[MEMP_MAX]数组 :
const struct memp_desc* const memp_pools[MEMP_MAX] = { 
#define LWIP_MEMPOOL(name,num,size,desc) &memp_ ## name, 
#include "lwip/priv/memp_std.h" 
}; 

举例:

若 memp_std.h 只启用 LWIP_RAW 和 LWIP_UDP 类型的内存池,这个数组展开之后如下所示:

const struct memp_desc* const memp_pools[MEMP_MAX] = { &memp_memp_RAW_PCB, &memp_memp_UDP_PCB, 
};


 


 5.2 lwIP内存池函数

lwIP内存池函数

描述

memp_init()

内存池初始化

memp_malloc()

申请内存池

memp_free()

释放内存池

………………….

………………….


5.2.1 memp_init 函数和 memp_init_pool 函数 

// 初始化所有内存池
void memp_init(void) 
{ u16_t i; /* 遍历,需要多少个内存池 */ for (i = 0; i < LWIP_ARRAYSIZE(memp_pools); i++) { // 调用 memp_init_pool 函数对每个内存池进行初始化memp_init_pool(memp_pools[i]); } 
} // 初始化单个内存池
void memp_init_pool(const struct memp_desc *desc) 
{ int i; struct memp *memp; // 初始化内存池的表头指针为 NULL*desc->tab = NULL; /* 内存对齐 */ // 对内存池的起始地址进行对齐操作memp = (struct memp*)LWIP_MEM_ALIGN(desc->base); /* 将内存块链接成链表形式 */ for (i = 0; i < desc->num; ++i) { // 将当前内存块的 next 指针指向当前表头memp->next = *desc->tab; // 更新表头指针为当前内存块*desc->tab = memp; /* 地址偏移 */// 移动指针到下一个内存块的位置memp = (struct memp *)(void *)((u8_t *)memp + MEMP_SIZE + desc->size); } 
}

5.2.2 memp_malloc 函数和 memp_malloc_pool 函数 

memp_malloc 函数需要传入申请内存池的类型,如 UDP_PCB…,接着根据传入的类型来 查找对应的内存池描述符,查找完成之后根据该内存池描述符的 tab 指针指向内存池分配给用户,并且把 tab 指针偏移至下一个空闲内存池。

// 从指定类型的内存池中分配内存块
void * 
memp_malloc(memp_t type) 
{ void *memp; // 调用 do_memp_malloc_pool 函数从对应内存池分配内存memp = do_memp_malloc_pool(memp_pools[type]); return memp; 
} // 实际执行内存池分配操作的函数
static void* 
do_memp_malloc_pool(const struct memp_desc *desc) 
{ struct memp *memp; // 获取内存池表头指向的内存块memp = *desc->tab; if (memp != NULL) { // 若内存块存在,更新表头指针指向下一个内存块*desc->tab = memp->next; // 返回排除管理信息部分的内存块地址return ((u8_t*)memp + MEMP_SIZE); } else {// 这里原代码没有处理逻辑,可根据实际情况添加错误处理等代码}// 若没有可用内存块,返回 NULLreturn NULL; 
}

 5.2.3 memp_free 函数与 memp_free_pool 函数

释放函数非常简单,只需对内存池描述符的 tab 指针偏移至释放的内存池。

// 释放指定类型内存池中的内存块
void memp_free(memp_t type, void *mem) 
{ // 判断内存块的起始地址是否为空if (mem == NULL) { return; } // 调用 do_memp_free_pool 函数执行实际的释放操作do_memp_free_pool(memp_pools[type], mem); 
} // 实际执行内存池内存块释放操作的函数
static void do_memp_free_pool(const struct memp_desc* desc, void *mem) 
{ struct memp *memp; // 根据内存块的地址偏移得到内存块的起始地址memp = (struct memp *)(void *)((u8_t*)mem - MEMP_SIZE); // 内存块的下一个就是链表中的第一个空闲内存块memp->next = *desc->tab; // *desc->tab 指向 memp 内存块*desc->tab = memp; 
}

http://www.ppmy.cn/ops/152683.html

相关文章

Flink Gauss CDC:深度剖析存量与增量同步的创新设计

目录 设计思路 1.为什么不直接用FlinkCDC要重写Flink Gauss CDC 2.存量同步的逻辑是什么 2.1、单主键的切片策略是什么 2.2、​​​​​复合主键作切片,怎么保证扫描到所有的数据 3、增量同步的逻辑是什么 4、存量同步结束之后如何无缝衔接增量同步 5、下游数据如何落…

B树系列详解

B树 前言1. B树的概念2. B-树的插入3. B-树的插入实现3.1 B-树节点设计3.2 插入key的过程3.3 B树的简单验证3.4 B树性能分析 4. B树和B*树4.1 B树5.2 B*树5.3 总结 前言 常见的搜索结构&#xff1a; 种类数据格式时间复杂度顺序查找无要求O(N)二分查找有序O( l o g 2 N log_2…

单片机-STM32 WIFI模块--ESP8266 (十二)

1.WIFI模块--ESP8266 名字由来&#xff1a; Wi-Fi这个术语被人们普遍误以为是指无线保真&#xff08;Wireless Fidelity&#xff09;&#xff0c;并且即便是Wi-Fi联盟本身也经常在新闻稿和文件中使用“Wireless Fidelity”这个词&#xff0c;Wi-Fi还出现在ITAA的一个论文中。…

Cursor的详细使用指南

以下是一份关于 Cursor 的详细使用指南&#xff1a; 一、安装与设置 下载与安装&#xff1a; 首先&#xff0c;访问 Cursor 的官方网站&#xff0c;根据你的操作系统&#xff08;Windows、Mac 或 Linux&#xff09;下载相应的安装程序。运行安装程序&#xff0c;按照屏幕上的提…

GESP202309 三级【进制判断】题解(AC)

》》》点我查看「视频」详解》》》 [GESP202309 三级] 进制判断 题目描述 N N N 进制数指的是逢 N N N 进一的计数制。例如&#xff0c;人们日常生活中大多使用十进制计数&#xff0c;而计算机底层则一般使用二进制。除此之外&#xff0c;八进制和十六进制在一些场合也是常用…

Linux(Centos 7.6)命令详解:wc

1.命令作用 打印文件的行数、单词数、字节数&#xff0c;如果指定了多个文件&#xff0c;还会打印以上三种数据的总和(Print newline, word, and byte counts for each FILE, and a total line if more than one FILE is specified) 2.命令语法 Usage: wc [OPTION]... [FIL…

SpringBoot实现定时任务,使用自带的定时任务以及调度框架quartz的配置使用

SpringBoot实现定时任务&#xff0c;使用自带的定时任务以及调度框架quartz的配置使用 文章目录 SpringBoot实现定时任务&#xff0c;使用自带的定时任务以及调度框架quartz的配置使用一. 使用SpringBoot自带的定时任务&#xff08;适用于小型应用&#xff09;二. 使用调度框架…

c++常见设计模式之装饰器模式

基础介绍 装饰器模式是结构型设计模式&#xff0c;从字面意思看装饰器设计模式就是用来解决在原有的实现基础上添加一些额外的实现的问题。那么正统的概念是什么呢&#xff1f;装饰器模式允许我们动态的向对象添加新的 行为&#xff0c;同时不改变其原有的结构。它是一种比继承…