xuos-web/docs/doc/kernel/mm.md

380 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 内存管理
## 前言
计算机系统中,变量、中间数据一般存放在 RAM 中,只有在实际使用时才将它们从 RAM 调入到 CPU 中进行运算。一些数据需要的内存大小需要在程序运行过程中根据实际情况确定,这就要求系统具有对内存空间进行动态管理的能力,在用户需要一段内存空间时,向系统申请,系统选择一段合适的内存空间分配给用户,用户使用完毕后,再释放回系统,以便系统将该段内存空间回收再利用。</br>
由于实时系统中对时间的要求非常严格,内存管理往往要比通用操作系统要求苛刻得多:
</br></br>
1分配内存的时间必须是确定的。一般内存管理算法是根据需要存储的数据的长度在内存中去寻找一个与这段数据相适应的空闲内存块然后将数据存储在里面。而寻找这样一个空闲内存块所耗费的时间是不确定的因此对于实时系统来说这就是不可接受的实时系统必须要保证内存块的分配过程在可预测的确定时间内完成否则实时任务对外部事件的响应也将变得不可确定。
</br></br>
2随着内存不断被分配和释放整个内存区域会产生越来越多的碎片系统中还有足够的空闲内存但因为它们地址并非连续不能组成一块连续的完整内存块会使得程序不能申请到大的内存。对于通用系统而言这种不恰当的内存分配算法可以通过重新启动系统来解决但是对于那些需要常年不间断地工作于野外的嵌入式系统来说频繁的重启是无法接受的。
</br></br>
3嵌入式系统的资源环境也是不尽相同不同的系统内存大小不同如何为这些不同的系统选择适合它们的高效率的内存分配算法也是一个复杂的问题。
</br></br>
XiUOS操作系统提供了独特的内存管理分配算法进行内存管理通过静态内存管理和动态内存管理相结合保证分配和释放内存的实时性提高内存的使用率有效地规避了内存碎片问题同时增加了内存检索的速度。
</br></br>
## 内存堆
### 静态内存管理
#### 静态内存划分
静态内存包含2个链表。其中每个链表都具有block_size静态内存块大小、total_count静态内存块的总个数、free_count空闲内存块的总个数和free_list空闲链表这四个属性。
其中block_size属性记录了当前链表中每个静态内存块的大小
total_count属性记录了系统初始化之后分配给该链表中静态内存块的总个数
free_count属性记录了该链表中还可以分配给用户静态内存块的个数
free_list属性则真正指向各个空闲静态内存块。
</br></br>
下图为静态内存链表的具体情况。图中包括两个静态链表1和2。静态链表头1指向的内存池中存放的静态内存块的大小都是32字节静态链表头2所指向的内存池中存放的静态内存块的大小都是64字节。此外系统分别配置了静态链表头1和静态链表头2中静态内存块的total_count个数为256和128。因此静态链表头1最多可以响应用户256次的小于32字节的内存请求静态链表头2最多可以响应用户128次的介于33-64字节之间的内存请求一旦对应的静态内存块分配完了系统会向动态内存区域寻求内存空间分配。
<div style="display: flex;justify-content: center;align-items: center;">
<img src="./imagesrc/fig1.png" width =100%/>
</div>
#### 静态内存分配
在用户发起内存分配请求时,若用户请求分配的内存空间大小小于等于预设的静态内存阈值,且所述静态内存区域有相应空闲的内存块,则从静态内存区域分配相应大小的内存空间。
</br></br>
在用户发起内存释放请求时,所述分配释放请求解析根据用户提供的内存地址决定后续的操作:
</br>
如果所述内存地址是不合法的,则直接返回结果给用户。
</br>
如果所述内存地址是合法地址,分配释放请求解析模块解析所述内存地址所属内存区域,所述内存地址在静态内存区域,则内存分配任务由静态内存管理模块负责;所述内存地址在动态内存区域,则内存分配任务由动态内存管理模块负责。
</br></br>
从静态内存区域分配静态内存块的过程,包括三种情况。</br>
+ 当用户请求分配内存大小小于等于32字节那么分配释放请求解析模块解析该内存大小由静态内存管理模块负责。系统获取静态链表头1从静态链表头1中获取一个静态内存块返回给用户</br>
+ 当用户请求分配内存大小为3364字节那么分配释放请求解析模块解析该内存大小由静态内存管理模块负责。系统获取静态链表头2从静态链表头2中获取一个静态内存块返回给用户</br>
+ 若所获得的静态链表头是一个空的链表,即没有空闲的静态内存块可用,那么通知分配释放请求解析模块在静态内存区域中内存分配失败。
#### 静态内存释放
当用户请求释放内存所述内存用ptr表示释放操作包括三种情况。</br>
+ 所述ptr内存地址是不合法的则直接返回释放操作。</br>
+ 所述ptr指向的静态内存块属于静态链表头1管理则将所述ptr指向的静态内存块放置到链表头1的头部</br>
+ 若所述ptr指向的静态内存块属于链表头2管理则将所述ptr指向的静态内存块放置到链表头2的头部。
### 动态内存管理
#### 动态内存划分
静态内存划分后剩下的内存区域作为动态内存分配给动态内存区域。动态内存区域管理用到了三种重要的数据结构分别是已分配动态内存段、空闲动态内存段和动态内存管理数据结构。下图显示了已分配动态内存段和空闲动态内存段这两种数据结构。其中已分配动态内存包括元数据信息和用户的数据段用户数据元数据信息中的size属性记录了该动态内存段的内存大小prev_size属性则记录了该动态内存段前一个相邻动态内存段的内存大小从而可以获取前一个相邻动态内存段。用户数据则是分配给用户使用的内存空间。空闲动态内存段的元数据信息具有size、prev_size、prev和next这四个属性其中size和prev_size属性与已分配动态内存段的对应属性表示的意义相同prev属性记录了前一个空闲动态内存段最后一个next属性则记录了下一个空闲动态内存段。
</br></br>
<div style="display: flex;justify-content: center;align-items: center;">
<img src="./imagesrc/fig2.png" width =90%/>
</div>
</br></br>
下图展示了动态内存区域初始化之后的状态,系统将动态内存区域划分为三个动态内存段,分别是起始地址处已分配动态内存段和结尾已分配动态内存段,中间区域的内存用一个空闲动态内存段记录。
</br></br>
<div style="display: flex;justify-content: center;align-items: center;">
<img src="./imagesrc/fig3.png" width =80%/>
</div>
</br></br>
动态内存管理数据结构是动态内存区域管理中的核心数据结构用于组织所有的空闲动态内存段。所述动态内存管理数据结构具有total_size、dynamic_start、dynamic_end和freeLists这四个属性其中total_size属性用于记录动态内存区域的大小dynamic_start属性用于记录起始地址处已分配动态内存段dynamic_end属性用于记录结尾处已分配动态内存段freeLists属性是一个多级链表每一级链表记录了内存大小在指定范围的空闲动态内存段。根据空闲内存段的大小系统判断空闲内存段所属动态链表然后将空闲内存段插入不同的空闲链表中。</br></br>
系统配置空闲链表的个数为10。当动态内存段大小在1-31字节之间则插入到空闲链表freeList1中当动态内存段大小在32-63字节之间时则插入到空闲链表freeList2中需要特别说明的是当动态内存段大小大于或者等于8192字节时都插入到空闲链表freeList10中。如下表所示
<div>
<table style="margin-left: auto; margin-right: auto; table-layout: fixed;" width = 500 >
<tr>
<td width = 240>
申请的内存范围
</td>
<td width = 260>
分配动态链表头
</td>
</tr>
<tr>
<td>
1 ~ 31字节
</td>
<td>
freeList1
</td>
</tr>
<tr>
<td>
32 ~ 63字节
</td>
<td>
freeList2
</td>
</tr>
<tr>
<td>
64 ~127字节
</td>
<td>
freeList3
</td>
</tr>
<tr>
<td>
……
</td>
<td>
……
</td>
</tr>
<tr>
<td>
8192 ~MAX(MAX由系统配置)
</td>
<td>
freeList10
</td>
</tr>
</table>
</div>
#### 动态内存分配
系统根据要分配或者要释放的内存段大小计算要操作的内存段链表头当内存段大小为1 ~ 31字节那么内存计算单元计算之后将获取空闲动态链表头freeList1当内存段大小为32 ~63字节那么内存计算单元计算之后将获取动态链表头freeList2。以此类推。</br></br>
当用户请求从动态内存段申请内存时,动态内存管理模块只能有以下几种分配情况:</br>
+ 用户请求分配30个字节freeList1链表中有一个32字节内存大小可以满足用户的需求之后进行内存块的分割操作32可以分配为30字节 + 2字节但是2字节小于系统要求的8字节对齐大小因此将32字节内存返回给用户</br>
+ 用户请求分配45个字节并且系统已经知道静态内存区域无法分配。内存计算单元计算45字节对应的空闲动态链表获取freeList2freeList2链表中有一个56字节内存大小可以满足用户的需求之后进行内存块的分割操作56可以分配为45字节 + 11字节11字节小于系统要求的8字节对齐大小因此除了返回45字节动态内存给用户还要执行11字节的内存释放操作</br>
+ 用户请求分配8000个字节。内存计算单元计算8000字节对应的空闲动态链表获取freeList9freeList9链表中没有空闲动态内存段没有找到可分配的动态内存段并且freeList9不是最后一级链表更新链表头到freeList10freeList10链表中动态内存段可以满足用户的需求之后进行内存块的分割操作9000可以分配为8192字节 + 808字节808字节大于系统要求的8字节对齐大小因此除了返回8192动态内存给用户还要执行808字节的释放操作</br>
+ 用户请求分配6000000个字节。内存计算单元计算6000000字节对应的空闲动态链表获取freeList10freeList10链表中没有空闲动态内存段没有找到可分配的动态内存段并且freeList10是最后一级链表内存分配操作失败结束内存分配操作。</br>
#### 动态内存释放
当用户请求释放内存所述内存用ptr表示。分配释放请求解析模块解析判断ptr所述区域为动态内存区域则将释放ptr所指向的内存区域到动态内存段中。
<div style="display: flex;justify-content: center;align-items: center;">
<img src="./imagesrc/fig9.png" width =95%/>
</div>
释放一块动态内存段上图提供了动态内存段释放和合并的三种情况其中每一个动态内存段都包含了两个指针分别指向前一个相邻内存段和后一个相邻内存段下面介绍图9展示的三种释放并合并的三种情况
+ 如图中a所示四块动态内存段其中ABD三段为已分配动态内存段C为空闲动态内存段。当前系统要释放内存段B动态内存管理模块检测到B内存段的后一个相邻C也是空闲内存段则系统会合并内存段BC然后释放BC合并之后的内存段。
+ 如图中b所示ABCD四块已分配内存段。系统要释放内存段B动态内存管理模块检测到B的相邻内存段都是已分配内存段那么直接释放内存段B。
+ 如图中c所示四块动态内存段其中AC是空闲内存段BD是已分配内存段。系统要释放内存段B动态内存管理模块检测到B的前一个相邻内存段A和后一个相邻内存段B都是空闲内存段那么先合并ABC三个内存段形成一个大的空闲内存段ABC然后释放合并形成的ABC空闲内存段。
## 内存池
内存堆管理器可以分配任意大小的内存块非常灵活和方便。但其也存在明显的缺点一是分配效率不高在每次分配时都要空闲内存块查找二是容易产生内存碎片。为了提高内存分配的效率并且避免内存碎片XiUOS提供了另外一种内存管理方法内存池。
</br></br>
内存池在创建时先向系统预先申请分配一定数量、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。
## 函数接口
### 内存堆管理
#### 内存初始化
```C
void xs_SystemHeapInit(void *start_phy_address, void *end_phy_address)
```
这个函数用来初始化静态和动态内存。
| 参数 | 描述 |
| :------ | :------ |
| start_phy_address | 内存开始的物理地址 |
| end_phy_address | 内存结束的物理地址 |
</br>
#### 分配和释放内存
```C
void *xs_malloc(xs_size_t size)
```
这个函数用来分配一块合适大小的内存如果分配成功则返回分配内存的首地址如果分配失败则返回NULL。
| 参数 | 描述 |
| :------ | :------ |
| size | 需要被分配的内存大小 |
</br>
```C
void *xs_realloc(void *pointer, xs_size_t size)
```
这个函数用来重新分配一块内存将原内存块中的数据保存到新的内存中去并将原内存块释放。如果分配成功则返回分配内存的首地址如果分配失败则返回NULL。
| 参数 | 描述 |
| :------ | :------ |
| pointer | 原内存块的指针 |
| size | 需要被分配的内存大小 |
</br>
```C
void *xs_calloc(xs_size_t count, xs_size_t size)
```
这个函数会分配多个内存块并将内存块中的数据初始化为0。
| 参数 | 描述 |
| :------ | :------ |
| count | 需要分配的内存块数量 |
| size | 单个内存块的大小 |
</br>
```C
void xs_free(void *pointer)
```
这个函数用来释放一个内存块
| 参数 | 描述 |
| :------ | :------ |
| pointer | 指向需要被释放的内存块的指针 |
</br>
#### 内存钩子函数
```C
void xs_MallocSetHook(void (*MallocHook)(void *pointer, xs_size_t size))
```
这个函数设置了一个 MallocHook 函数MallocHook 函数会在动态内存分配之前调用。
| 参数 | 描述 |
| :------ | :------ |
| MallocHook | MallocHook 函数指针 |
</br>
```C
void xs_FreeSetHook(void (*FreeHook)(void *pointer))
```
这个函数设置了一个FreeHook 函数, FreeHook 函数会在内存块被释放回动态内存区时调用。
| 参数 | 描述 |
| :------ | :------ |
| FreeHook | FreeHook 函数指针 |
</br>
#### 其他函数
```C
void xs_MemoryInfo(xs_uint32 *total_memory, xs_uint32 *used_memory, xs_uint32 *max_used_memory)
```
这个函数用于获取内存的统计信息。
| 参数 | 描述 |
| :------ | :------ |
| total_memory | 内存总量 |
| used_memory | 已使用的内存 |
| max_used_memory | 最大分配内存 |
</br>
```C
void list_mem(void)
```
打印内存信息。
</br>
```C
void list_buddy(void)
```
打印动态内存中的空闲节点信息。
</br>
### 内存池
#### 创建内存池
```C
xs_gm_t xs_CreateMemGather(const char *gm_name, xs_size_t block_number, xs_size_t one_block_size)
```
创建一个内存池。如果创建成功返回第一个内存块的地址否则返回XS_NULL(0)。
| 参数 | 描述 |
| :------ | :------ |
| gm_name | 内存池名 |
| block_number | 内存池中的内存块数量 |
| one_block_size | 每个内存块的容量 |
</br>
```C
xs_err_t xs_InitMemGather(struct xs_MemGather *gm_handler, const char *gm_name, void *begin_address, xs_size_t gm_size, xs_size_t one_block_size)
```
初始化内存池,用于静态内存管理模式。
| 参数 | 描述 |
| :------ | :------ |
| gm_handler | 内存池对象 |
| gm_name | 内存池名称 |
| begin_address | 内存池起始地址 |
| gm_size | 内存池数据区大小 |
| one_block_size | 每个内存块的容量 |
</br>
#### 删除内存池
```C
xs_err_t xs_DeleteMemGather(xs_gm_t gm_handler)
```
删除由xs_CreateMemGather创建的内存池返回0。
| 参数 | 描述 |
| :------ | :------ |
| gm_handler | 需要被删除的内存池地址 |
</br>
```C
xs_err_t xs_RemoveMemGather(struct xs_MemGather *gm_handler)
```
删除由xs_MemGatherInit创建的内存池返回0。
| 参数 | 描述 |
| :------ | :------ |
| gm_handler | 需要被删除的内存池地址 |
</br>
#### 分配内存块
```C
void *xs_AllocBlockMemGather(xs_gm_t gm_handler, xs_int32 wait_time)
```
该函数将从指定的内存池中分配一个内存块。如果内存池中有可用的内存块则从内存池的空闲块链表上取下一个内存块将空闲内存块数目减1并返回这个内存块如果内存池中已经没有空闲内存块则判断超时时间设置若超时时间设置为零则立刻返回空内存块如果等待时间大于零则把当前线程挂起在该内存池对象上直到内存池中有可用的自由内存块或等待时间到达。
| 参数 | 描述 |
| :------ | :------ |
| gm_handler | 内存池对象 |
| wait_time | 超时时间 |
</br>
#### 释放内存块
```C
void xs_FreeBlockMemGather(void *data_block)
```
这个函数用于释放指定的内存块,然后增加内存池对象的可用内存块数目,并把该被释放的内存块加入空闲内存块链表上。接着判断该内存池对象上是否有挂起的线程,如果有则唤醒挂起线程链表上的首线程。
| 参数 | 描述 |
| :------ | :------ |
| data_block | 需要被释放的内存块指针 |
</br>
#### 内存池钩子函数
```C
void xs_GmSetAllocHook(void (*gm_allocation_hook)(struct xs_MemGather *gm, void *date_ptr))
```
该钩子函数会在内存池分配之前调用。
| 参数 | 描述 |
| :------ | :------ |
| MallocHook | 钩子函数指针 |
</br>
```C
void xs_GmSetFreeHook(void (*gm_release_hook)(struct xs_MemGather *gm, void *date_ptr))
```
该钩子函数会在内存池被删除后调用。
| 参数 | 描述 |
| :------ | :------ |
| gm_release_hook | 钩子函数指针 |
</br>
## 应用场景
### 内存堆应用场景
+ 为数组动态分配内存的常规场景。
**优点:** 能够实时分配和释放内存</br>
**缺点:** 容易产生碎片
### 内存池应用场景
+ 当线程间以信号通信时,在发送和接收信号前,可以初始化一个内存池储存信号。
+ Wifi 模块运行动态储存接收数据。
**优点:** 增加了内存动态分配的效率,提高内存的使用率,减少内存碎片的产生。
## 应用示例