【STM32】TCP/IP通信协议--LWIP内存管理

五、LWIP内存管理

1.什么是内存管理?

(1)内存管理,是指软件运行时对计算机内存资源的分配的使用的技术,其主要目的是如何高效、快速的分配,并且在适当的时候释放和回收内存资源(就比如C语言当中的malloc 、free分配和释放)

内存分配:大数组,完成之后返回内存地址

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

(2)LWIP内存管理策略

1.内存堆:提供合适大小的内存,剩余内存返回堆中

2.内存池:只能申请固定大小的内存,能有效防止内存碎片

3.C库: C运行时库自带的内存分配策略(不建议使用)

lwip内存池和内存堆本质上直接操作数组实现

(3)lwip内存堆和内存池的应用

  • 接收数据:MAC内核数组【内存堆和内存池可适用,正点原子阿波罗版本的使用的是内存池】

  • 发送数据:用户调用lwip的API接口【lwip一般选用内存堆申请内存】

  • 用户调用:可调用lwip的内存池和内存堆API接口申请内存

  • 接口控制块:netconn、socket、raw接口

  • 构建消息:API消息、数据包消息

2.lwip内存堆简介

lwip内存堆是一种可变长的分配策略,可以随意申请任意大小的内存,lwip内存堆采用的是First Fit(首次拟合)内存算法

(1)First Fit算法

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

优点:

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

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

  • 要求分配最小值以及相邻空间块合并,有效防止内存碎片

缺点:

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

  • 分配和释放时,从低地址开始寻找,会导致效率慢

3.lwip内存堆原理解析

1

通过开辟一个内存堆,使用模拟IC运行时库的内存分配策略实现【大数组】

(1)管理内存块的结构体:

如下源码所示:

struct mem {mem_size_t next;      //指向下一个节点索引mem_size_t prev;      //指向上一个节点索引u8_t used;            //描述内存块是否可用    0:未使用   1:已使用
};
(2)最小内存分配
#ifndef MIN_SIZE
#define MIN_SIZE             12//最小分配内存
#endif /* MIN_SIZE */

为了防止内存碎片,lwip内核定义了最小分配大小MIN_SIZE。当用户申请的内存小于最小分配内存时,系统将分配MIN_SIZE大小的内存,

(3)对齐定义
//对齐操作
#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)//内存堆对齐

SIZEOF_STRUCT_MEM宏定义用于确保内存大小进行4字节对齐,这不仅可以大大提升CPU的访问速度还因为某些硬件平台只能从特定地址处获取特定类型的数据。

(4)定义内存堆的空间
#ifndef LWIP_RAM_HEAP_POINTER 
​
/*定义堆内存空间*/
​
LWIP_DECLARE_MEMORY_ALIGNED(ram_heap, MEM_SIZE_ALIGNED + (2U*SIZEOF_STRUCT_MEM)); 
​
#define LWIP_RAM_HEAP_POINTER ram_heap 
​
#endif

无论是内存池还是内存堆都是对一个大数组进行操作。这种大数组被称为ram_heap数组,其大小常被定义为 MEM_SIZE_ALIGNED + (2U*SIZEOF_STR UCT_MEM)。这个能让用户申请内存时在这个大数组中分配相应大小的内存块,减少内存碎片。

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

lwIP 内核使用三个指针:ram 指针、ram_end 指针和 lfree 指针。

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

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

lfree 指针指向ram_heap数组最低内存地址的空闲内存块。

在内存分配当中lwip根据lfree指针指向的空闲内存进行分配,从而快速找到可用的内存块,并有效分配内存给需要的任务。ram_end指针用于检测总内存堆空间中是否存在空闲的内存,以便进行进一步的内存分配。

(6)内存初始化mem_init():

下图是对堆空间进行初始化,初始化后的lfree指针指向第一个内存块,该内存块由控制块可用内存所组成,ram_end指针指向堆空间的尾部,用于判断堆空间是否存在可用内存,若lfree指针指向ram_end指针,则表示该堆空间没有可用内存进行分配

控制块:标记内存是否可用

可用内存:实际可分配的内存区域

void mem_init(void)
{struct mem *mem;
​LWIP_ASSERT("Sanity check alignment",(SIZEOF_STRUCT_MEM & (MEM_ALIGNMENT-1)) == 0);
​/* 对内存堆的地址(全局变量的名)进行对齐指向 ram_heap*/ram = (u8_t *)LWIP_MEM_ALIGN(LWIP_RAM_HEAP_POINTER);/* 建立第一个内存块,内存块由内存块头+空间组成。 */mem = (struct mem *)(void *)ram;//附加在每个内存块前面的结构体/* 下一个内存块不存在,因此指向内存堆的结束 */mem->next = MEM_SIZE_ALIGNED;/* 前一个内存块就是它自己,因为这是第一个内存块 */ mem->prev = 0;/* 第一个内存块没有被使用 */ mem->used = 0;/* 初始化堆的末端(指向 MEM_SIZE_ALIGNED 底部位置)*/ram_end = (struct mem *)(void *)&ram[MEM_SIZE_ALIGNED];/* 最后一个内存块被使用。因为其后面没有可用空间,必须标记为已被使用 */ram_end->used = 1;/* 下一个不存在,因此指向内存堆的结束 */ram_end->next = MEM_SIZE_ALIGNED;/* 前一个不存在,因此指向内存堆的结束 */ram_end->prev = MEM_SIZE_ALIGNED;
​/* 已释放的索引最小的内存块就是上面建立的第一个内存块。*/lfree = (struct mem *)(void *)ram;
​MEM_STATS_AVAIL(avail, MEM_SIZE_ALIGNED);/* 这里建立一个互斥信号量,主要是用来进行内存的申请、释放的保护 */if(sys_mutex_new(&mem_mutex) != ERR_OK) {LWIP_ASSERT("failed to create mem_mutex", 0);}
}

在内存分配过程中,lfree指针从低地址开始不短查找和划分内存,直到ram_end指针所指向的地址,这意味着lfree指针从堆空间的起始位置开始,逐个遍历内存块,直到找到可用的内存或到达堆空间的末尾才能结束分配。

(7)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;}
}

4.LWIP内存池简介

(1)什么是内存池

lwip内存池是把连续的内存池分成多个大小相同的内存空间,并通过单链表的方式连接起来。当用户申请内存时,系统会从单链表的头 部取出一个内存块进行分配,释放内存时只需将内存块放回链表的头部。

lwip内存池优点:

分配速度快,防止内存碎片,回收便捷

lwip内存池缺点:

资源浪费,申请大型内存时,可能申请失败

lwip内存池应用场景:

在 lwIP 中,存在多种固定大小的数据结构,这些数据结构的特点是预先知道其大小并且在整个生命周期中保持不变。比如说,在建立 TCP 连接时,需要使用 TCP 控制块这种数据结构,其大小是固定的。为了满足这些数据结构的内存分配需求,lwIP 在内存初始化时创建了动态内存池 POOL预先分配一定数量的内存块。这种内存管理方式有助于提高内存分配的效率和性能。

(2)实现LWIP内存池的文件

对于内存堆中的动态内存池,运用了很多复杂的宏定义的运用。所以我们将重点探究这四个关键文件:memp.c、memp.h、memp_std.h 和 memp_prive.h。

i.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;
};

memp结构体和memp_desc结构体的关系:

由图可知:每一个 memp_desc 结构体都是用于管理同一类型的内存池。 这些内存池,也就是内存块,是通过链表的形式相互连接起来的。

ii.memp_std.h 文件

该文件是 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 */

通过上面代码,可发现:不同类型的内存池是通过相应的宏定义声明启用的。LWIP_MEMPOOL 这个宏定义用于初始化各种类型的内存池。

iii. memp.h 文件

文件主要定义了 memp_t 枚举类型,该类型用于获取各类内存池的数量声明了宏定义和函数以提供外部文件使用,这里定义里前面所提到了LWIP_MEMPOOL 宏定义,

typedef enum {
​
/* ##为 C 语言的连接符,例如 MEMP_##A,A = NAME ,所以等于 MEMP_NAME */
#define LWIP_MEMPOOL(name,num,size,desc) MEMP_##name,
​
#include "lwip/priv/memp_std.h"MEMP_MAX} memp_t;
​
#include "lwip/priv/memp_priv.h" /* 该文件需要使用上面的枚举 */
#include "lwip/stats.h"

根据 memp_std.h 文件中启用的内存池类型来计算 MEMP_MAX,即各类内存池的最大数量。计算方法如下:

1,LWIP_MEMPOOL 宏定义指向 MEMP_##name(##为 C 语言中的连接符)

2,通过#include "lwip/priv/memp_std.h"文件来启用所需的内存池类型

iv.memp.c 文件

在memp.h当中,我们有提到:LWIP_MEMPOOL 指向 LWIP_MEMPOOL_DECLARE 宏定义,而在memp.c 文件当中的const memp_pools[MEMP _MAX]数组则用于管理各类内存池的描述符

#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 类型的内存池时,展开后的枚举类型如下:

u8_t memp_memory_RAW_PCB_base[((((((num) * (MEMP_SIZE +
(((size) + MEM_ALIGNMENT - 1U) & ~(MEM_ALIGNMENT-1U))))) +
MEM_ALIGNMENT - 1U)))];
​
static struct memp *memp_tab_RAW_PCB;
const struct memp_desc memp_RAW_PCB= { \LWIP_MEM_ALIGN_SIZE(size), \(num), \memp_memory_TCPIP_MSG_API_base, \&memp_tab_TCPIP_MSG_API \};u8_t memp_memory_UDP_PCB_base[((((((num) * (MEMP_SIZE +
(((size) + MEM_ALIGNMENT - 1U) & ~(MEM_ALIGNMENT-1U))))) +
MEM_ALIGNMENT - 1U)))];
​
static struct memp *memp_tab_UDP_PCB;const struct memp_desc memp_UDP_PCB= { \LWIP_MEM_ALIGN_SIZE(size), \(num), \memp_memory_UDP_PCB_base, \&memp_tab_UDP_PCB \};

这段代码通过使用 LWIP_MEMPOOL_DECLARE 宏定义,声明了各类内存池的描述和管理信息。具体来说,它定义了memp_desc 结构体,比如 :

memp_RAW_PCB:用于描述该类型的内存池的数量、大小、分配内存地址以及指向空闲内存池的指针。

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

memp_pools[MEMP_RAW_PCB],它取自 memp_RAW_PCB 变 量的地址。这是我们在前面展开LWIP_MEMPOOL_DECLARE 宏定义时定义的变量。

v.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_pools[i]);}
}
​
void memp_init_pool(const struct memp_desc *desc)
{int i;struct memp *memp;*desc->tab = NULL;/* 内存对齐 */memp = (struct memp*)LWIP_MEM_ALIGN(desc->base);/* 将内存块链接成链表形式 */for (i = 0; i < desc->num; ++i) {memp->next = *desc->tab;*desc->tab = memp;/* 地址偏移*/ memp = (struct memp *)(void *)((u8_t *)memp +
MEMP_SIZE + desc->size);}
}
​

每个类型的描述符都是用于管理和描述该类型的内存池。这些同一类型的内存池内部通过指向下一个节点的指针链接起来,形成一个链表。通过第二个 for 循 环语句,我们可以遍历这些同一类型的内存池,并将其以链表的形式进行链接。

memp_pool 数组包含了不同类型的内存池描述符,每个描述符负责管理同类型的内存池。这些内存池通过指针链接成一个单向链表,方便管理和访问。同一类型的内存池都 在同一个数组中分配,通过 base 指针可以找到该数组的首地址。tab 指针指向第一个空闲的内 存池,当用户申请内存池时,将从 tab 指针指向的内存池进行分配。分配完成后,tab 指针将偏移到下一个空闲内存池的地址,以便下次分配。

vi.memp_malloc 函数和 memp_malloc_pool 函数

内存池有多种类型,因此用户在申请内存池时需要**明确申请的类型lwIP 内存池的申请函数是 memp_malloc**

void *
memp_malloc(memp_t type)
{void *memp;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{}return NULL;
}
​

memp_malloc 函数根据用户传入的内存池类型,如 UDP_PCB 等,在 memp_pool 数组中查找对应的内存池描述符。一旦找到对应的描述符,该函数会根据描述符中的 tab 指针来分配内存给用户,并将 tab 指针偏移至下一个空闲内存池。

vii.memp_free 函数与 memp_free_pool 函数

内存池的释放函数相对简单,它需要传入两个参数:内存池的类型和要释放的内存池的地址。通过这两个参数,lwIP 内核可以确定该类型内存池描述符的位置,以及需要释放的内存池的具体位置。

void memp_free(memp_t type, void *mem)
{if (mem == NULL) /* 判断内存块的起始地址释放为空 */{return;}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;
}

释放函数很简单,只需要将内存池描述符的 tab 指针偏移至要释放的内存池。这样,释放的内存块就会被返回到相应的内存池中,以供后续使用。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

安全的价值:构建现代企业的基础

物理安全对于组织来说并不是事后才考虑的问题&#xff1a;它是关键的基础设施。零售商、医疗保健提供商、市政当局、学校和所有其他类型的组织都依赖安全系统来保障其人员和场所的安全。 随着安全技术能力的不断发展&#xff0c;许多组织正在以更广泛的视角看待他们的投资&am…

SQL学习1

24.9.28学习目录 一.数据库1.SQL语句基础2.匹配条件 一.数据库 对于嵌入式的数据库&#xff0c;其使用的是SQLite这种小型数据库&#xff1b; 在ubuntu中的下载方法 //字符界面 sudo apt-get install sqlite3//图形界面 sudo apt-get install sqlitemanSQLite特点&#xff1a…

ACL 2023--MetaAdapt: 通过元学习实现领域自适应的少量样本虚假信息检测

https://github.com/Yueeeeeeee/MetaAdapt 随着社交媒体上出现的新话题&#xff08;例如COVID-19&#xff09;成为虚假信息传播的来源&#xff0c;克服原始训练领域&#xff08;即源领域&#xff09;与这些目标领域之间的分布变化&#xff0c;仍然是虚假信息检测中的一项复杂任…

5分钟精通Excel在go中的使用

一些简单操作可以在官方文档中找到&#xff0c;应该足够无经验的朋友们入门 介绍 - 《Excelize v2.2 中文文档》 - 书栈网 BookStack 这里贴一个中文版的链接&#xff08;以excelize库为例&#xff0c;相对其他库来说&#xff0c;体验很不错&#xff09;&#xff0c;不过要注…

PWA(Progressive web APPs,渐进式 Web 应用): manifest.json、 Service Worker

文章目录 引言I 什么是 PWA功能特性技术上分为三个部分安装应用II Web 应用清单将Web 应用清单文件链接到站点manifest.json字段说明III Service Worker( 缓存管理)IV 结合构建工具让项目支持 PWA应用使用插件vite-plugin-pwaworkbox-webpack-plugin插件扩展知识将 PWA 作为脱机…

紫光 FPGA固化RAM位置的操作流程

1. 前提条件&#xff1a;需要已经编译出一个功能完整的没有时序违例的版本出来&#xff1b; 2. 将RAM导出至txt文件&#xff1a; 这个过程需要几分钟&#xff0c;耐心等待一下。 等待提示成功就可以进行下一步操作了。 3. 将【2】中的txt文件中的内容全选复制粘贴到pcf文件的…

物体实例分割,机器人拾取

物体实例分割是计算机视觉领域的一个关键任务&#xff0c;它旨在从图像中分割出每个独立物体&#xff0c;并且为每个物体实例提供一个独特的标识。这一任务不仅识别出图像中的物体&#xff0c;还能区分出多个同类物体的不同实例&#xff0c;例如在一张桌子上摆放的多个相同的杯…

AI直播巅峰!2024年AI无人直播app排行榜领先者揭晓!

AI直播巅峰&#xff01;2024年AI无人直播app排行榜领先者揭晓&#xff01; 在科技日新月异的今天&#xff0c;AI技术正以惊人的速度渗透到我们生活的每一个角落&#xff0c;其中&#xff0c;AI无人直播app的兴起无疑成为了直播行业的一股革新力量。随着技术的不断成熟和市场的…

瓶子类型检测系统源码分享

瓶子类型检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

论文阅读:A Generalization of Transformer Networks to Graphs

论文阅读&#xff1a;A Generalization of Transformer Networks to Graphs 论文地址1 摘要2 贡献Graph TransformerOn Graph Sparsity&#xff08;图稀疏&#xff09;On Positional Encodings&#xff08;位置编码&#xff09;3 Graph Transformer Architecture&#xff08;架…

关于Fake Location定位,运动世界校园问题

不好意思&#xff0c;之前那个文章其实是很早之前的&#xff0c;不知道为什么审核了很久一直没有通过&#xff0c;然后前几周莫名其妙点了一下重新发布&#xff0c;竟然发布成功了&#xff0c;这个方法已经失效了&#xff0c;要可以稳定&#xff0c;我建议是买一台root的手机&a…

给Ubuntu虚拟机设置静态IP地址(固定IP)

查看 为Ubuntu虚拟机配置静态IP地址&#xff08;固定IP&#xff09;的方法经过亲自测试&#xff0c;已被证实有效。 这里你记得网关就可以了&#xff0c;等下要用 查看配置前的网络信息 ifconfig 查看网关 route -n 配置 配置网络文件 cd /etc/netplan/ ls 查看自己的文件的名…

o1规划能力首测!已超越语言模型范畴,preview终于赢mini一回

克小西 发自 凹非寺 量子位 | 公众号 QbitAI o1-preview终于赢过了mini一次&#xff01; 亚利桑那州立大学的最新研究表明&#xff0c;o1-preview在规划任务上&#xff0c;表现显著优于o1-mini。 相比于传统模型的优势更是碾压级别&#xff0c;在超难任务上的准确率比Llama3.…

C++第五讲(1):STL--string--各个函数的使用方法

C第五讲&#xff1a;STL--string 1.STL简介2.string类2.1string类的常见构造2.1.1重点1&#xff1a; string&#xff08;&#xff09;2.1.2重点2&#xff1a;string(const string& str)2.1.3使用3&#xff1a;string(const string& str, size_t pos, size_t len npos)…

4.5 了解大数据处理基本流程

文章目录 1. 引言2. 数据采集2.1 数据库采集2.2 实时数据采集2.3 网络爬虫采集 3. 数据预处理3.1 数据清洗3.2 数据集成3.3 数据归约3.4 数据转换 4. 数据处理与分析4.1 数据处理4.2 数据分析 5. 数据可视化与应用5.1 数据可视化5.2 ECharts框架5.3 课堂作业 6. 结语 1. 引言 …

光控资本:什么是优质股,近期估值创历史新低的优质股盘点?

在股票商场中&#xff0c;选到优质股进行出资&#xff0c;可以让出资者取得更高的出资酬谢。美联储发布降息&#xff0c;关于A股商场而言&#xff0c;估值创新低的优质股或许将获益于美联储降息。 根据近期数据&#xff0c;归纳10家以上安排评级的个股中&#xff0c;有19只个股…

Delphi实现计算器——状态机

成品展示&#xff1a; 方案&#xff1a; 采用状态机和静态工厂模式实现。 1.使用工厂方法模式来创建操作对象 定义了一个抽象的操作类TOperation,其中声明了Calculate方法用于执行具体的计算。 然后针对不同的操作(加、减、乘、除、取模)分别创建了具体的操作类,如TAddOp…

JWT令牌技术介绍及使用

一、JWT介绍 JWT是JSON Web Token的缩写&#xff0c;即JSON Web令牌&#xff0c;是一种自包含令牌。 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。 JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息&#xff0c;以便于从资源服务…

数组解构是如何降低 JavaScript 的运行速度

在JavaScript开发中&#xff0c;解构赋值是一个广受欢迎的特性&#xff0c;它让代码更加简洁易读。然而&#xff0c;不同的解构模式可能会对性能产生显著影响。本文将深入探讨数组解构赋值可能带来的性能问题&#xff0c;并提供优化建议。 解构赋值的两种模式 JavaScript中的解…