转载:【lwip】03-内存管理 - 李柱明 - 博客园

目录

  • 前言
  • 3. 内存管理
    • 3.1 内存分配策略
      • 3.1.1 固定大小的内存块
      • 3.1.2 可变大小分配
    • 3.2 动态内存池(pool)
      • 3.2.1 介绍
      • 3.2.2 内存池的预处理
      • 3.2.3 内存池的初始化
      • 3.2.4 内存分配
      • 3.2.5 内存释放
    • 3.2.6 内存池源码定义简要分析
      • 定义内存池资源源码分析
      • 保存各个内存池控制块地址
      • 定位所有内存池
    • 3.3 动态内存堆
      • 3.3.1 内存堆组织结构
      • 3.3.2 内存堆初始化
      • 3.3.3 内存堆分配
      • 3.3.4 内存堆释放
    • 3.4 使用 C 库的 malloc 和 free 来管理内存
    • 3.5 lwip 中的配置
      • 3.5.1 几个重要的宏
      • 3.5.2 内存池的方式实现内存堆分配配置
  • 附件-代码
    • memp_init();
    • memp_malloc();
    • memp_free();
    • 内存堆组织结构源码
    • mem_init();
    • mem_malloc();
    • mem_free();


前言

想说的:

内存的动态申请&释放最重要的参考是指针;

申请得到的内存返回的是可用空间的其实地址(指针);

释放时也是传入该地址(指针)让内部算法进行释放。

一般这些地址前面部分是内存分配器管理的空间,用于管理本小段内存。

李柱明博客:【lwip】03-内存管理 - 李柱明 - 博客园

3. 内存管理

lwip 提供两种简单高效的动态内存管理策略:

  1. 动态内存堆管理(heap)
  2. 动态内存池管理(pool)

3.1 内存分配策略

一般内存分配策略分两种:

  1. 分配固定大小的内存块;
  2. 利用内存堆进行动态分配。

lwip 支持 C 标准库中的 malloc 和 free 进行内存分配。(不建议使用)

3.1.1 固定大小的内存块

系统在初始化的时候会把可用的内存划分为 N 块固定大小的内存。然后通过单链表的方式把这些内存块连接起来。

用户申请的时候直接在链表的头部取出,且只能申请到固定大小的内存块。

释放的时候把内存块放到链表头部即可。

优点:

  • 分配效率高。
  • 没有内存碎片。

缺点:

  • 可能会浪费内存。(如:单个内存块的单位很大,而实际申请使用很小)
3.1.2 可变大小分配

可变大小分配的算法有很多种,lwip 采用 First Fit(首次拟合)内存管理算法:

  • 申请内存时只要找到一个比所请求的内存大的空闲块,就从中切割出合适的块,并把剩余的部分返回到动态内存堆中;

  • 这种分配策略分配的内存块大小有限制,要求请求的分配大小不能小于 MIN_SIZE,否则请求会被分配到 MIN_SIZE 大小的内存空间;

  • 在申请到的内存前面几个字节是内存分配器管理用的私有数据,也就是本段内存控制块,不允许用户修改。

结构参考:

优点:

  • 在限定上、下线的条件下,用户可自由申请需要的大小空间。
  • 首次拟合,分配速度稍快。

缺点:

  • 容易造成内存碎片。

    • 内存碎片:内存空间是有的,都是不连续,都是很小块。用户在申请大空间时就会申请失败。

3.2 动态内存池(pool)

lwip 使用到动态内存池的原因是很多协议首部或者控制块是固定大小的。

3.2.1 介绍

系统将所有可用区域以固定大小的字节单位进行划分,然后用单链表将所有空闲内存块连接起来。

同一链表中,所有节点大小都是相同的。这种分配只是前面讲的((20210803155807-x09b60h))的一个升级。

申请大小必须是指定固定大小字节的值(如 4、8、16 等等)。

lwip 源文件中 memp.c 和 memp.h 就是动态内存池分配策略。

3.2.2 内存池的预处理

在 lwip 内存初始化时,会初始化相应的内存池:

内核按照宏配置以固定的单位划分内存,然后用链表管理所有空闲块,组成一个内存池。

使用:

  • 外边提供 LWIP_MEMPOOL 宏定义,然后在包含 memp_std.h 文件,编译器就会处理。

参考例子:

  • // memp_std.h 文件
    
    #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 */
  • // 使用
    
    typedef enum
    {
    #define LWIP_MEMPOOL(name,num,size,desc)  MEMP_##name,
    #include "lwip/priv/memp_std.h"
        MEMP_MAX
    } memp_t;
  • // 预编译结果。 (假设宏都开通了)
    
    typedef enum
    {
        MEMP_RAW_PCB,
        MEMP_UDP_PCB,
        MEMP_MAX
    } memp_t;

memp_t 类型在整个内存池的管理中是最重要的存在。

通过内存池申请内存的时候,唯一的参数就是 memp_t 类型的,通过该类型告知分配函数去哪个 pool 申请内存块。

3.2.3 内存池的初始化

lwip 初始化时,会调用 memp_init() 函数对内存池进行初始化。

源码参考:((20210803171034-68omo3p))

说明:

  • MEMP_SIZE:内存分配器管理的空间。
  • desc->size:单个内存块大小。

3.2.4 内存分配

API:memp_malloc(memp_t type);,参数为内存类型。参考:((20210803185249-o03r2pp))

申请时直接从对应链表中拿出第一个空闲块。

主要代码为:memp = *desc->tab; // 核心

3.2.5 内存释放

API:memp_free(memp_t type, void *mem);。参考:((20210803190146-rtjgyhd))

使用完毕的块插回到对应链表。

3.2.6 内存池源码定义简要分析

主要分三步:

  1. 定义内存池资源。包括内存空间和数据结构。
  2. 把内存池资源地址保存到数组memp_pools中。
  3. 根据内存池内容定义内存枚举,能在memp_pools数组中直接找到对应的内存池。

其源码实现原理是通过修改宏LWIP_MEMPOOL(name,num,size,desc)实现不同的宏函数,再通过包含#include "lwip/priv/memp_std.h"头文件,通过预编译,实现上述三步。

定义内存池资源源码分析

核心源码:memp.c

#define LWIP_MEMPOOL(name,num,size,desc) LWIP_MEMPOOL_DECLARE(name,num,size,desc)
#include "lwip/priv/memp_std.h"

这样就分配好所有内存池需要的内存空间和数据结构资源了。

继续分析。

宏方法LWIP_MEMPOOL_DECLARE(name,num,size,desc)

/**
 * @ingroup mempool
 * Declare a private memory pool
 * Private mempools example:
 * .h: only when pool is used in multiple .c files: LWIP_MEMPOOL_PROTOTYPE(my_private_pool);
 * .c:
 *   - in global variables section: LWIP_MEMPOOL_DECLARE(my_private_pool, 10, sizeof(foo), "Some description")
 *   - call ONCE before using pool (e.g. in some init() function): LWIP_MEMPOOL_INIT(my_private_pool);
 *   - allocate: void* my_new_mem = LWIP_MEMPOOL_ALLOC(my_private_pool);
 *   - free: LWIP_MEMPOOL_FREE(my_private_pool, my_new_mem);
 *
 * To relocate a pool, declare it as extern in cc.h. Example for GCC:
 *   extern u8_t \_\_attribute\_\_((section(".onchip_mem"))) memp_memory_my_private_pool_base[];
 */
#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) \
    // 定义该内存池链表指针 \
  static struct memp *memp_tab_ ## name; \
    // 定义该内存池数据结构实体 \
  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 \
  };

#endif /* MEMP_MEM_MALLOC */

上述实现中的具体宏实现汇总:

/* 定义内存 */
#ifndef LWIP_DECLARE_MEMORY_ALIGNED
#define LWIP_DECLARE_MEMORY_ALIGNED(variable_name, size) u8_t variable_name[LWIP_MEM_ALIGN_BUFFER(size)]
#endif

/* 定义内存统计 */
#define LWIP_MEMPOOL_DECLARE_STATS_INSTANCE(name) static struct stats_mem name;

/* 内存池控制块数据结构struct memp_desc参数中的宏 */
#define DECLARE_LWIP_MEMPOOL_DESC(desc) (desc),
#define LWIP_MEMPOOL_DECLARE_STATS_REFERENCE(name) &name,
#ifndef LWIP_MEM_ALIGN_SIZE
#define LWIP_MEM_ALIGN_SIZE(size) (((size) + MEM_ALIGNMENT - 1U) & ~(MEM_ALIGNMENT-1U))
#endif

内存池控制块数据结构:

/* 内存池控制块 */
struct memp_desc {
#if defined(LWIP_DEBUG) || MEMP_OVERFLOW_CHECK || LWIP_STATS_DISPLAY
  /* 当前内存池文本描述 */
  const char *desc;
#endif /* LWIP_DEBUG || MEMP_OVERFLOW_CHECK || LWIP_STATS_DISPLAY */
#if MEMP_STATS
  /* 内存统计 */
  struct stats_mem *stats;
#endif

  /* 每个成员size */
  u16_t size;

#if !MEMP_MEM_MALLOC
  /* 成员个数 */
  u16_t num;

  /* 数据区基地址 */
  u8_t *base;

  /* 内存池链表的二级指针。(注意,该链表时没有哨兵的) */
  struct memp **tab;
#endif /* MEMP_MEM_MALLOC */
};
保存各个内存池控制块地址

lwip库内会把各个内存池保存到memp_pools数组中。

代码实现也很简单:

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_pools内存池地址汇总的数组中,通过枚举实现:

/* 创建由memp管理的所有内存池的列表。MEMP_MAX表示末尾的空池 */
typedef enum {
#define LWIP_MEMPOOL(name,num,size,desc)  MEMP_##name,
#include "lwip/priv/memp_std.h"
  MEMP_MAX
} memp_t;

3.3 动态内存堆

动态内存堆管理(heap)分为:

  • C 标准库自带的内存管理策略。
  • lwip 自身实现的内存堆管理策略。

C 和 lwip 实现的内存堆管理在 lwip 中只能通过宏 MEM_LIBC_MALLOC 来进行二选一。

lwip 的内存池和内存堆设计非常灵活:

  1. 通过宏 MEM_USE_POOLS 可以使能内存堆基于内存池实现
  2. 通过宏 MEMP_MEM_MALLOC 可以实现内存池基于内存堆实现
3.3.1 内存堆组织结构

源码&注释参考:((20210803195754-ppimyyp))

3.3.2 内存堆初始化

lwip 初始化时,会调用 mem_init() 函数对内存池进行初始化。

源码及注释参考:((20210803203835-ay0l1ou))

其简要内容:

  • 初始化该堆内存。
  • 初始化时,内存是空的,所以只有一个空闲的内存块。
  • 该空闲的内存块上一个为空,下一个为结尾。
  • 为了分支空闲块链表往下找,所以来个结尾内存块,一直标记为已使用,且上一个、下一个都指向本身。
  • 由上得出以下内存结构:

3.3.3 内存堆分配

API:mem_malloc(mem_size_t size_in);:源码参考((20210803210417-ezc7i9y))

简要内容:(其过程可以在脑海里想象一下)其实就是找到一个够大的空闲块。

  • 遍历空闲块链表,找到一个够大的空闲块。

  • 若该块足够大,被申请后剩余的空间能够组成一个新的空闲块节点(大于 mem 结构体大小 + 最小空闲空间大小),那就组成一个新的空闲块,初始化并插入空闲块链表。

    • 注意,组成新的节点要判断该节点的下一个节点是不是整个内存堆的结尾节点,若是,则不用管下一个节点的前一个变量。若不是,则要赋值下一个节点的前一个是新的节点。(就是双向链表中插入算法的步骤之一,这个步骤遇到内存堆尾节点是不用管的)
  • 若申请后剩余的空间不够组成新的空闲块节点,则不用创建新的空闲块节点。

  • 申请成功后返回的是用户实际可用的地址。该地址前面就是控制块,用户切记不要试图修改控制块内容。

3.3.4 内存堆释放

API:mem_free(void *rmem);:源码参考:

根据用户释放的内存块地址,通过偏移 mem 结构体大小得到正确的内存块起始地址,并且根据 mem 中保存的内存块信息进行释放、合并等操作,并将 used 字段清零,表示该内存块未被使用。

简要内容:

  • 过滤非法指针。
  • 找到需要是否的内存块控制块。
  • 若该内存块已被使用则释放。
  • 释放后检查该内存块在空闲链表中是否有效。
  • 若下一个内存块为空闲,则合并内存块。

3.4 使用 C 库的 malloc 和 free 来管理内存

使用宏 MEM_LIBC_MALLOC 来决定使用 C 库的还是使用 lwip 自己实现的。

使用 C 库的配置:

 #if MEM_LIBC_MALLOC
 void
 mem_init(void)
 {
 }
 void *
 mem_trim(void *mem, mem_size_t size)
 {
     LWIP_UNUSED_ARG(size);
     return mem;
 }

 #ifndef mem_clib_free
 #define mem_clib_free free
 #endif
 #ifndef mem_clib_malloc
 #define mem_clib_malloc malloc
 #endif
 #ifndef mem_clib_calloc
 #define mem_clib_calloc calloc
 #endif

 #define MEM_LIBC_STATSHELPER_SIZE 0

 #endif

使用 c 库中的系统内存块作为内存堆,其接口也是 c 库封装的,所以我们不必做什么处理,只要把 c 库的动态内存管理接口交给 lwip 封装即可。

3.5 lwip 中的配置

3.5.1 几个重要的宏
  • MEM_LIBC_MALLOC:该宏定义是否使用 C 标准库自带的内存分配策略。默认为 0,不使用。

    • 为 0:使用 lwip 自己实现的动态内存策略:动态内存池和动态内存堆。

      • MEMP_MEM_MALLOC:该宏定义表示是否使用 LwIP 内存堆分配策略实现内存池分配。默认为 0。
      • MEM_USE_POOLS:该宏定义表示是否使用 LwIP 内存池分配策略实现内存堆的分配。默认为 0。
      • 上面两个宏只能只能开其一。
3.5.2 内存池的方式实现内存堆分配配置

宏配置

#define MEM_USE_POOLS 1
#define MEMP_USE_CUSTOM_POOLS 1
#define MEMP_MEM_MALLOC 0

lwippools.h

LWIP_MALLOC_MEMPOOL_START

LWIP_MALLOC_MEMPOOL(20, 256)
LWIP_MALLOC_MEMPOOL(10, 512)
LWIP_MALLOC_MEMPOOL(5, 1512)

LWIP_MALLOC_MEMPOOL_END

注意:

  • 内存池的大小要依次增大,在编译阶段,编译器就会将这些内存个数及大小添加到系统的内存池之中。

    • 这样在用户申请内存的时候,最匹配的内存池中的内存块已经用完,可选择更大的内存池进行匹配,按小到达是为了能够高效匹配到内存块。

附件-代码

memp_init();

/**
 * Initializes lwIP built-in pools.
 * Related functions: memp_malloc, memp_free
 *
 * Carves out memp_memory into linked lists for each pool-type.
 */
void
memp_init(void)
{
  u16_t i;

  /* for every pool: */
  for (i = 0; i < LWIP_ARRAYSIZE(memp_pools); i++) {
    memp_init_pool(memp_pools[i]);

    /* 静态部分 */
#if LWIP_STATS && MEMP_STATS
    lwip_stats.memp[i] = memp_pools[i]->stats;
#endif
  }

#if MEMP_OVERFLOW_CHECK >= 2
  /* check everything a first time to see if it worked */
  memp_overflow_check_all();
#endif /* MEMP_OVERFLOW_CHECK >= 2 */
}

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

/**
 * Initialize custom memory pool.
 * Related functions: memp_malloc_pool, memp_free_pool
 *
 * @param desc pool to initialize
 */
void
memp_init_pool(const struct memp_desc *desc)
{
#if MEMP_MEM_MALLOC
  LWIP_UNUSED_ARG(desc);
#else
  int i;
  struct memp *memp;

  *desc->tab = NULL;
  memp = (struct memp*)LWIP_MEM_ALIGN(desc->base);
  /* create a linked list of memp elements */
  for (i = 0; i < desc->num; ++i) {
    memp->next = *desc->tab;
    *desc->tab = memp;
#if MEMP_OVERFLOW_CHECK
    memp_overflow_init_element(memp, desc);
#endif /* MEMP_OVERFLOW_CHECK */
   /* cast through void* to get rid of alignment warnings */
   memp = (struct memp *)(void *)((u8_t *)memp + MEMP_SIZE + desc->size
#if MEMP_OVERFLOW_CHECK
      + MEMP_SANITY_REGION_AFTER_ALIGNED
#endif
    );
  }
#if MEMP_STATS
  desc->stats->avail = desc->num;
#endif /* MEMP_STATS */
#endif /* !MEMP_MEM_MALLOC */

#if MEMP_STATS && (defined(LWIP_DEBUG) || LWIP_STATS_DISPLAY)
  desc->stats->name  = desc->desc;
#endif /* MEMP_STATS && (defined(LWIP_DEBUG) || LWIP_STATS_DISPLAY) */
}

memp_malloc();

/**
 * Get an element from a specific pool.
 *
 * @param type the pool to get an element from
 *
 * @return a pointer to the allocated memory or a NULL pointer on error
 */
void *
#if !MEMP_OVERFLOW_CHECK
memp_malloc(memp_t type)
#else
memp_malloc_fn(memp_t type, const char* file, const int line)
#endif
{
  void *memp;
  LWIP_ERROR("memp_malloc: type < MEMP_MAX", (type < MEMP_MAX), return NULL;);

#if MEMP_OVERFLOW_CHECK >= 2
  memp_overflow_check_all();
#endif /* MEMP_OVERFLOW_CHECK >= 2 */

#if !MEMP_OVERFLOW_CHECK
  memp = do_memp_malloc_pool(memp_pools[type]);
#else
  memp = do_memp_malloc_pool_fn(memp_pools[type], file, line);
#endif

  return memp;
}

static void*
#if !MEMP_OVERFLOW_CHECK
do_memp_malloc_pool(const struct memp_desc *desc)
#else
do_memp_malloc_pool_fn(const struct memp_desc *desc, const char* file, const int line)
#endif
{
  struct memp *memp;
  SYS_ARCH_DECL_PROTECT(old_level);

#if MEMP_MEM_MALLOC
  memp = (struct memp *)mem_malloc(MEMP_SIZE + MEMP_ALIGN_SIZE(desc->size));
  SYS_ARCH_PROTECT(old_level);
#else /* MEMP_MEM_MALLOC */
  SYS_ARCH_PROTECT(old_level);

  memp = *desc->tab; // 核心
#endif /* MEMP_MEM_MALLOC */

  if (memp != NULL) {
#if !MEMP_MEM_MALLOC
#if MEMP_OVERFLOW_CHECK == 1
    memp_overflow_check_element_overflow(memp, desc);
    memp_overflow_check_element_underflow(memp, desc);
#endif /* MEMP_OVERFLOW_CHECK */

    *desc->tab = memp->next;
#if MEMP_OVERFLOW_CHECK
    memp->next = NULL;
#endif /* MEMP_OVERFLOW_CHECK */
#endif /* !MEMP_MEM_MALLOC */
#if MEMP_OVERFLOW_CHECK
    memp->file = file;
    memp->line = line;
#if MEMP_MEM_MALLOC
    memp_overflow_init_element(memp, desc);
#endif /* MEMP_MEM_MALLOC */
#endif /* MEMP_OVERFLOW_CHECK */
    LWIP_ASSERT("memp_malloc: memp properly aligned",
                ((mem_ptr_t)memp % MEM_ALIGNMENT) == 0);
#if MEMP_STATS
    desc->stats->used++;
    if (desc->stats->used > desc->stats->max) {
      desc->stats->max = desc->stats->used;
    }
#endif
    SYS_ARCH_UNPROTECT(old_level);
    /* cast through u8_t* to get rid of alignment warnings */
    return ((u8_t*)memp + MEMP_SIZE);
  } else {
    LWIP_DEBUGF(MEMP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, (0x10007901, "memp_malloc: out of memory in pool\n"));
#if MEMP_STATS
    desc->stats->err++;
#endif
  }

  SYS_ARCH_UNPROTECT(old_level);
  return NULL;
}

memp_free();

/**
 * Put an element back into its pool.
 *
 * @param type the pool where to put mem
 * @param mem the memp element to free
 */
void
memp_free(memp_t type, void *mem)
{
#ifdef LWIP_HOOK_MEMP_AVAILABLE
  struct memp *old_first;
#endif

  LWIP_ERROR("memp_free: type < MEMP_MAX", (type < MEMP_MAX), return;);

  if (mem == NULL) {
    return;
  }

#if MEMP_OVERFLOW_CHECK >= 2
  memp_overflow_check_all();
#endif /* MEMP_OVERFLOW_CHECK >= 2 */

#ifdef LWIP_HOOK_MEMP_AVAILABLE
  old_first = *memp_pools[type]->tab;
#endif

  do_memp_free_pool(memp_pools[type], mem);

#ifdef LWIP_HOOK_MEMP_AVAILABLE
  if (old_first == NULL) {
    LWIP_HOOK_MEMP_AVAILABLE(type);
  }
#endif
}

static void
do_memp_free_pool(const struct memp_desc* desc, void *mem)
{
  struct memp *memp;
  SYS_ARCH_DECL_PROTECT(old_level);

  LWIP_ASSERT("memp_free: mem properly aligned",
                ((mem_ptr_t)mem % MEM_ALIGNMENT) == 0);

  /* cast through void* to get rid of alignment warnings */
  memp = (struct memp *)(void *)((u8_t*)mem - MEMP_SIZE);

  SYS_ARCH_PROTECT(old_level);

#if MEMP_OVERFLOW_CHECK == 1
  memp_overflow_check_element_overflow(memp, desc);
  memp_overflow_check_element_underflow(memp, desc);
#endif /* MEMP_OVERFLOW_CHECK */

#if MEMP_STATS
  desc->stats->used--;
#endif

#if MEMP_MEM_MALLOC
  LWIP_UNUSED_ARG(desc);
  SYS_ARCH_UNPROTECT(old_level);
  mem_free(memp);
#else /* MEMP_MEM_MALLOC */
  memp->next = *desc->tab;
  *desc->tab = memp;

#if MEMP_SANITY_CHECK
  LWIP_ASSERT("memp sanity", memp_sanity(desc));
#endif /* MEMP_SANITY_CHECK */

  SYS_ARCH_UNPROTECT(old_level);
#endif /* !MEMP_MEM_MALLOC */
}

内存堆组织结构源码

// 位于文件 mem.c

/**
 * The heap is made up as a list of structs of this type.
 * This does not have to be aligned since for getting its size,
 * we only use the macro SIZEOF_STRUCT_MEM, which automatically aligns.
 */
struct mem {
  /** index (-> ram[next]) of the next struct */
  mem_size_t next; // 指向下一个内存块。并非指针,而是与堆头的偏移。
  /** index (-> ram[prev]) of the previous struct */
  mem_size_t prev; // 指向上一个内存块。并非指针,而是与堆头的偏移。
  /** 1: this area is used; 0: this area is unused */
  u8_t used; // 标记内存是否已经被使用。
};

/** All allocated blocks will be MIN_SIZE bytes big, at least!
 * MIN_SIZE can be overridden to suit your needs. Smaller values save space,
 * larger values could prevent too small blocks to fragment the RAM too much. */
#ifndef MIN_SIZE
#define MIN_SIZE             12 // 申请的内存最小为12字节
#endif /* MIN_SIZE */
/* some alignment macros: we define them here for better source code layout */
#define MIN_SIZE_ALIGNED     LWIP_MEM_ALIGN_SIZE(MIN_SIZE)
#define SIZEOF_STRUCT_MEM    LWIP_MEM_ALIGN_SIZE(sizeof(struct mem))
#define MEM_SIZE_ALIGNED     LWIP_MEM_ALIGN_SIZE(MEM_SIZE)

/** If you want to relocate the heap to external memory, simply define
 * LWIP_RAM_HEAP_POINTER as a void-pointer to that location.
 * If so, make sure the memory at that location is big enough (see below on
 * how that space is calculated). */
#ifndef LWIP_RAM_HEAP_POINTER
/** the heap. we need one struct mem at the end and some room for alignment */
LWIP_DECLARE_MEMORY_ALIGNED(ram_heap, MEM_SIZE_ALIGNED + (2U*SIZEOF_STRUCT_MEM)); // 内核的内存堆空间
#define LWIP_RAM_HEAP_POINTER ram_heap // 重命名 内核的内存堆空间
#endif /* LWIP_RAM_HEAP_POINTER */

/** pointer to the heap (ram_heap): for alignment, ram is now a pointer instead of an array */
static u8_t *ram; // 指向内存堆对齐后的起始地址
/** the last entry, always unused! */
static struct mem *ram_end; // 指向内存堆中最后一个内存块。一直未使用。
/** pointer to the lowest free block, this is used for faster search */
static struct mem *lfree; // 空闲内存块链表指针。

/** concurrent access protection */
#if !NO_SYS
static sys_mutex_t mem_mutex; // 互斥量
#endif

mem_init();

/**
 * Zero the heap and initialize start, end and lowest-free
 */
void
mem_init(void)
{
  struct mem *mem;

  LWIP_ASSERT("Sanity check alignment",
    (SIZEOF_STRUCT_MEM & (MEM_ALIGNMENT-1)) == 0);

  /* align the heap */
  ram = (u8_t *)LWIP_MEM_ALIGN(LWIP_RAM_HEAP_POINTER); // 内存堆对齐后的起始地址被记录在ram中
  /* initialize the start of the heap */
  mem = (struct mem *)(void *)ram; // 在内存堆起始位置放置一个mem类型的结构体,因为初始化后的内存堆就是一个大的空闲内存块,每个空闲内存块的前面都需要放置一个mem结构体
  mem->next = MEM_SIZE_ALIGNED; // 下一个内存块的偏移量为MEM_SIZE_ALIGNED,这相对于直接到内存堆的结束地址了
  mem->prev = 0; // 上一个内存块为空
  mem->used = 0; // 未被使用
  /* initialize the end of the heap */
  ram_end = (struct mem *)(void *)&ram[MEM_SIZE_ALIGNED]; // 内存堆末尾的位置放置一个mem类型的结构体,并初始化表示内存堆结束的内存块。
  ram_end->used = 1; // 一直被使用。就是不给用户使用(因为后面没有空闲内存)。
  ram_end->next = MEM_SIZE_ALIGNED; // 指回本身
  ram_end->prev = MEM_SIZE_ALIGNED; // 指回本身

  /* initialize the lowest-free pointer to the start of the heap */
  lfree = (struct mem *)(void *)ram; // 空闲内存块链表指针,因为只有一块,所以先指向第一块

  MEM_STATS_AVAIL(avail, MEM_SIZE_ALIGNED);

  if (sys_mutex_new(&mem_mutex) != ERR_OK) { // 创建一个内存堆分配时候使用的互斥量,如果是无操作系统的情况。(在OS下才有效)
    LWIP_ASSERT("failed to create mem_mutex", 0);
  }
}

mem_malloc();

/**
 * Allocate a block of memory with a minimum of 'size' bytes.
 *
 * @param size is the minimum size of the requested block in bytes.
 * @return pointer to allocated memory or NULL if no free memory was found.
 *
 * Note that the returned value will always be aligned (as defined by MEM_ALIGNMENT).
 */
void *
mem_malloc(mem_size_t size)
{
  mem_size_t ptr, ptr2;
  struct mem *mem, *mem2;
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
  u8_t local_mem_free_count = 0;
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */
  LWIP_MEM_ALLOC_DECL_PROTECT();

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

  /* Expand the size of the allocated memory region so that we can
     adjust for alignment. */
  size = LWIP_MEM_ALIGN_SIZE(size); // 字节大小进行字节对齐。

  if (size < MIN_SIZE_ALIGNED) { // 对比申请内存大小最小值
    /* every data block must be at least MIN_SIZE_ALIGNED long */
    size = MIN_SIZE_ALIGNED;
  }

  if (size > MEM_SIZE_ALIGNED) { // 申请的大小大于整个堆空间,就申请失败
    return NULL;
  }

  /* protect the heap from concurrent access */
  sys_mutex_lock(&mem_mutex); // 上锁
  LWIP_MEM_ALLOC_PROTECT();
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
  /* run as long as a mem_free disturbed mem_malloc or mem_trim */
  do {
    local_mem_free_count = 0;
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */

    /* Scan through the heap searching for a free block that is big enough,
     * beginning with the lowest free block.
     */
    for (ptr = (mem_size_t)((u8_t *)lfree - ram); ptr < MEM_SIZE_ALIGNED - size;
         ptr = ((struct mem *)(void *)&ram[ptr])->next) { // 遍历空闲内存块链表,直到找到第一个适合用户需求的内存块大小。
      mem = (struct mem *)(void *)&ram[ptr]; // 得到这个内存块起始地址。
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
      mem_free_count = 0;
      LWIP_MEM_ALLOC_UNPROTECT();
      /* allow mem_free or mem_trim to run */
      LWIP_MEM_ALLOC_PROTECT();
      if (mem_free_count != 0) {
        /* If mem_free or mem_trim have run, we have to restart since they
           could have altered our current struct mem. */
        local_mem_free_count = 1;
        break;
      }
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */

      if ((!mem->used) &&
          (mem->next - (ptr + SIZEOF_STRUCT_MEM)) >= size) { // 如果该内存块是未使用的,并且它的大小不小于用户需要的大小加上mem结构体的大小,就满足用户的需求。
        /* mem is not used and at least perfect fit is possible:
         * mem->next - (ptr + SIZEOF_STRUCT_MEM) gives us the 'user data size' of mem */

        if (mem->next - (ptr + SIZEOF_STRUCT_MEM) >= (size + SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED)) { // 是否需要把本段剩下的组成一个新的内存块
          /* (in addition to the above, we test if another struct mem (SIZEOF_STRUCT_MEM) containing
           * at least MIN_SIZE_ALIGNED of data also fits in the 'user data space' of 'mem')
           * -> split large block, create empty remainder,
           * remainder must be large enough to contain MIN_SIZE_ALIGNED data: if
           * mem->next - (ptr + (2*SIZEOF_STRUCT_MEM)) == size,
           * struct mem would fit in but no data between mem2 and mem2->next
           * @todo we could leave out MIN_SIZE_ALIGNED. We would create an empty
           *       region that couldn't hold data, but when mem->next gets freed,
           *       the 2 regions would be combined, resulting in more free memory
           */
          ptr2 = ptr + SIZEOF_STRUCT_MEM + size; // 剩下空闲空间组成块
          /* create mem2 struct */
          mem2 = (struct mem *)(void *)&ram[ptr2]; // 赋值初始化&把新的节点插入链表
          mem2->used = 0;
          mem2->next = mem->next;
          mem2->prev = ptr;
          /* and insert it between mem and mem->next */
          mem->next = ptr2;
          mem->used = 1; // 标记申请到的已使用

          if (mem2->next != MEM_SIZE_ALIGNED) { // 如果不是结尾节点,就把其下一节点的节点内容补充完善。(其实就是插入节点操作中的一部分)
            ((struct mem *)(void *)&ram[mem2->next])->prev = ptr2;
          }
          MEM_STATS_INC_USED(used, (size + SIZEOF_STRUCT_MEM));
        } else { // 如果剩下空间不够组成一个新的节点,那就不组新的。直接使用。
          /* (a mem2 struct does no fit into the user data space of mem and mem->next will always
           * be used at this point: if not we have 2 unused structs in a row, plug_holes should have
           * take care of this).
           * -> near fit or exact fit: do not split, no mem2 creation
           * also can't move mem->next directly behind mem, since mem->next
           * will always be used at this point!
           */
          mem->used = 1;
          MEM_STATS_INC_USED(used, mem->next - (mem_size_t)((u8_t *)mem - ram));
        }
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
mem_malloc_adjust_lfree:
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */
        if (mem == lfree) { // 如果成功申请到,那就更新空闲块链表指针
          struct mem *cur = lfree; // 找到第一个低地址的空闲内存块。
          /* Find next free block after mem and update lowest free pointer */
          while (cur->used && cur != ram_end) {
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
            mem_free_count = 0;
            LWIP_MEM_ALLOC_UNPROTECT();
            /* prevent high interrupt latency... */
            LWIP_MEM_ALLOC_PROTECT();
            if (mem_free_count != 0) {
              /* If mem_free or mem_trim have run, we have to restart since they
                 could have altered our current struct mem or lfree. */
              goto mem_malloc_adjust_lfree;
            }
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */
            cur = (struct mem *)(void *)&ram[cur->next];
          }
          lfree = cur; // 将lfree指向该空闲内存块。
          LWIP_ASSERT("mem_malloc: !lfree->used", ((lfree == ram_end) || (!lfree->used)));
        }
        LWIP_MEM_ALLOC_UNPROTECT();
        sys_mutex_unlock(&mem_mutex); // 解锁
        LWIP_ASSERT("mem_malloc: allocated memory not above ram_end.",
         (mem_ptr_t)mem + SIZEOF_STRUCT_MEM + size <= (mem_ptr_t)ram_end);
        LWIP_ASSERT("mem_malloc: allocated memory properly aligned.",
         ((mem_ptr_t)mem + SIZEOF_STRUCT_MEM) % MEM_ALIGNMENT == 0);
        LWIP_ASSERT("mem_malloc: sanity check alignment",
          (((mem_ptr_t)mem) & (MEM_ALIGNMENT-1)) == 0);

        return (u8_t *)mem + SIZEOF_STRUCT_MEM; // 返回内存块可用的起始地址
      }
    }
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
    /* if we got interrupted by a mem_free, try again */
  } while (local_mem_free_count != 0);
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */
  LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SERIOUS, (0x100078ff, "mem_malloc: could not allocate %hd bytes\n", (s16_t)size));
  MEM_STATS_INC(err);
  LWIP_MEM_ALLOC_UNPROTECT();
  sys_mutex_unlock(&mem_mutex); // 解锁
  return NULL; // 申请失败
}

mem_free();

/**
 * Put a struct mem back on the heap
 *
 * @param rmem is the data portion of a struct mem as returned by a previous
 *             call to mem_malloc()
 */
void
mem_free(void *rmem)
{
    struct mem *mem;
    LWIP_MEM_FREE_DECL_PROTECT();

    if (rmem == NULL) // 空就返回
    {
        LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_TRACE |
                    LWIP_DBG_LEVEL_SERIOUS,
                    ("mem_free(p == NULL) was called.\n"));
        return;
    }
    if ((((mem_ptr_t)rmem) & (MEM_ALIGNMENT - 1)) != 0)
    {
        LWIP_MEM_ILLEGAL_FREE("mem_free: sanity check alignment");
        LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SEVERE,
                    ("mem_free: sanity check alignment\n"));
        /* protect mem stats from concurrent access */
        MEM_STATS_INC_LOCKED(illegal);
        return;
    }

    mem = (struct mem *)(void *)((u8_t *)rmem -
        (SIZEOF_STRUCT_MEM + MEM_SANITY_OFFSET));   // 找到控制块

    if ((u8_t *)mem < ram ||
            (u8_t *)rmem + MIN_SIZE_ALIGNED > (u8_t *)ram_end) // 对释放的地址进行偏移,得到真正内存块的起始地址
    {
        LWIP_MEM_ILLEGAL_FREE("mem_free: illegal memory");
        LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SEVERE,
                    ("mem_free: illegal memory\n"));
        /* protect mem stats from concurrent access */
        MEM_STATS_INC_LOCKED(illegal);
        return;
    }

    /* protect the heap from concurrent access */
    LWIP_MEM_FREE_PROTECT();

    /* mem has to be in a used state */
    if (!mem->used) // 要释放的内存块是否被使用,未被使用就直接返回
    {
        LWIP_MEM_ILLEGAL_FREE("mem_free: illegal \
    memory: double free");
        LWIP_MEM_FREE_UNPROTECT();
        LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SEVERE,
                    ("mem_free: illegal memory: double free?\n"));
        /* protect mem stats from concurrent access */
        MEM_STATS_INC_LOCKED(illegal);
        return;
    }

    if (!mem_link_valid(mem)) // 判断一下内存块在链表中的连接是否正常,如果不正常也直接返回
    {
        LWIP_MEM_ILLEGAL_FREE("mem_free: illegal memory:\
    non-linked: double free");
        LWIP_MEM_FREE_UNPROTECT();
        LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SEVERE,
                ("mem_free: illegal memory: non-linked: double free?\n"));
        /* protect mem stats from concurrent access */
        MEM_STATS_INC_LOCKED(illegal);
        return;
    }

    /* mem is now unused. */
    mem->used = 0; // 标记为未使用

    if (mem < lfree)
    {
        /* the newly freed struct is now the lowest */
        lfree = mem; // 更新lfree指针
    }

    MEM_STATS_DEC_USED(used, mem->next -
                    (mem_size_t)(((u8_t *)mem - ram)));

    /* finally, see if prev or next are free also */
    plug_holes(mem);  // 判断下一个内存块是否为空,为空则合并
    MEM_SANITY();

    LWIP_MEM_FREE_UNPROTECT();
}

 static void
 plug_holes(struct mem *mem)
 {
     struct mem *nmem;
     struct mem *pmem;

     LWIP_ASSERT("plug_holes: mem >= ram", (u8_t *)mem >= ram);
     LWIP_ASSERT("plug_holes: mem < ram_end",
                 (u8_t *)mem < (u8_t *)ram_end);
     LWIP_ASSERT("plug_holes: mem->used == 0", mem->used == 0);

     /* plug hole forward */
     LWIP_ASSERT("plug_holes: mem->next <= MEM_SIZE_ALIGNED",
                 mem->next <= MEM_SIZE_ALIGNED);

     nmem = ptr_to_mem(mem->next);
     if (mem != nmem && nmem->used == 0 &&
             (u8_t *)nmem != (u8_t *)ram_end)
     {
     /* if mem->next is unused and not end of ram, combine mem and mem->next */
         if (lfree == nmem)
         {
             lfree = mem;
         }
         mem->next = nmem->next;
         if (nmem->next != MEM_SIZE_ALIGNED)
         {
             ptr_to_mem(nmem->next)->prev = mem_to_ptr(mem);
         }
     }

     /* plug hole backward */
     pmem = ptr_to_mem(mem->prev);
     if (pmem != mem && pmem->used == 0)
     {
         /* if mem->prev is unused, combine mem and mem->prev */
         if (lfree == mem)
         {
             lfree = pmem;
         }
         pmem->next = mem->next;
         if (mem->next != MEM_SIZE_ALIGNED)
         {
             ptr_to_mem(mem->next)->prev = mem_to_ptr(pmem);
         }
     }
 }

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

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

相关文章

「Mac畅玩鸿蒙与硬件25」UI互动应用篇2 - 计时器应用实现

本篇将带领你实现一个实用的计时器应用&#xff0c;用户可以启动、暂停或重置计时器。该项目将涉及时间控制、状态管理以及按钮交互&#xff0c;是掌握鸿蒙应用开发的重要步骤。 关键词 UI互动应用时间控制状态管理用户交互 一、功能说明 在这个计时器应用中&#xff0c;用户…

使用MongoDB Atlas构建无服务器数据库

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用MongoDB Atlas构建无服务器数据库 MongoDB Atlas 简介 注册账户 创建集群 配置网络 设置数据库用户 连接数据库 设计文档模式…

从零开始的c++之旅——继承

1. 继承 1.继承概念及定义 继承是面向对象编程的三大特点之一&#xff0c;它使得我们可以在原有类特性的基础之上&#xff0c;增加方法 和属性&#xff0c;这样产生的新的类&#xff0c;称为派生类。 继承 呈现了⾯向对象程序设计的层次结构&#xff0c;以前我们接触的…

正向解析和反向解析

正向解析 服务端&#xff1a; [rootlocalhost rhel]# vim /etc/named.conf [rootlocalhost named]# vim /var/named/named.openlab.com 客户端&#xff1a; [rootlocalhost rhel]# nslookup 反向解析 服务端&#xff1a; [rootlocalhost rhel]# vim /etc/named.conf [ro…

计算机网络:网络层 —— 路由信息协议 RIP

文章目录 路由选择协议动态路由协议路由信息协议 RIPRIP 的重要特点RIP的基本工作过程RIP的距离向量算法RIP存在的问题RIP版本和相关报文的封装 路由选择协议 因特网是全球最大的互联网&#xff0c;它所采取的路由选择协议具有以下三个主要特点&#xff1a; 自适应&#xff1a…

基于yolov5的输电线,电缆检测系统,支持图像检测,视频检测和实时摄像检测功能(pytorch框架,python源码)

更多目标检测和图像分类识别项目可看我主页其他文章 功能演示&#xff1a; yolov5&#xff0c;输电线(线缆)检测系统&#xff0c;系统既支持图像检测&#xff0c;也支持视频和摄像实时检测【pytorch框架】_哔哩哔哩_bilibili &#xff08;一&#xff09;简介 基于yolov5的输…

删除WPS的智能识别目录

很烦&#xff0c;对吧 智能识别目录很垃圾&#xff0c;无法直接删除&#xff0c;如果你选择左边的目录&#xff0c;删除的话&#xff0c;会顺便把右边的正文也删除了。 那么如何只删除左边目录&#xff0c;保留右边的正文呢&#xff1f;只有一个办法&#xff1a; ctrlshiftC复…

客户端与微服务之间的桥梁---网关

当我们创建好了N多个微服务或者微服务的实例之后&#xff0c;每个服务暴露出不同的端口地址&#xff0c;一般对于客户端请求&#xff0c;只需要请求一个端口&#xff0c;要隔离客户端和微服务的直接关系&#xff0c;保证微服务的安全性和灵活性&#xff0c;避免敏感信息的泄露。…

构建您自己的 RAG 应用程序:使用 Ollama、Python 和 ChromaDB 在本地设置 LLM 的分步指南

在数据隐私至关重要的时代&#xff0c;建立自己的本地语言模型 &#xff08;LLM&#xff09; 为公司和个人都提供了至关重要的解决方案。本教程旨在指导您完成使用 Ollama、Python 3 和 ChromaDB 创建自定义聊天机器人的过程&#xff0c;所有这些机器人都托管在您的系统本地。以…

C++STL-deque、stack、queue、priority_queue

C教学总目录 deque、stack、queue、priority_queue 1、deque2、stack使用介绍3、stack实现4、queue使用介绍5、queue实现6、priority_queue使用介绍7、priority_queue实现8、反向迭代器 1、deque deque是双端队列&#xff0c;我们学习的队列是先进先出的(First in first out)&a…

【c++篇】:掌握vector基础知识--基本操作与使用全知道

✨感谢您阅读本篇文章&#xff0c;文章内容是个人学习笔记的整理&#xff0c;如果哪里有误的话还请您指正噢✨ ✨个人主页&#xff1a;余辉zmh–CSDN博客 ✨文章所属专栏&#xff1a;c篇–CSDN博客 文章目录 前言一.vector的基本概念1.定义2.主要特性和优点 二.vector的基本操作…

如何建购物网站提升用户体验

在构建一个购物网站时&#xff0c;用户体验是至关重要的&#xff0c;它直接影响到顾客的满意度和转化率。为了提升用户体验&#xff0c;可以从以下几个方面入手。 首先&#xff0c;网站设计应简洁明了。确保导航栏清晰易懂&#xff0c;让用户在寻找商品时不会迷失。此外&#x…

勒索软件如何传播?

在本文中&#xff0c;我们将讨论勒索软件对企业的影响并解释这些攻击的具体传播方式。 我们还将提供可采取的切实步骤来保护您自己和您的企业免受这些不断上升的威胁。 勒索软件对小型企业的攻击日益增多 勒索软件仍然是全球各种规模企业的头号威胁。 小型企业数据泄露的成…

Claude 3.5 新功能 支持对 100 页的PDF 图像、图表和图形进行可视化分析

Claude 3.5 Sonnet发布PDF图像预览新功能&#xff0c;允许用户分析长度不超过100页的PDF中的视觉内容。 此功能使用户能够轻松上传文档并提取信息&#xff0c;特别适用于包含图表、图形和其他视觉元素的研究论文和技术文档。 视觉PDF分析&#xff1a;用户现在可以从包含各种视觉…

交换排序(冒泡/快排)

一 . 交换排序 交换排序基本思想 : 所谓交换 &#xff0c; 就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置 。 交换序列的特点是 &#xff1a; 将键值较大的记录向序列的尾部移动 &#xff0c; 键值较小的记录向序列的前部移动 1.1 冒泡排序 在前面中 …

【反射率】-- Lab 转换(excel)

系列文章目录 文章目录 系列文章目录前言一、CIE1.CIE 简介2.cie 1931标准色度匹配函数数据3.从CIE1931RGB到CIE1931 XYZ 二、Lab颜色空间的理解1.Lab色差公式怎么计算色差 三、D65光源Lab计算总结 前言 一、CIE 1.CIE 简介 CIE是由国际照明工程领域中光源制造、照明设计和光…

[ 问题解决篇 ] win11中本地组策略编辑器gpedit.msc打不开(gpedit.msc缺失)

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

c语言素数优化,图解

方法① 2~m-1范围 整体思路就是&#xff0c;整数取余0就break&#xff0c;后续判断取余不为0的i次数&#xff0c;如果到头也就是i值溢出m-1 也就是最后一次循环i都没break&#xff0c;说明全部取余都不为0&#xff0c;贼为素数 尽头 i<m-1 等于号和-1可以抵消&#xff0c; …

跨境电商行业中的主数据有哪些?

在全球化和数字化的推动下&#xff0c;跨境电商行业正迎来前所未有的发展机遇。无论是品牌拓展国际市场还是小型卖家进入全球电商平台&#xff0c;跨境电商企业都需要面对海量数据的管理与整合。在这个行业中&#xff0c;主数据管理尤为重要&#xff0c;因为跨境电商涉及到复杂…

opencv - py_imgproc - py_grabcut GrabCut 算法提取前景

文章目录 使用 GrabCut 算法进行交互式前景提取目标理论演示 使用 GrabCut 算法进行交互式前景提取 目标 在本章中 我们将了解 GrabCut 算法如何提取图像中的前景我们将为此创建一个交互式应用程序。 理论 GrabCut 算法由英国剑桥微软研究院的 Carsten Rother、Vladimir K…