lwIP——3 内存管理

news2025/5/26 9:16:25

目录

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 
    {
        // 这里原代码没有处理逻辑,可根据实际情况添加错误处理等代码
    }
    // 若没有可用内存块,返回 NULL
    return 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.coloradmin.cn/o/2281593.html

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

相关文章

使用 Serilog 在 .NET Core 6.0 中日志记录

在本文中&#xff0c;我们将讨论日志记录的基础知识以及在 .NET Core 6 中使用 Serilog 的逐步实现。 先决条件 1、Visual Studio 2022。 2、C# 和 .NET Core 的基础知识。 3、.NET Core 6 SDK。 日志记录基础知识 从技术角度来说&#xff0c;日志记录是记录事件并捕获应用程…

当 Facebook 窥探隐私:用户的数字权利如何捍卫?

随着社交平台的普及&#xff0c;Facebook 已经成为全球用户日常生活的一部分。然而&#xff0c;伴随而来的隐私问题也愈发严峻。近年来&#xff0c;Facebook 频频被曝出泄露用户数据、滥用个人信息等事件&#xff0c;令公众对其隐私保护措施产生质疑。在这个信息化时代&#xf…

微信小程序隐藏右侧胶囊按钮,分享和关闭即右侧三个点和小圆圈按钮

在微信小程序开发过程中&#xff0c;可能需要将右侧的胶囊按钮、即右侧的三个点和小圆圈按钮关闭掉。如图&#xff1a; 这时&#xff0c;我们只需在该页面的json文件中进行相关配置即可 {"navigationBarTitleText": "商品详情页","navigationStyle&q…

Sharding-JDBC 5.4.1+SpringBoot3.4.1+MySQL8.4.1 使用案例

最近在升级 SpringBoot 项目&#xff0c;原版本是 2.7.16&#xff0c;要升级到 3.4.0 &#xff0c;JDK 版本要从 JDK8 升级 JDK21&#xff0c;原项目中使用了 Sharding-JDBC&#xff0c;版本 4.0.0-RC1&#xff0c;在升级 SpringBoot 版本到 3.4.0 之后&#xff0c;服务启动失败…

鸿蒙仓颉环境配置(仓颉SDK下载,仓颉VsCode开发环境配置,仓颉DevEco开发环境配置)

目录 ​1&#xff09;仓颉的SDK下载 1--进入仓颉的官网 2--点击图片中的下载按钮 3--在新跳转的页面点击即刻下载 4--下载 5--找到你们自己下载好的地方 6--解压软件 2&#xff09;仓颉编程环境配置 1--找到自己的根目录 2--进入命令行窗口 3--输入 envsetup.bat 4--验证是否安…

【数据库】详解MySQL数据库中索引的本质与底层原理

目录 1.MySQL索引的本质 1.1.索引的重要性 1.2.索引演示 1.3.索引的底层原理 1.3.1磁盘IO的原理 1.3.2.硬盘的主要结构 1.3.3.工作情形 1.3.4.各主要部件说明 1.3.5.扇区中是如何表示01数据的 2.MySQL索引底层原理 2.1.二叉查找树 2.2.平衡二叉查找树 2.3.B树和B树…

网络编程原理:回显服务器与客户端通信交互功能

文章目录 路由器及网络概念网络通信基础TCP/IP 五层协议封装和分用封装分用 网络编程&#xff08;网络协议&#xff09;UDP类 API使用实现回显通信程序回显服务器(UDP代码)回显客户端(UDP代码) TCP API使用回显服务器(TCP代码)回显客户端(TCP代码) 路由器及网络概念 网络发展是…

AIGC视频扩散模型新星:Video 版本的SD模型

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍慕尼黑大学携手 NVIDIA 等共同推出视频生成模型 Video LDMs。NVIDIA 在 AI 领域的卓越成就家喻户晓&#xff0c;而慕尼黑大学同样不容小觑&#xff0c;…

[Day 15]54.螺旋矩阵(简单易懂 有画图)

今天我们来看这道螺旋矩阵&#xff0c;和昨天发的题很类似。没有技巧&#xff0c;全是循环。小白也能懂~ 力扣54.螺旋矩阵 题目描述&#xff1a; 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; …

用Python绘制一只懒羊羊

目录 一、准备工作 二、Turtle库简介 三、绘制懒羊羊的步骤 1. 导入Turtle库并设置画布 2. 绘制头部 3. 绘制眼睛 4. 绘制嘴巴 5. 绘制身体 6. 绘制四肢 7. 完成绘制 五、运行代码与结果展示 六、总结 在这个趣味盎然的技术实践中,我们将使用Python和Turtle图形…

QT QTreeWidget控件 全面详解

本系列文章全面的介绍了QT中的57种控件的使用方法以及示例,包括 Button(PushButton、toolButton、radioButton、checkBox、commandLinkButton、buttonBox)、Layouts(verticalLayout、horizontalLayout、gridLayout、formLayout)、Spacers(verticalSpacer、horizontalSpacer)、…

掌握Spring事务隔离级别,提升并发处理能力

Spring框架支持的事务隔离级别与标准的JDBC隔离级别保持一致&#xff0c;共包括五大隔离级别&#xff0c;它们分别是&#xff1a;DEFAULT&#xff08;默认隔离级别&#xff09;、READ_UNCOMMITTED&#xff08;读未提交&#xff09;、READ_COMMITTED&#xff08;读已提交&#x…

Vue基础(2)

19、组件之间传递数据 组件与组件之间不是完全独立的&#xff0c;而是有交集的&#xff0c;那就是组件与组 件之间是可以传递数据的 传递数据的解决方案就是 props ComponentA.vue <template><!-- 使用ComponentB组件&#xff0c;并传递title属性 --><h3>…

Git知识分享

一、理解git首先要理清楚下面五个概念&#xff1a; 1、工作区(git add 命令之前的样子) 2、stash 暂存(暂存工作区和暂存区的更改) 3、暂存区(git add 命令之后的存储区, 4、本地仓库(git commit提交的位置) 5、远程仓库(git push提交的位置) 二、git常用命令&#xff1a; 1、g…

【2024 - 年终总结】叶子增长,期待花开

写在前面&#xff1a;本博客仅作记录学习之用&#xff0c;部分图片来自网络&#xff0c;如需引用请注明出处&#xff0c;同时如有侵犯您的权益&#xff0c;请联系删除&#xff01; 文章目录 前言论博客创作保持2024的记录清单博客科研开源工作生活 总结与展望互动致谢参考 前言…

分类问题(二元,多元逻辑回归,费歇尔判别分析)spss实操

分类模型&#xff1a; 二分类和多分类&#xff1a; 对于二分类模型 &#xff0c;我们将介绍逻辑回归和Fisher线性判别分析两种分类算法; 对于多分类模型&#xff0c;我们将简单介绍Spss中的多分类线性判别分析和多分类逻辑回归的操作步骤 二分类: 基于广义线性模型&#x…

k8s使用nfs持久卷

开启持久化卷后可以实现服务开启在不同节点也能读取到和拿到服务节点的文件。 基本流程为将集群中一个节点作为服务节点安装共享储存应用的服务端选择目录和开启端口&#xff0c;其他节点根据端口挂载目录。然后使用kubesphere选择相应的镜像并将端口信息和挂载目录信息作为参…

kalman滤波器C++设计仿真案例

很多同学看了我之前的文章&#xff0c;都对kalman滤波器的原理有了理解&#xff0c;但我发现&#xff0c;在具体工程设计过程中&#xff0c;还是很多人都感觉到无从下手&#xff0c;一些参数也不知道如何选取。 这样吧。我这里举一些简单的例子&#xff0c;并用C来一步一步的进…

2025.1.21——六、BUU XSS COURSE 1 XSS漏洞|XSS平台搭建

题目来源&#xff1a;buuctf BUU XSS COURSE 1 目录 一、打开靶机&#xff0c;整理信息 二、解题思路 step 1&#xff1a;输入框尝试一下 step 2&#xff1a;开始xss注入 step 3&#xff1a;搭建平台 step 4&#xff1a;利用管理员cookie访问地址 三、小结 二编&#…

微信小程序使用上拉加载onReachBottom。页面拖不动。一直无法触发上拉的事件。

1&#xff0c;可能是原因是你使用了scroll-view的标签&#xff0c;用onReachBottom触发加载事件。这两个是有冲突的。没办法一起使用。如果页面的样式是滚动的是无法去触发页面的onReachBottom的函数的。因此&#xff0c;你使用overflow:auto.来使用页面的某些元素滚动&#xf…