FreeRTOS 快速入门(二)之内存管理

news2024/11/15 11:05:41

目录

  • 一、概述
  • 二、FreeRTOS 中管理内存的 5 种方法
    • 1、Heap_1
    • 2、Heap_2
    • 3、Heap_3
    • 4、Heap_4
      • 4.1 内存申请
      • 4.2 内存释放
    • 5、Heap_5
  • 三、Heap 相关的函数
    • 1、pvPortMalloc/vPortFree
    • 2、xPortGetFreeHeapSize
    • 3、xPortGetMinimumEverFreeHeapSize
    • 4、malloc 失败的钩子函数


一、概述

在计算系统中,变量、中间数据一般存放在系统存储空间中,只有在实际使用时才将 它们从存储空间调入到中央处理器内部进行运算。通常存储空间可以分为两种:内部存储 空间和外部存储空间。内部存储空间访问速度比较快,能够按照变量地址随机地访问,也 就是我们通常所说的 RAM(随机存储器),或电脑的内存;而外部存储空间内所保存的内 容相对来说比较固定,即使掉电后数据也不会丢失,可以把它理解为电脑的硬盘。在这一 章中我们主要讨论内部存储空间(RAM)的管理——内存管理。

FreeRTOS 操作系统将内核与内存管理分开实现,操作系统内核仅规定了必要的内存管 理函数原型,而不关心这些内存管理函数是如何实现的,所以在 FreeRTOS 中提供了多种 内存分配算法(分配策略),但是上层接口(API)却是统一的。这样做可以增加系统的 灵活性:用户可以选择对自己更有利的内存管理策略,在不同的应用场合使用不同的内存 分配策略。

如果 FreeRTOS 对象是动态创建的,那么标准 C 库 malloc() 和 free() 函数有时可用于此目的,但是…它们在嵌入式系统上并不总是可用,占用了宝贵的代码空间,不是线程安全的,而且不是确定性的 (执行函数所需时间将因调用而异),所以更多的时候需要的不是一个替代的内存分配实现。

一个嵌入式/实时系统的 RAM 和定时要求可能与另一个非常不同,所以单一的 RAM 分配算法 将永远只适用于一个应用程序子集。

为了避免此问题,FreeRTOS 将内存分配 API 保留在其可移植层。 可移植层在实现核心 FreeRTOS 功能的源文件之外, 允许提供适合于正在开发的实时系统的特定应用程序实现。 当 FreeRTOS 内核需要 RAM 时,它不调用 malloc(),而是调用 pvPortMalloc();释放 RAM 时, RTOS 内核调用 vPortFree(),而不是 free()。

FreeRTOS 提供了几种堆管理方案, 其复杂性和功能各不相同。 你也可以提供自己的堆实现, 甚至同时使用两个堆实现。 同时使用两个堆实现 允许将任务堆栈和其他 FreeRTOS 对象放置在 内部 RAM 中,并将应用程序数据放置在较慢的外部 RAM 中。

二、FreeRTOS 中管理内存的 5 种方法

前面已经提到,FreeRTOS 中内存管理的接口函数为:pvPortMalloc 、vPortFree,对应于 C 库的 malloc、free。文件在 FreeRTOS/Source/portable/MemMang 下,它也是放在 portable 目录下,表示你可以提供自己的函数。

文件优点缺点
heap_1.c分配简单,时间确定只分配、不回收
heap_2.c动态分配、最佳匹配碎片、时间不定
heap_3.c调用标准库函数速度慢、时间不定
heap_4.c相邻空闲内存可合并可解决碎片问题、时间不定
heap_5.c在 heap_4 基础上支持分隔的内存块可解决碎片问题、时间不定

1、Heap_1

它只实现了 pvPortMalloc,没有实现 vPortFree。如果你的程序不需要删除内核对象,那么可以使用 heap_1

  • 实现最简单
  • 没有碎片问题
  • 一些要求非常严格的系统里,不允许使用动态内存,就可以使用 heap_1

如果您的应用程序从未删除任务、队列、信号量、互斥锁等,则可以使用(这实际上涵盖了使用 FreeRTOS 的大多数应用程序)。始终具有确定性(总是需要相同的时间来执行),不会导致内存碎片化。非常简单,且从静态分配的数组分配内存, 这意味着它通常适合用于不允许真实动态内存分配的应用程序 。

它的实现原理很简单,首先定义一个大数组:

/* Allocate the memory for the heap. */

##if ( configAPPLICATION_ALLOCATED_HEAP == 1 )
/* The application writer has already defined the array used for the RTOS
 * heap - probably so it can be placed in a special segment or address. */
	extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
##else
	static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
##endif /* configAPPLICATION_ALLOCATED_HEAP */

然后,对于 pvPortMalloc 调用时,从这个数组中分配空间。

FreeRTOS 在创建任务时,需要 2 个内核对象:task control block(TCB)、stack。

使用 heap_1 时,内存分配过程如下图所示:

  • A:创建任务之前整个数组都是空闲的
  • B:创建第 1 个任务之后,蓝色区域被分配出去了
  • C:创建 3 个任务之后的数组使用情况

2、Heap_2

Heap_2 之所以还保留,只是为了兼容以前的代码。新设计中不再推荐使用 Heap_2。建议使用 Heap_4 来替代 Heap_2,更加高效。

Heap_2 也是在数组上分配内存,跟 Heap_1 不一样的地方在于:

  • Heap_2 使用最佳匹配算法(best fit)来分配内存
  • 它支持 vPortFree

最佳匹配算法:

  • 假设 heap 有 3 块空闲内存:5 字节、25 字节、100 字节
  • pvPortMalloc 想申请 20 字节
  • 找出最小的、能满足 pvPortMalloc 的内存:25 字节
  • 把它划分为 20 字节、5 字节
    • 返回这 20 字节的地址
    • 剩下的 5 字节仍然是空闲状态,留给后续的 pvPortMalloc 使用

Heap_4 相比,Heap_2 不会合并相邻的空闲内存,所以 Heap_2 会导致严重的"碎片化"问题。虽然不再推荐使用 Heap_2,但是它的效率还是远高于 malloc、free。

如果应用程序动态地创建和删除任务, 且分配给正在创建任务的堆栈大小总是相同的 , 那么 heap2.c 可以在大多数情况下使用。 但是, 如果分配给正在创建任务的堆栈的大小不是总相同, 那么可用的空闲内存可能会被碎片化成许多小块 , 最终导致分配失败。 在这种情况下,heap_4.c 是更好的选择。

应用程序直接调用 pvPortMalloc()vPortFree(), 而不是仅通过其他 FreeRTOS API 函数间接调用。

如果您应用程序的队列、任务、信号量、互斥锁等的顺序不可预测, 可能会导致内存碎片化。 这对几乎所有的应用程序来说都是不可能的, 但应牢记这一点。非确定性,但比大多数标准 C 库 malloc 实现更有效。

使用 heap_2 时,内存分配过程如下图所示:

  • A:创建了 3 个任务
  • B:删除了一个任务,空闲内存有 3 部分:顶层的、被删除任务的 TCB 空间、被删除任务的 Stack 空间
  • C:创建了一个新任务,因为 TCB、栈大小跟前面被删除任务的 TCB、栈大小一致,所以刚好分配到原来的内存

3、Heap_3

Heap_3 使用标准 C 库里的 malloc、free 函数,所以堆大小由链接器的配置决定,配置项 configTOTAL_HEAP_SIZE 不再起作用。

C 库里的 malloc、free 函数并非线程安全的,Heap_3 中先暂停 FreeRTOS 的调度器,再去调用这些函数,使用这种方法实现了线程安全。

使用 Heap_3 时需要链接器设置堆,需要编译器库提供 malloc() 和 free() 实现。不具有确定性,能会大大增加 RTOS 内核代码大小。

请注意,使用 heap_3 时,FreeRTOSConfig.h 中的 configTOTAL_HEAP_SIZE 设置无效 。

4、Heap_4

Heap_4 使用首次适应算法(first fit)来分配内存。它还会把相邻的空闲内存合并为一个更大的空闲内存,这有助于较少内存的碎片问题。

首次适应算法:

  • 假设堆中有 3 块空闲内存:5 字节、200 字节、100 字节
  • pvPortMalloc 想申请 20 字节
  • 找出第 1 个能满足 pvPortMalloc 的内存:200 字节
  • 把它划分为 20 字节、180 字节
    • 返回这 20 字节的地址
    • 剩下的 180 字节仍然是空闲状态,留给后续的 pvPortMalloc 使用

Heap_4 会把相邻空闲内存合并为一个大的空闲内存,可以较少内存的碎片化问题。适用于这种场景:频繁地分配、释放不同大小的内存。

可用堆空间的总量通过 configTOTAL_HEAP_SIZE(定义于 FreeRTOSConfig.h 中)设置。 提供了 configAPPLICATION_ALLOCATED_HEAP(在 FreeRTOSConfig.h 配置宏), 以允许将堆放置在内存中的特定地址。

xPortGetFreeHeapSize() 函数被调用时返回未分配的堆空间总量, xPortGetMinimumEverFreeHeapSize() 函数返回 FreeRTOS 应用程序启动的系统中已存在的最小空闲堆空间量。 这两个函数都没有提供关于未分配的 内存如何碎片化为小块的信息。

vPortGetHeapStats() 函数提供了其他信息。 它填充了一个 heap_t 结构体的成员,如下所示。

/* Prototype of the vPortGetHeapStats() function. */
void vPortGetHeapStats( HeapStats_t *xHeapStats );
 
/* Definition of the Heap_stats_t structure. */
 
typedef struct xHeapStats
{
       size_t xAvailableHeapSpaceInBytes;      /* The total heap size currently available - this is the sum of all the free blocks, not the largest block that can be allocated. */
       size_t xSizeOfLargestFreeBlockInBytes;     /* The maximum size, in bytes, of all the free blocks within the heap at the time vPortGetHeapStats() is called. */
       size_t xSizeOfSmallestFreeBlockInBytes; /* The minimum size, in bytes, of all the free blocks within the heap at the time vPortGetHeapStats() is called. */
       size_t xNumberOfFreeBlocks;            /* The number of free memory blocks within the heap at the time vPortGetHeapStats() is called. */
       size_t xMinimumEverFreeBytesRemaining; /* The minimum amount of total free memory (sum of all free blocks) there has been in the heap since the system booted. */
       size_t xNumberOfSuccessfulAllocations;   /* The number of calls to pvPortMalloc() that have returned a valid memory block. */
       size_t xNumberOfSuccessfulFrees;     /* The number of calls to vPortFree() that has successfully freed a block of memory. */
} HeapStats_t;

Heap_4 的使用过程举例如下:

  • A:创建了 3 个任务
  • B:删除了一个任务,空闲内存有 2 部分:
    • 顶层的
    • 被删除任务的 TCB 空间、被删除任务的 Stack 空间合并起来的
  • C:分配了一个 Queue,从第 1 个空闲块中分配空间
  • D:分配了一个 User 数据,从 Queue 之后的空闲块中分配
  • E:释放的 Queue,User 前后都有一块空闲内存
  • F:释放了 User 数据,User 前后的内存、User 本身占据的内存,合并为一个大的空闲内存


heap_2 实现相比,导致堆空间严重碎片化成多个小块的可能性更小(即使正在分配和释放的内存是随机大小)。不具有确定性,但比大多数标准 C 库 malloc 实现更有效。

heap_4.c 对于想在应用程序代码中直接使用可移植层内存分配方案的应用程序特别有用 (而不是 通过调用函数 pvPortMalloc()vPortFree() 来间接使用)。

Heap_4 执行的时间是不确定的,但是它的效率高于标准库的 malloc、free。

4.1 内存申请

heap_4.c 方案的内存申请函数与 heap_2.c 方案的内存申请函数大同小异,同样是从链表头 xStart 开始遍历查找合适的内存块,如果某个空闲内存块的大小能容得下用户要申请的内存,则将这块内存取出用户需要内存空间大小的部分返回给用户,剩下的内存块组成 一个新的空闲块,按照空闲内存块起始地址大小顺序插入到空闲块链表中,内存地址小的在前,内存地址大的在后。

在插入到空闲内存块链表的过程中,系统还会执行合并算法将 地址相邻的内存块进行合并:判断这个空闲内存块是相邻的空闲内存块合并成一个大内存 块,如果可以则合并,合并算法是 heap_4.c 内存管理方案和 heap_2.c 内存管理方案最大的不同之处,这样一来,会导致的内存碎片就会大大减少,内存管理方案适用性就很强,能 一样随机申请和释放内存的应用中,灵活性得到大大的提高,heap_4.c 内存初始化的源码和完成示意图具体见下图。

void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL;
	vTaskSuspendAll();
	{
		/* If this is the first call to malloc then the heap will require
		initialisation to setup the list of free blocks. */
		if( pxEnd == NULL )
		{
			prvHeapInit();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
 
		/* Check the requested block size is not so large that the top bit is
		set.  The top bit of the block size member of the BlockLink_t structure
		is used to determine who owns the block - the application or the
		kernel, so it must be free. */
		if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
		{
			/* The wanted size is increased so it can contain a BlockLink_t
			structure in addition to the requested amount of bytes. */
			if( xWantedSize > 0 )
			{
				xWantedSize += xHeapStructSize;
 
				/* Ensure that blocks are always aligned to the required number
				of bytes. */
				if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
				{
					/* Byte alignment required. */
					xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
					configASSERT( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) == 0 );
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
 
			if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )
			{
				/* Traverse the list from the start	(lowest address) block until
				one	of adequate size is found. */
				pxPreviousBlock = &xStart;
				pxBlock = xStart.pxNextFreeBlock;
				while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
				{
					pxPreviousBlock = pxBlock;
					pxBlock = pxBlock->pxNextFreeBlock;
				}
 
				/* If the end marker was reached then a block of adequate size
				was	not found. */
				if( pxBlock != pxEnd )
				{
					/* Return the memory space pointed to - jumping over the
					BlockLink_t structure at its start. */
					pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );
 
					/* This block is being returned for use so must be taken out
					of the list of free blocks. */
					pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;
 
					/* If the block is larger than required it can be split into
					two. */
					if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
					{
						/* This block is to be split into two.  Create a new
						block following the number of bytes requested. The void
						cast is used to prevent byte alignment warnings from the
						compiler. */
						pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );
						configASSERT( ( ( ( size_t ) pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 );
 
						/* Calculate the sizes of two blocks split from the
						single block. */
						pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
						pxBlock->xBlockSize = xWantedSize;
 
						/* Insert the new block into the list of free blocks. */
						prvInsertBlockIntoFreeList( pxNewBlockLink );
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
 
					xFreeBytesRemaining -= pxBlock->xBlockSize;
 
					if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining )
					{
						xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
 
					/* The block is being returned - it is allocated and owned
					by the application and has no "next" block. */
					pxBlock->xBlockSize |= xBlockAllocatedBit;
					pxBlock->pxNextFreeBlock = NULL;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
 
		traceMALLOC( pvReturn, xWantedSize );
	}
	( void ) xTaskResumeAll();
 
	#if( configUSE_MALLOC_FAILED_HOOK == 1 )
	{
		if( pvReturn == NULL )
		{
			extern void vApplicationMallocFailedHook( void );
			vApplicationMallocFailedHook();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	#endif
 
	configASSERT( ( ( ( size_t ) pvReturn ) & ( size_t ) portBYTE_ALIGNMENT_MASK ) == 0 );
	return pvReturn;
}

4.2 内存释放

heap_4.c 内存管理方案的内存释放函数 vPortFree() 也比较简单,根据传入要释放的内存块地址,偏移之后找到链表节点,然后将这个内存块插入到空闲内存块链表中,在内存块插入过程中会执行合并算法,这个我们已经在内存申请中讲过了(而且合并算法多用于释放内存中)。最后是将这个内存块标志为“空闲”(内存块节点的 xBlockSize 成员变量最高位清 0)、再更新未分配的内存堆大小即可,下面是 vPortFree 的源码实现和示意图。

void vPortFree( void *pv )
{
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;
 
	if( pv != NULL )
	{
		/* The memory being freed will have an BlockLink_t structure immediately
		before it. */
		puc -= xHeapStructSize;
 
		/* This casting is to keep the compiler from issuing warnings. */
		pxLink = ( void * ) puc;
 
		/* Check the block is actually allocated. */
		configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 );
		configASSERT( pxLink->pxNextFreeBlock == NULL );
 
		if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 )
		{
			if( pxLink->pxNextFreeBlock == NULL )
			{
				/* The block is being returned to the heap - it is no longer
				allocated. */
				pxLink->xBlockSize &= ~xBlockAllocatedBit;
 
				vTaskSuspendAll();
				{
					/* Add this block to the list of free blocks. */
					xFreeBytesRemaining += pxLink->xBlockSize;
					traceFREE( pv, pxLink->xBlockSize );
					prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
				}
				( void ) xTaskResumeAll();
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
}

调用 prvInsertBlockIntoFreeList() 函数将释放的内存块添加到空闲内存块链表中,在这过程中,如果内存块可以合并就会进行内存块合并,否则就单纯插入空闲内存块链表(按内存地址排序)。 按照内存释放的过程,当我们释放一个内存时,如果与它相邻的内存块都不是空闲的, 那么该内存块并不会合并,只会被添加到空闲内存块链表中;而如果某个时间段释放了另一个内存块,发现该内存块前面有一个空闲内存块与它在地址上是连续的,那么这两个内存块会合并成一个大的内存块,并插入空闲内存块链表 中。整个过程示意图具体见下图:

下图是释放一个无法合并的内存块:

下图是释放一个可以合并的内存块:

5、Heap_5

Heap_5 分配内存、释放内存的算法跟 Heap_4 是一样的。相比于 Heap_4Heap_5 并不局限于管理一个大数组:它可以管理多块、分隔开的内存。

在嵌入式系统中,内存的地址可能并不连续,这种场景下可以使用 Heap_5。既然内存时分隔开的,那么就需要进行初始化:确定这些内存块在哪、多大:

  • 在使用 pvPortMalloc 之前,必须先指定内存块的信息
  • 使用 vPortDefineHeapRegions 来指定这些信息
typedef struct HeapRegion
{
	uint8_t * pucStartAddress; // 起始地址
	size_t xSizeInBytes;       // 大小
} HeapRegion_t;

使用一个 HeapRegion_t 数组可以指定多块地址,在这个数组中,低地址在前、高地址在后。比如:

HeapRegion_t xHeapRegions[] =
{
	{ ( uint8_t * ) 0x80000000UL, 0x10000 }, // 起始地址0x80000000,大小0x10000
	{ ( uint8_t * ) 0x90000000UL, 0xa0000 }, // 起始地址0x90000000,大小0xa0000
	{ NULL, 0 } // 表示数组结束
};

vPortDefineHeapRegions 函数原型如下:

void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );

xHeapRegions 数组传给 vPortDefineHeapRegions 函数,即可初始化 Heap_5

三、Heap 相关的函数

1、pvPortMalloc/vPortFree

函数原型:

void * pvPortMalloc( size_t xWantedSize );
void vPortFree( void * pv );

作用:分配内存、释放内存。如果分配内存不成功,则返回值为 NULL。

2、xPortGetFreeHeapSize

函数原型:

size_t xPortGetFreeHeapSize( void );

当前还有多少空闲内存,这函数可以用来优化内存的使用情况。比如当所有内核对象都分配好后,执行此函数返回 2000,那么 configTOTAL_HEAP_SIZE 就可减小 2000。

注意:在 heap_3 中无法使用。

3、xPortGetMinimumEverFreeHeapSize

函数原型:

size_t xPortGetMinimumEverFreeHeapSize( void );

返回:程序运行过程中,空闲内存容量的最小值。

注意:只有 heap_4heap_5 支持此函数。

4、malloc 失败的钩子函数

pvPortMalloc 函数内部:

void * pvPortMalloc( size_t xWantedSize )vPortDefineHeapRegions
{
	......
	#if ( configUSE_MALLOC_FAILED_HOOK == 1 )
	{
		if( pvReturn == NULL )
		{
			extern void vApplicationMallocFailedHook( void );
			vApplicationMallocFailedHook();
		}
	}
	#endif
	return pvReturn;
}

所以,如果想使用这个钩子函数,需要注意以下几点:

  • FreeRTOSConfig.h 中,把 configUSE_MALLOC_FAILED_HOOK 定义为 1
  • 提供 vApplicationMallocFailedHook 函数
  • pvPortMalloc 失败时,才会调用此函数

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

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

相关文章

CDGA|数据治理,就像在厨房里炒一盘好菜

数据治理&#xff0c;就像在厨房里炒一盘好菜&#xff0c;是一门既讲究技巧又注重细节的艺术。在这个信息爆炸的时代&#xff0c;数据如同食材&#xff0c;是支撑企业决策、优化运营、驱动创新的基石。而数据治理&#xff0c;则是将这些纷繁复杂的数据“食材”精心挑选、清洗、…

使用NPS搭建socks5隧道 | 内网穿透

在看春秋云镜靶场的WP时碰到用NPS来做代理的&#xff0c;这里刚好看到这篇文章&#xff1a;https://www.cnblogs.com/cute-puli/p/15508251.html&#xff0c;学习一下。 GUI界面管理更加方便。 实验环境 网络拓扑&#xff1a; kali&#xff1a; VMnet1&#xff08;公网&…

ORB-SLAM3演示及运行

ORB-SLAM安装完成后的运行案例 ros启动 1、修改双目部分文件&#xff0c;主要是图象订阅话题名称 因为我是用的双目灰度相机&#xff0c;需要修改ORB_SLAM3/Examples/ROS/ORB_SLAM3/src下的 ros_stereo.cc和 ros_stereo_intertial.cc. 把订阅的话题改为自己系统发布的图象…

盲盒小程序开发,创新市场收益渠道

对于年轻消费者来说&#xff0c;盲盒是一个具有超强惊喜感和刺激性的消费方式&#xff0c;盲盒也在各大社交平台迅速火爆&#xff0c;线下门店更是上演“大长龙”式的排队景观&#xff0c;盲盒成为了一个具有非常大发展前景的行业&#xff01; 一、线上发展 盲盒的销售渠道除…

矩阵和神经网络的优雅与力量-《Python神经网络编程》读后感

《Python神经网络编程》是一本非常优秀的神经网络入门编程书&#xff0c;作者手把手从安装环境开始&#xff0c;每一行代码都是在树莓派上就能运行的&#xff0c;甚至可以说不需要什么第三方库&#xff0c;仅仅用了矩阵的优雅和力量&#xff0c;就能够在树莓派上顺利的运行。 …

并发编程 | CountDownLatch是如何控制线程执行流程

CountDownLatch 是 Java 并发编程中的一个同步工具类&#xff0c;这个工具经常用来用来协调多个线程之间的同步&#xff0c;下面我们就来一起认识一下 CountDownLatch。 CountDownLatch介绍 应用场景 CountDownLatch主要是用于让一个或多个线程等待其他线程完成某些操作后再…

鸿蒙Harmony实战开发知识:“UIAbility组件的3种启动模式”

UIAbility的启动模式是指UIAbility实例在启动时的不同呈现状态。针对不同的业务场景&#xff0c;系统提供了三种启动模式&#xff1a; singleton启动模式 singleton启动模式为单实例模式&#xff0c;也是默认情况下的启动模式。 每次调用startAbility()方法时&#xff0c;如…

windows下的redis7.0.11的下载

天&#xff0c;我找redis7.0.11的安装包就找了好久&#xff0c;终于给我找到了。市面上好多是linux版本的。 安装包&#xff1a;Release Redis 7.0.11 for Windows zkteco-home/redis-windows GitHub 解压之后是这样的。 然后你要测试能不能启动&#xff1a; 1、指定配置文…

复现DOM型XSS攻击(1-8关)

目录 第一关&#xff1a;​ 分析代码&#xff1a; 第二关&#xff1a; 分析代码&#xff1a; 第三关&#xff1a; 分析代码&#xff1a; 第四关&#xff1a; 分析代码&#xff1a; 第五关&#xff1a; 分析代码&#xff1a; 第六关&#xff1a; 分析代码&#xff1…

volatitle-线程并发-小白一文速通

目录 简而言之 专业术语解释 1、可见性 原理简介 原理图解 其他方式 2、原子性 原理简介 结合实例分析 3、有序性 原理简介 线程安全问题 Volatile效果 1、保证可见性 2、保证有序性 3、无法保证原子性 Volatile底层的实现机制(重点掌握) 经典案例 Java双重检…

朗致面试----Java开发、Java架构师

一共三轮面试。第一轮是逻辑行测&#xff0c;第二轮是技术面试&#xff08;面试官-刘老师&#xff09;&#xff0c;第三轮是CTO面试&#xff08;面试官-屠老师&#xff09;。第三轮Coding做完之后共享屏幕讲一个你自己负责过的项目&#xff08;请提前准备好架构图&#xff0c;开…

【AI趋势8】具身智能

随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;已经从概念走向实际应用&#xff0c;深刻影响着我们的生产和生活方式。在众多AI技术载体中&#xff0c;人型机器人凭借其独特的类人形态和全身自由度&#xff0c;成为人工智能领域的终极载体之一。本文将深入…

系列:水果甜度个人手持设备检测-产品规划汇总与小结

系列:水果甜度个人手持设备检测 -- 产品规划汇总与小结 背景 接上一篇&#xff0c;我们从假设的用户需求出发&#xff0c;规划输出软硬件结合的一体化产品。在产品中搭载近红外光谱&#xff08;NIR&#xff09;、超声检测的模块&#xff0c;并针对不同的瓜果类型&#xff0c…

【开源分享】CommLite 跨平台文本UI串口调试助手

文章目录 1. 简介2. 编译3. 使用4. 借鉴&思考参考 1. 简介 CommLite是一款基于CSerialPort的文本UI串口调试助手。 gitee仓库 2. 编译 编译非常简单&#xff0c;按照文档操作即可&#xff1a; $ git clone --depth1 https://github.com/itas109/CommLite.git $ cd Comm…

数据结构----AVL树

小编会一直更新数据结构相关方面的知识&#xff0c;使用的语言是Java&#xff0c;但是其中的逻辑和思路并不影响&#xff0c;如果感兴趣可以关注合集。 希望大家看完之后可以自己去手敲实现一遍&#xff0c;同时在最后我也列出一些基本和经典的题目&#xff0c;可以尝试做一下。…

牛客网习题——通过C++实现

一、目标 实现下面4道练习题增强C代码能力。 1.求123...n_牛客题霸_牛客网 (nowcoder.com) 2.计算日期到天数转换_牛客题霸_牛客网 (nowcoder.com) 3.日期差值_牛客题霸_牛客网 (nowcoder.com) 4.打印日期_牛客题霸_牛客网 (nowcoder.com) 二、对目标的实现 1.求123...n_…

Java二十三种设计模式-访问者模式(21/23)

本文深入探讨了访问者模式&#xff0c;一种允许向对象结构添加新操作而不修改其本身的设计模式&#xff0c;涵盖了其定义、组成部分、实现方式、使用场景、优缺点、与其他模式的比较&#xff0c;以及最佳实践和替代方案。 访问者模式&#xff1a;为对象结构添加新的操作 引言 …

黑神话:悟空-配置推荐

显卡推荐&#xff08;按类别整理&#xff09; 1. GTX 10系列、GTX 16系列&#xff1a; 如果希望体验光线追踪&#xff0c;建议根据预算升级到RTX 40系列显卡。对于1080p分辨率&#xff0c;至少需要RTX 4060才能流畅运行。 2. RTX 20系列&#xff1a; RTX 2060、RTX 2070&#…

Openboxes 移动终端APP项目开发环境搭建与调试

文章目录 前言项目简介APP开发环境搭建APP开发环境启动及调试主应用程序启动及调试结语 前言 openboxes 项目还有一个针对移动端的项目&#xff1a;openboxes-mobile&#xff0c;但是这个项目的默认分支&#xff08;develop&#xff09;并没有与openboxes的默认分支对应&#…

LabVIEW优化内存使用

在LabVIEW中&#xff0c;优化内存使用的关键在于理解LabVIEW的内存管理机制并采用一些最佳实践。以下是一些可能帮助减少内存占用的方法&#xff1a; 1. 减少数据副本的生成 避免不必要的数据复制&#xff1a;每当你在程序中传递数组或子数组时&#xff0c;LabVIEW可能会创建副…