ESP32 碰上内存分配问题

1、背景

看图片

_calloc_r ->_malloc_r ->heap_caps_malloc_default->heap_caps_malloc->multi_heap_malloc->multi_heap_malloc_impl->get_next_block

/* Return the next sequential block in the heap.
 */
static inline heap_block_t *get_next_block(const heap_block_t *block)
{
    intptr_t next = block->header & NEXT_BLOCK_MASK;
    if (next == 0) {
        return NULL; /* last_block */
    }
    assert(next > (intptr_t)block);
    return (heap_block_t *)next;
}

在分配堆内存时,碰上了这个断言,触发应用程序反复重启。

 1.1 参考文档

ESP32 程序的内存模型 - 知乎MCU 中的内存资源可能是其最宝贵的资源,因为它在芯片中占据最大的面积。更新的应用程序对内存的需求正在不断增长。为了充分利用硬件资源,理解内存架构并能针对应用程序的实际用例进行内存优化变得至关重要。特别…icon-default.png?t=N7T8https://zhuanlan.zhihu.com/p/345915256?utm_id=0

https://www.cnblogs.com/sinferwu/p/17253138.htmlicon-default.png?t=N7T8https://www.cnblogs.com/sinferwu/p/17253138.html

esp32 heap 内存管理简析-CSDN博客文章浏览阅读1.2w次,点赞2次,收藏29次。嵌入式系统运行时的内存情况是非常值得关注的。本文档用于分析乐鑫ESP32 SDK(版本esp-idf-v3.0-rc1) Heap (堆内存)管理的实现。 1:Heap管理主要函数接口与数据结构 1.1主要函数接口ESP32的SDK对于heap部分管理的源码位于路径\esp-idf-v3.0-rc1\components\heap下,可以简单的认为分为两层:heap_caps_init.c与hea..._esp32 heaphttps://blog.csdn.net/abc517789065/article/details/79680214https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/system/mem_alloc.htmlicon-default.png?t=N7T8https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/system/mem_alloc.html

2.理论知识

2.0 内存管理

 ESP-IDF 应用程序使用常见的计算机架构模式:由程序控制流动态分配的内存(即 )、由函数调用动态分配的内存(即 )以及在编译时分配的 静态内存

由于 ESP-IDF 是一个多线程 RTOS 环境(FreeRTOS的变种),因此每个 RTOS 任务都有自己的栈,这些栈默认在创建任务时从堆中分配。在FreeRTOS的实现中,每个任务用到两个内存块,其中一块用来保存任务自身的数据结构--类似Task_t(process control block);另一块内存块用作任务的栈。xTaskCreate()两个内存块都是内部实现从堆中动态分配的,而xTaskCreateStatic的两个内存块都需要程序控制流作为参数传递给它。

2.1 ESP32内存类型和特性

ESP32包含多种类型的RAM

DRAM:是连接到 CPU 数据总线上的内存,用于存储数据。这是作为堆访问最常见的一种内存(用作bss段、data段和堆)。

IRAM:是连接到 CPU 指令总线上的内存,通常仅用于存储可执行数据(即指令,text段)。如果作为通用内存访问,则所有访问必须为 32 位可访问内存。---4字节地址严格对齐

D/IRAM:连接到 CPU 数据总线和指令总线的 RAM,因此可用作指令 RAM 或数据 RAM

ESP32 还可外扩SPI RAM,通过缓存将 片外 RAM 集成到 ESP32 的内存映射中,访问方式与 DRAM 类似。

2.2 ESP32内存图

520K SRAM = 192KSRAM0 + 128KSRAM1 +200KSRAM2

由上图可知,SRAM0被用作IRAM,SRAM2被用作DRAM,SRAM1既可被用作IRAM,也可被用作DRAM。SRAM1默认被用作DRAM,以补充应用程序中数据空间的紧缺。

2.3 基于内存属性的堆内存分配器

ESP32 使用多种类型的 RAM,因此具备不同属性的堆,即基于属性的内存分配器允许应用程序按不同目的进行堆分配。

2.3.0 堆内存分配器管理的DRAM大小和初始化过程

启动时,DRAM 堆包含应用程序未静态分配的所有数据内存,减少静态分配的缓冲区将增加可用的空闲堆空间。静态分配和其他特殊目的保留的内存外,其他内存均受到堆内存分配器的管理。

/* Initialize the heap allocator to use all of the memory not
   used by static data or reserved for other purposes
 */
void heap_caps_init()

 系统调用heap_caps_init实现Heap空间初始化的过程,实质就是初始heap_t数据结构的过程。简单的看可以分为三个步骤:

<1> 遍历当前系统的可用内存区域,并对类型相同的相邻区域进行合并

<2> 将可用内存区域通过register_heap初始化为Heap分区的结构(建立Meta Data头以及数据区的链表)

<3> 将所有Heap分区信息通过链表连接到registered_heaps

2.3.0.1 步骤1

heap_caps_init中的关键代码段1

    /* Get the array of regions that we can use for heaps
       (with reserved memory removed already.)
     */
    size_t num_regions = soc_get_available_memory_region_max_count();
    soc_memory_region_t regions[num_regions];
    num_regions = soc_get_available_memory_regions(regions);

    //The heap allocator will treat every region given to it as separate. In order to get bigger ranges of contiguous memory,
    //it's useful to coalesce adjacent regions that have the same type.
    for (int i = 1; i < num_regions; i++) {
        soc_memory_region_t *a = &regions[i - 1];
        soc_memory_region_t *b = &regions[i];
        if (b->start == a->start + a->size && b->type == a->type ) {
            a->type = -1;
            b->start = a->start;
            b->size += a->size;
        }
    }

    /* Count the heaps left after merging */
    size_t num_heaps = 0;
    for (int i = 0; i < num_regions; i++) {
        if (regions[i].type != -1) {
            num_heaps++;
        }
    }

 其中有一个关键的数据结构体soc_memory_region_t,其成员包括region的起始地址、大小、类型。

/* Region descriptor holds a description for a particular region of memory on a particular SoC.
 */
typedef struct
{
    intptr_t start;  ///< Start address of the region
    size_t size;            ///< Size of the region in bytes
    size_t type;             ///< Type of the region (index into soc_memory_types array)
    intptr_t iram_address; ///< If non-zero, is equivalent address in IRAM
} soc_memory_region_t;

什么叫类型相同的相邻区域,就是前region和当前region的内存类型相同,前region的起始地址+大小=当前region的起始地址,那么就是类型相同的相邻区域,可以合并。

2.3.0.2 步骤2
/* Type for describing each registered heap */
typedef struct heap_t_ {
    uint32_t caps[SOC_MEMORY_TYPE_NO_PRIOS]; ///< Capabilities for the type of memory in this heap (as a prioritised set). Copied from soc_memory_types so it's in RAM not flash.
    intptr_t start;
    intptr_t end;
    portMUX_TYPE heap_mux;
    multi_heap_handle_t heap;
    SLIST_ENTRY(heap_t_) next;
} heap_t;

关键数据结构heap_t,描述注册heap的类型,成员有caps--功能,起始地址和结束地址、互斥锁等等

  ESP_EARLY_LOGI(TAG, "Initializing. RAM available for dynamic allocation:");
    for (int i = 0; i < num_regions; i++) {
        soc_memory_region_t *region = &regions[i];
        const soc_memory_type_desc_t *type = &soc_memory_types[region->type];
        heap_t *heap = &temp_heaps[heap_idx];
        if (region->type == -1) {
            continue;
        }
        heap_idx++;
        assert(heap_idx <= num_heaps);

        memcpy(heap->caps, type->caps, sizeof(heap->caps));
        heap->start = region->start;
        heap->end = region->start + region->size;
        vPortCPUInitializeMutex(&heap->heap_mux);
        if (type->startup_stack) {
            /* Will be registered when OS scheduler starts */
            heap->heap = NULL;
        } else {
            register_heap(heap);
        }
        SLIST_NEXT(heap, next) = NULL;

        ESP_EARLY_LOGI(TAG, "At %08X len %08X (%d KiB): %s",
                       region->start, region->size, region->size / 1024, type->name);
    }

关键在于register_heap(),注册这些可用region

2.3.0.3 步骤3

找到第一个DRAM,分配永久堆数据,用作运行时的链表--数组。设置锁并将之加入到链表中。

 heap_t *heaps_array = NULL;
    for (int i = 0; i < num_heaps; i++) {
        if (heap_caps_match(&temp_heaps[i], MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL)) {
            /* use the first DRAM heap which can fit the data */
            heaps_array = multi_heap_malloc(temp_heaps[i].heap, sizeof(heap_t) * num_heaps);
            if (heaps_array != NULL) {
                break;
            }
        }
    }
    assert(heaps_array != NULL); /* if NULL, there's not enough free startup heap space */

    memcpy(heaps_array, temp_heaps, sizeof(heap_t)*num_heaps);

    /* Iterate the heaps and set their locks, also add them to the linked list. */
    for (int i = 0; i < num_heaps; i++) {
        if (heaps_array[i].heap != NULL) {
            multi_heap_set_lock(heaps_array[i].heap, &heaps_array[i].heap_mux);
        }
        if (i == 0) {
            SLIST_INSERT_HEAD(&registered_heaps, &heaps_array[0], next);
        } else {
            SLIST_INSERT_AFTER(&heaps_array[i-1], &heaps_array[i], next);
        }
    }

 有一个疑问,这个heap_t 链表是不能扩容的,用来表述可用的堆空间。这个链表和后面分配的总链表和free链表有什么关系?

2.3.0.4  void register_heap(heap_t *region)

register_heap(heap)

        ->multi_heap_register((void *)region->start, heap_size)

                ->multi_heap_register_impl(start, size)

register_heap将普通的内存区域初始化为Heap分区结构,multi_heap_register_impl是其最终实现功能的接口. 


multi_heap_handle_t multi_heap_register_impl(void *start_ptr, size_t size)
{
    uintptr_t start = ALIGN_UP((uintptr_t)start_ptr);
    uintptr_t end = ALIGN((uintptr_t)start_ptr + size);
    heap_t *heap = (heap_t *)start;
    size = end - start;

    if (end < start || size < sizeof(heap_t) + 2*sizeof(heap_block_t)) {
        return NULL; /* 'size' is too small to fit a heap here */
    }
    heap->lock = NULL;
    heap->last_block = (heap_block_t *)(end - sizeof(heap_block_t));

    /* first 'real' (allocatable) free block goes after the heap structure */
    heap_block_t *first_free_block = (heap_block_t *)(start + sizeof(heap_t));
    first_free_block->header = (intptr_t)heap->last_block | BLOCK_FREE_FLAG;
    first_free_block->next_free = heap->last_block;

    /* last block is 'free' but has a NULL next pointer */
    heap->last_block->header = BLOCK_FREE_FLAG;
    heap->last_block->next_free = NULL;

    /* first block also 'free' but has legitimate length,
       malloc will never allocate into this block. */
    heap->first_block.header = (intptr_t)first_free_block | BLOCK_FREE_FLAG;
    heap->first_block.next_free = first_free_block;

    /* free bytes is:
       - total bytes in heap
       - minus heap_t header at top (includes heap->first_block)
       - minus header of first_free_block
       - minus whole block at heap->last_block
    */
    heap->free_bytes = size - sizeof(heap_t) - sizeof(first_free_block->header) - sizeof(heap_block_t);
    heap->minimum_free_bytes = heap->free_bytes;

    return heap;
}

发现了吧返回值类型是multi_heap_handle_t,在函数实现中用的是heap_t,怎么回事?

typedef struct multi_heap_info *multi_heap_handle_t;

/* Block in the heap

   Heap implementation uses two single linked lists, a block list (all blocks) and a free list (free blocks).

   'header' holds a pointer to the next block (used or free) ORed with a free flag (the LSB of the pointer.) is_free() and get_next_block() utility functions allow typed access to these values.

   'next_free' is valid if the block is free and is a pointer to the next block in the free list.
*/
typedef struct heap_block {
    intptr_t header;                  /* Encodes next block in heap (used or unused) and also free/used flag */
    union {
        uint8_t data[1];              /* First byte of data, valid if block is used. Actual size of data is 'block_data_size(block)' */
        struct heap_block *next_free; /* Pointer to next free block, valid if block is free */
    };
} heap_block_t;

/* These masks apply to the 'header' field of heap_block_t */
#define BLOCK_FREE_FLAG 0x1  /* If set, this block is free & next_free pointer is valid */
#define NEXT_BLOCK_MASK (~3) /* AND header with this mask to get pointer to next block (free or used) */

/* Metadata header for the heap, stored at the beginning of heap space.

   'first_block' is a "fake" first block, minimum length, used to provide a pointer to the first used & free block in
   the heap. This block is never allocated or merged into an adjacent block.

   'last_block' is a pointer to a final free block of length 0, which is added at the end of the heap when it is
   registered. This block is also never allocated or merged into an adjacent block.
 */
typedef struct multi_heap_info {
    void *lock;
    size_t free_bytes;
    size_t minimum_free_bytes;
    heap_block_t *last_block;
    heap_block_t first_block; /* initial 'free block', never allocated */
} heap_t;

 一段可用的连续内存被初始化为Heap分区时,分区起始地址会写入一个MetaData头(即是multi_heap_info类型)。MetaData头后紧接着是第一个真正可以用来分配的内存块block0,block0包含一个信息头heap_block_t以及实际数据区。

在Heap分区的最后,有一个heap_block_t的信息头作为整个分区的结尾。heap_block_t结构中有指向下个节点的指针,这样分区所有block组成链表用于访问

最后可用的就是Heap_Block0_DATA的。 

由此可知,管理内存就通过这两级。先通过遍历heap_t链表查找符合申请内存类型的heap_t,在从heap遍历以找到最优满足大小的Heap_Block。

2.3.1 Heap空间分配

Heap空间是系统运行期间用户通过函数申请的,如何分配与释放是关键问题。本节描述关于Heap空间如何分配的问题?

2.3.1.3 C标准函数库malloc()和free()----MALLOC_CAP_DEFAULT属性

调用 malloc() 时,ESP-IDF malloc() 内部调用 heap_caps_malloc_default(size),使用属性 MALLOC_CAP_DEFAULT 分配内存。该属性可实现字节寻址功能,即存储空间的最小编址单位为字节。

对于Heap空间的分配过程,通过对代码的理解,可以简单的概括为三个步骤:

当用户向系统通过malloc申请Heap内存空间时:

<1>系统会遍历每个Heap分区(通过registered_heaps),找到满足申请内存类型(如MALLOC_CAP_DEFAULT属性)的分区;再依次遍历分区下所有可用的Heap Block,找到可用空间满足申请内存大小的Heap Block。

<2>遍历所有Heap Block期间,会出现几种可能:

        A:若Heap Block的可用空间小于用户申请的内存大小,显然不能使用,继续查找下一个Heap Block;

        B:若Heap Block的可用空间正好等于用户申请的内存大小,这时系统出现了最佳的分配选择,策略选择该Heap Block作为本次分配的Best Heap Block,停止遍历

        C:若Heap Block的可用空间大于用户申请的内存大小,则先行记录下这个Heap Block的位置以及它的实际可用空间大小,然后继续遍历查找;在找到下一个满足条件的Heap Block时,比较两者,选取可用空间较小的记录下来。策略的目的即是保证最终得到一个既满足用户申请需求又最不浪费空间的Best Heap Block。

<3>对应步骤<2>选出的Best Heap Block:

        A:若Best HeapBlock不存在,意味着用户申请失败;

        B:若Best HeapBlock空间正好满足用户申请的需求,则无需另做处理,直接给到用户即可;

        C:若Best HeapBlock的空间除去用户申请的需求还有盈余,则需要进行分割。盈余部分需要分割成为新的可用(空闲)Heap Block,等待下次内存申请。

三次分配,从初始化、分配SpaceA、SpaceB、SpaceC。

3、问题分析

回归到背景中问题,malloc分配中出现问题_calloc_r,从中看出调用关系

_calloc_r ->

        _malloc_r ->

                heap_caps_malloc_default->

                        heap_caps_malloc->

                                multi_heap_malloc->

                                        multi_heap_malloc_impl->

                                                split_if_necessary->

                                                        get_next_block

void *multi_heap_malloc_impl(multi_heap_handle_t heap, size_t size)
{
    heap_block_t *best_block = NULL;
    heap_block_t *prev_free = NULL;
    heap_block_t *prev = NULL;
    size_t best_size = SIZE_MAX;
    size = ALIGN_UP(size);

    if (size == 0 || heap == NULL) {
        return NULL;
    }

    multi_heap_internal_lock(heap);

    /* Note: this check must be done while holding the lock as both
       malloc & realloc may temporarily shrink the free_bytes value
       before they split a large block. This can result in false negatives,
       especially if the heap is unfragmented.
    */
    if (heap->free_bytes < size) {
        MULTI_HEAP_UNLOCK(heap->lock);
        return NULL;
    }

    /* Find best free block to perform the allocation in */
    prev = &heap->first_block;
    for (heap_block_t *b = heap->first_block.next_free; b != NULL; b = b->next_free) {
        MULTI_HEAP_ASSERT(b > prev, &prev->next_free); // free blocks should be ascending in address
        MULTI_HEAP_ASSERT(is_free(b), b); // block should be free
        size_t bs = block_data_size(b);
        if (bs >= size && bs < best_size) {
            best_block = b;
            best_size = bs;
            prev_free = prev;
            if (bs == size) {
                break; /* we've found a perfect sized block */
            }
        }
        prev = b;
    }

    if (best_block == NULL) {
        multi_heap_internal_unlock(heap);
        return NULL; /* No room in heap */
    }

    prev_free->next_free = best_block->next_free;
    best_block->header &= ~BLOCK_FREE_FLAG;

    heap->free_bytes -= block_data_size(best_block);

    split_if_necessary(heap, best_block, size, prev_free);

    if (heap->free_bytes < heap->minimum_free_bytes) {
        heap->minimum_free_bytes = heap->free_bytes;
    }

    multi_heap_internal_unlock(heap);

    return best_block->data;
}

/* Split a block so it can hold at least 'size' bytes of data, making any spare
   space into a new free block.

   'block' should be marked in-use when this function is called (implementation detail, this function
   doesn't set the next_free pointer).

   'prev_free_block' is the free block before 'block', if already known. Can be NULL if not yet known.
   (This is a performance optimisation to avoid walking the freelist twice when possible.)
*/
static void split_if_necessary(heap_t *heap, heap_block_t *block, size_t size, heap_block_t *prev_free_block)
{
    const size_t block_size = block_data_size(block);
    MULTI_HEAP_ASSERT(!is_free(block), block); // split block shouldn't be free
    MULTI_HEAP_ASSERT(size <= block_size, block); // size should be valid
    size = ALIGN_UP(size);

    /* can't split the head or tail block */
    assert(!is_first_block(heap, block));
    assert(!is_last_block(block));

    heap_block_t *new_block = (heap_block_t *)(block->data + size);
    heap_block_t *next_block = get_next_block(block);

    if (is_free(next_block) && !is_last_block(next_block)) {
        /* The next block is free, just extend it upwards. */
        new_block->header = next_block->header;
        new_block->next_free = next_block->next_free;
        if (prev_free_block == NULL) {
            prev_free_block = get_prev_free_block(heap, block);
        }
        /* prev_free_block should point to the next block (which we found to be free). */
        MULTI_HEAP_ASSERT(prev_free_block->next_free == next_block,
                          &prev_free_block->next_free); // free blocks should be in order
        /* Note: We have not introduced a new block header, hence the simple math. */
        heap->free_bytes += block_size - size;
#ifdef MULTI_HEAP_POISONING_SLOW
        /* next_block header needs to be replaced with a fill pattern */
        multi_heap_internal_poison_fill_region(next_block, sizeof(heap_block_t), true /* free */);
#endif
    } else {
        /* Insert a free block between the current and the next one. */
        if (block_data_size(block) < size + sizeof(heap_block_t)) {
            /* Can't split 'block' if we're not going to get a usable free block afterwards */
            return;
        }
        if (prev_free_block == NULL) {
            prev_free_block = get_prev_free_block(heap, block);
        }
        new_block->header = block->header | BLOCK_FREE_FLAG;
        new_block->next_free = prev_free_block->next_free;
        /* prev_free_block should point to a free block after new_block */
        MULTI_HEAP_ASSERT(prev_free_block->next_free > new_block,
                          &prev_free_block->next_free); // free blocks should be in order
        heap->free_bytes += block_data_size(new_block);
    }
    block->header = (intptr_t)new_block;
    prev_free_block->next_free = new_block;
}

看看heap_block这个结构体,包括一个总的block 链表和一个空闲block列表

/* Block in the heap

   Heap implementation uses two single linked lists, a block list (all blocks) and a free list (free blocks).

   'header' holds a pointer to the next block (used or free) ORed with a free flag (the LSB of the pointer.) is_free() and get_next_block() utility functions allow typed access to these values.

   'next_free' is valid if the block is free and is a pointer to the next block in the free list.
*/
typedef struct heap_block {
    intptr_t header;                  /* Encodes next block in heap (used or unused) and also free/used flag */
    union {
        uint8_t data[1];              /* First byte of data, valid if block is used. Actual size of data is 'block_data_size(block)' */
        struct heap_block *next_free; /* Pointer to next free block, valid if block is free */
    };
} heap_block_t;

block的信息被损坏,查看block信息。

3.1 问题原因和解决方法

原因在于内存溢出,申请的空间不足放下,强制填入导致后一个block的信息被破坏。

检查每个内存申请。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Web前端—移动Web第四天(vw适配方案、vw和vh的基本使用、综合案例-酷我音乐)

版本说明 当前版本号[20231122]。 版本修改说明20231122初版 目录 文章目录 版本说明目录移动 Web 第四天01-vw适配方案vw和vh基本使用vw布局vh布局混用问题 02-综合案例-酷我音乐准备工作头部布局头部内容搜索区域banner 区域标题公共样式排行榜内容推荐歌单布局推荐歌单内…

rsync配置和守护进程实践

目录 一、rsync概念 1.rsync简介 2.rsync特点 3、增量和全局传输 二、Rsync工作方式 1.准备好rsync备份服务器 2.本地的数据传输模式 3.远程的数据传输模式 4.rsync数据推拉模式 三、实践 1.准备三台虚拟机 2.都安装rsync服务 3.拉取远程文件 3.推送文件 4.rsyn…

使用云 SIEM 解决方案保护IT 基础设施

什么是云 SIEM 基于云的 SIEM 解决方案将 SIEM 功能作为服务提供&#xff0c;云 SIEM 解决方案可保护您的网络;提供威胁情报&#xff0c;提供用于检测、优先处理和解决安全事件的控制台&#xff0c;并帮助您遵守法规要求。云 SIEM 解决方案在管理本地和云环境的网络安全时提供…

java.lang.ArrayIndexOutOfBoundsException: (数组越界异常)

java.lang.ArrayIndexOutOfBoundsException: &#xff08;数组越界异常&#xff09; 如何解决数组越界异常&#xff1f;1.1条件判断1.2循环结构1.3 try-catch&#xff08;异常捕获&#xff09;避免数组越界异常的方法&#xff1a;数组越界异常的调试和排查技巧&#xff1a; 当我…

RAAGR2-Net:一种使用多个空间帧的并行处理的脑肿瘤分割网络

RAAGR2-Net: A brain tumor segmentation network using parallel processing of multiple spatial frames RAAGR2-Net&#xff1a;一种使用多个空间帧的并行处理的脑肿瘤分割网络背景贡献实验N4 bias-field-correction 数据预处理Z-score and re-sampling Z-score归一化&#…

给定一个非严格递增排列的有序数组,删除数组中的重复项

实例要求&#xff1a;1、给定一个非严格递增排列的有序数组 nums &#xff1b;2、原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff1b;3、返回删除后数组的新长度&#xff1b;4、元素的 相对顺序 应该保持 一致 &#xff1b;5、然后返回 nums 中唯一元素的…

C++一个关于delete的幼稚错误

分析 在项目开发中&#xff0c;遇上这么一个错误&#xff1a; 0xc00000fd stack_overflow 这是一个栈溢出的错误&#xff0c;奇怪&#xff0c;我delete怎么会提示这么一个错误呢&#xff1f; 与是问题了群&#xff1a; 总结 这个错误实在是蠢啊&#xff01; 没有细想&am…

含分布式电源的配电网可靠性评估(matlab代码)

1主要内容 该程序参考《基于仿射最小路法的含分布式电源配电网可靠性分析》文献方法&#xff0c;通过概率模型和时序模型分别进行建模&#xff0c;实现基于概率模型最小路法的含分布式电源配电网可靠性评估以及时序模型序贯蒙特卡洛模拟法的含分布式电源配电网可靠性评估。程序…

centos 安装k8s教程(一键安装k8s)

第一步 准备几台机器 第二步 K8s Manager 服务器中添加docker支持 安装教程请查看这个博客 docker 安装详细教程 点我 第三步安装 KuboardSpray 教程在这里 第四步 下载k8s资源包 第五步 安装k8s 点击安装后 显示如下&#xff1a;等待完成

年底了,我劝大家真别轻易离职...

年底了&#xff0c;一些不满现状&#xff0c;被外界的“高薪”“好福利”吸引的人&#xff0c;一般就在这时候毅然决然地跳槽了。 在此展示一套学习笔记 / 面试手册&#xff0c;年后跳槽的朋友可以好好刷一刷&#xff0c;还是挺有必要的&#xff0c;它几乎涵盖了所有的软件测试…

Web自动化测试流程:从入门到精通,帮你成为测试专家!

Web应用程序在今天的软件开发中占据着越来越重要的地位。保证Web应用程序的质量和稳定性是非常必要的&#xff0c;而自动化测试是一种有效的方法。本文将介绍Web自动化测试流程&#xff0c;并提供代码示例。 步骤一&#xff1a;选取测试工具 选择适合自己团队的自动化测试工具…

极限学习机

极限学习机&#xff08;ELM, Extreme Learning Machines&#xff09;是一种前馈神经网络&#xff0c;ELM 不需要基于梯度的反向传播来调整权重&#xff0c;而是通过 Moore-Penrose generalized inverse来设置权值。 标准的单隐藏层神经网络结构如下&#xff1a; 单隐藏层神经…

ETL-使用kettle批量复制sqlserver数据到mysql数据库

文章标题 1、安装sqlserver数据库2、下载kettle3、业务分析4、详细流程&#xff08;1&#xff09;转换1&#xff1a;获取sqlserver所有表格名字&#xff0c;将记录复制到结果&#xff08;2&#xff09;转换2&#xff1a;从结果设置变量&#xff08;3&#xff09;转换3&#xff…

unityplayer.dll如何安装?unityplayer.dll缺失的解决方法

Unityplayer.dll是Unity引擎所需的一个重要动态链接库&#xff08;DLL&#xff09;文件&#xff0c;负责在运行Unity创建的游戏或应用程序时处理相关的软件逻辑。如果此文件意外丢失&#xff0c;可能会导致错误提示&#xff0c;甚至阻止程序的正常运行。因此&#xff0c;对于许…

亚马逊2024版Listing打分标准大更新:权重规则调整,卖家们需关注!

亚马逊近期发布了关于“2024版Listing打分标准”的两篇文章&#xff0c;其中更新了Listing权重规则&#xff0c;引起了广大卖家的关注。 对于亚马逊卖家而言&#xff0c;打造产品Listing是产品上架前必须完成的重要任务&#xff0c;而想要成为爆款&#xff0c;则需要遵循亚马逊…

[点云分割] 基于最小切割的分割

效果&#xff1a; 代码&#xff1a; #include <iostream> #include <vector>#include <pcl/point_types.h> #include <pcl/io/pcd_io.h> #include <pcl/visualization/cloud_viewer.h> #include <pcl/filters/filter_indices.h> #include…

Vatee万腾的数字化探险:Vatee科技创新勾勒新的独特轨迹

在数字化时代的浪潮中&#xff0c;Vatee万腾以其强大的科技创新力量&#xff0c;开启了一场引人瞩目的数字化探险之旅。这不仅是一次技术的探索&#xff0c;更是对未知领域的大胆冒险&#xff0c;为科技的未来勾勒出一条独特的轨迹。 Vatee的数字化探险并非仅仅局限于技术的提升…

双11再创新高!家电行业如何通过矩阵管理,赋能品牌增长?

双11大促已落下帷幕&#xff0c;虽然今年不再战报满天飞&#xff0c;但从公布的数据来看&#xff0c;家电行业整体表现不俗。 根据抖音电商品牌业务发布的收官战报&#xff0c;家电行业创造了成交新纪录&#xff0c;整体同比增长125%。快手官方数据显示&#xff0c;消电家居行业…

Java操作excel之poi

1. 创建Excel 1.1 创建新Excel工作簿 引入poi依赖 <!-- https://mvnrepository.com/artifact/org.apache.poi/poi --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</ar…

内测分发平台的未来发展和趋势如何

大家好&#xff0c;我是咕噜-凯撒&#xff0c;随着软件开发行业的快速发展和更新迭代的频率不断加快&#xff0c;内测分发平台作为软件测试和发布的重要环节&#xff0c;将在未来扮演更加关键的角色。未来内测分发平台发展将呈现出一系列的新趋势,都有哪些方面呢。图片来源:new…
最新文章