# 内存管理

# 前言

XiUOS 操作系统提供了独特的内存管理分配算法进行内存管理,通过静态内存管理和动态内存管理相结合,保证分配和释放内存的实时性,提高内存的使用率,有效地规避了内存碎片问题,同时增加了内存检索的速度。

# 内存堆

# 静态内存管理

# 静态内存划分

静态内存包含2个链表,其中,每个链表都具有 block_size、total_count、free_count和free_list 这四个属性。

  • block_size 记录了当前链表中每个静态内存块的大小
  • total_count 记录了系统初始化之后分配给该链表中静态内存块的总个数
  • free_count 记录了该链表中还可以分配给用户静态内存块的个数
  • free_list 则真正指向各个空闲静态内存块

下图为静态内存链表的具体情况,图中包括两个静态链表1和2。静态链表头1指向的内存池中存放的静态内存块的大小都是32字节,静态链表头2所指向的内存池中存放的静态内存块的大小都是64字节。此外,系统分别配置了静态链表头1和静态链表头2中静态内存块的total_count个数为256和128。因此,静态链表头1最多可以响应用户256次的小于32字节的内存请求,静态链表头2最多可以响应用户128次的介于33-64字节之间的内存请求,一旦对应的静态内存块分配完了,系统会向动态内存区域寻求内存空间分配。

# 静态内存分配

在用户发起内存分配请求时,若用户请求分配的内存空间大小小于等于预设的静态内存阈值,且所述静态内存区域有相应空闲的内存块,则从静态内存区域分配相应大小的内存空间。

从静态内存区域分配静态内存块的过程,包括三种情况。

  • 当用户请求分配内存大小小于等于32字节,那么分系统获取静态链表头1,从静态链表头1中获取一个静态内存块返回给用户;
  • 当用户请求分配内存大小为33~64字节,那么系统获取静态链表头2,从静态链表头2中获取一个静态内存块返回给用户;
  • 若所获得的静态链表头是一个空的链表,即没有空闲的静态内存块可用,那么静态内存分配失败。

# 静态内存释放

当用户请求释放内存,释放操作包括三种情况。

  • 要释放的内存地址是不合法的,则直接返回释放操作;
  • 要释放的内存地址属于静态链表头1区域,则将内存块放置到链表头1的头部;
  • 要释放的内存地址属于链表头2区域,则将内存块放置到链表头2的头部。

# 动态内存管理

# 动态内存划分

静态内存划分后,剩下的内存区域作为动态内存分配给动态内存区域。动态内存区域管理用到了三种重要的数据结构,分别是已分配动态内存段、空闲动态内存段和动态内存管理数据结构。下图显示了已分配动态内存段和空闲动态内存段这两种数据结构。其中,已分配动态内存包括元数据信息和用户的数据段(用户数据),元数据信息中的 size 属性记录了该动态内存段的内存大小,prev_size 属性则记录了该动态内存段前一个相邻动态内存段的内存大小,从而可以获取前一个相邻动态内存段。用户数据则是分配给用户使用的内存空间。空闲动态内存段的元数据信息具有 size、prev_size、prev 和 next 这四个属性,其中,size 和 prev_size 属性与已分配动态内存段的对应属性表示的意义相同,prev 属性记录了前一个空闲动态内存段,最后一个 next 属性则记录了下一个空闲动态内存段。



下图展示了动态内存区域初始化之后的状态,系统将动态内存区域划分为三个动态内存段,分别是起始地址处已分配动态内存段和结尾已分配动态内存段,中间区域的内存用一个空闲动态内存段记录。



动态内存管理数据结构是动态内存区域管理中的核心数据结构,用于组织所有的空闲动态内存段。所述动态内存管理数据结构具有 total_size、dynamic_start、dynamic_end和freeLists 这四个属性,其中,total_size 属性用于记录动态内存区域的大小,dynamic_start 属性用于记录起始地址处已分配动态内存段,dynamic_end 属性用于记录结尾处已分配动态内存段,freeLists 属性是一个多级链表,每一级链表记录了内存大小在指定范围的空闲动态内存段。根据空闲内存段的大小,系统判断空闲内存段所属动态链表,然后将空闲内存段插入不同的空闲链表中。

系统可配置空闲链表的个数为10。当动态内存段大小在1-31字节之间,则插入到空闲链表freeList1中,当动态内存段大小在32-63字节之间时,则插入到空闲链表freeList2中,需要特别说明的是,当动态内存段大小大于或者等于8192字节时,都插入到空闲链表freeList10中。如下表所示:

申请的内存范围 分配动态链表头
1 ~ 31字节 freeList1
32 ~ 63字节 freeList2
64 ~127字节 freeList3
…… ……
8192 ~MAX(MAX由系统配置) freeList10

# 动态内存分配

系统根据要分配或者要释放的内存段大小计算要操作的内存段链表头,当内存段大小为1 ~ 31字节,那么计算之后将获取空闲动态链表头freeList1,当内存段大小为32 ~ 63字节,那么计算之后将获取动态链表头freeList2,依次类推。
当用户请求从动态内存段申请内存时,有以下几种分配情况:

  • 用户请求分配n个字节(对齐后的字节),freeListK 链表中有一个m字节内存大小可以满足用户的需求,之后进行内存块的分割操作,分成n字节和(n-m)字节,其中 n-m 小于 8字节,因此将m字节内存返回给用户;
  • 用户请求分配n个字节(对齐后的字节),freeListK 链表中有一个m字节内存大小可以满足用户的需求,之后进行内存块的分割操作,分成n字节和(n-m)字节,其中 n-m 大于或等于 8字节,因此将n字节内存返回给用户并且将n-m个字节重新插入动态内存管理结构;
  • 用户请求分配n个字节(对齐后的字节),freeListK 链表中没有空闲内存大小可以满足用户的需求,并且freeListK+1级链表存在,则从freeListK+1链表中分配内存;
  • 用户请求分配n个字节(对齐后的字节),freeListK 链表中没有空闲内存大小可以满足用户的需求,并且freeListK+1级链表不存在,则内存分配失败。

# 动态内存释放

当用户请求释放内存,系统释放内存区域到对应的动态内存段中。

释放一块动态内存段,上图提供了动态内存段释放和合并的三种情况,其中,每一个动态内存段都包含了两个指针,分别指向前一个相邻内存段和后一个相邻内存段,下面介绍图9展示的三种释放并合并的三种情况:

  • 如图中(a)所示,四块动态内存段,其中,A,B,D三段为已分配动态内存段,C为空闲动态内存段。当前,系统要释放内存段B,动态内存管理模块检测到B内存段的后一个相邻C也是空闲内存段,则系统会合并内存段B,C,然后释放BC合并之后的内存段。
  • 如图中(b)所示,A,B,C,D四块已分配内存段。系统要释放内存段B,动态内存管理模块检测到B的相邻内存段都是已分配内存段,那么直接释放内存段B。
  • 如图中(c)所示,四块动态内存段,其中,A,C是空闲内存段,B,D是已分配内存段。系统要释放内存段B,动态内存管理模块检测到B的前一个相邻内存段A和后一个相邻内存段B都是空闲内存段,那么先合并A,B,C三个内存段形成一个大的空闲内存段ABC,然后释放合并形成的ABC空闲内存段。

# 内存池

内存堆管理器可以分配任意大小的内存块,非常灵活和方便。但其也存在明显的缺点:一是分配效率不高,在每次分配时,都要空闲内存块查找;二是容易产生内存碎片。为了提高内存分配的效率,并且避免内存碎片,XiUOS提供了另外一种内存管理方法,内存池。
内存池在创建时先向系统预先申请分配一定数量、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。

# 函数接口

# 内存堆管理

# 内存初始化

void InitBoardMemory(void *start_phy_address, void *end_phy_address);

这个函数用来初始化静态和动态内存。

参数 描述
start_phy_address 内存开始的物理地址
end_phy_address 内存结束的物理地址

# 分配和释放内存

void *x_malloc(xs_size_t size);

这个函数用来分配一块合适大小的内存,如果分配成功,则返回分配内存的首地址;否则返回NULL。

参数 描述
size 需要被分配的内存大小

void *x_realloc(void *pointer, xs_size_t size);

这个函数用来重新分配一块内存,将原内存块中的数据保存到新的内存中去,并将原内存块释放。如果分配成功,则返回分配内存的首地址;否则返回NULL。

参数 描述
pointer 原内存块的指针
size 需要重新分配的内存大小

void *x_calloc(xs_size_t count, xs_size_t size);

这个函数会分配多个内存块并将内存块中的数据初始化为0。

参数 描述
count 需要分配的内存块数量
size 单个内存块的大小

void x_free(void *pointer);

这个函数用来释放内存。

参数 描述
pointer 指向需要被释放的内存

# 其他函数

void MemoryInfo(uint32 *total_memory, uint32 *used_memory, uint32 *max_used_memory);

这个函数用于获取内存的统计信息。

参数 描述
total_memory 内存总量
used_memory 已使用的内存
max_used_memory 最大分配内存

void ShowMemory(void);

打印内存信息。


void ListBuddy(void);

打印动态内存中的空闲节点信息。


# 内存池

# 创建内存池

GatherMemType CreateMemGather(const char *gm_name, x_size_t block_number, x_size_t one_block_size);

创建一个内存池。如果创建成功,返回第一个内存块的地址,否则返回XS_NULL。

参数 描述
gm_name 内存池名
block_number 内存池中的内存块数量
one_block_size 每个内存块的容量

x_err_t InitMemGather(struct MemGather *gm_handler, const char *gm_name, void  *begin_address, x_size_t gm_size, x_size_t  one_block_size);

初始化内存池,用于静态内存管理模式。

参数 描述
gm_handler 内存池对象
gm_name 内存池名称
begin_address 内存池起始地址
gm_size 内存池数据区大小
one_block_size 每个内存块的容量

# 删除内存池

x_err_t DeleteMemGather(GatherMemType gm_handler);

删除由xs_CreateMemGather创建的内存池。

参数 描述
gm_handler 需要被删除的内存池

x_err_t RemoveMemGather(struct MemGather *gm_handler);

删除由xs_MemGatherInit创建的内存池。

参数 描述
gm_handler 需要被移除的内存池

# 分配内存块

void *AllocBlockMemGather(GatherMemType gm_handler, int32 msec);

该函数将从指定的内存池中分配一个内存块。如果内存池中有可用的内存块,则从内存池的空闲块链表上取下一个内存块,将空闲内存块数目减1并返回这个内存块;如果内存池中已经没有空闲内存块,则判断超时时间设置:若超时时间设置为零,则立刻返回空内存块;如果等待时间大于零,则把当前线程挂起在该内存池对象上,直到内存池中有可用的自由内存块,或等待时间到达。

参数 描述
gm_handler 内存池对象
wait_time 超时时间

# 释放内存块

void FreeBlockMemGather(void *data_block);

这个函数用于释放指定的内存块,然后增加内存池对象的可用内存块数目,并把该被释放的内存块加入空闲内存块链表上。接着判断该内存池对象上是否有挂起的线程,如果有则唤醒挂起线程链表上的首线程。

参数 描述
data_block 需要被释放的内存块指针

# 应用场景

# 内存堆应用场景

  • 为数组动态分配内存的常规场景。

优点: 能够实时分配和释放内存
缺点: 容易产生碎片

# 内存池应用场景

  • 当线程间以信号通信时,在发送和接收信号前,可以初始化一个内存池储存信号。
  • Wifi 模块运行动态储存接收数据。

优点: 增加了内存动态分配的效率,提高内存的使用率,减少内存碎片的产生。

Last Updated: 4/30/2021, 6:02:45 PM