系统程序员成长计划-内存管理(三)

news/2024/7/20 16:07:23 标签: 内存管理, 嵌入式, c/c++

转载时请注明出处和作者联系方式
文章出处:http://www.limodev.cn/blog
作者联系方式:李先静 <xianjimli at hotmail dot com>

内存管理

在前面学习共享内存的时候,我们重新实现了循环队列,两个实现的不同之处只是在于内存分配和释放上。对比一下 fifo_ring_create的实现:

第一种实现用malloc分配内存。

FifoRing* fifo_ring_create(size_t length)
{
FifoRing* thiz = NULL;

return_val_if_fail(length > 1, NULL);

thiz = (FifoRing*)malloc(sizeof(FifoRing) + length * sizeof(void*));

if(thiz != NULL)
{
thiz->r_cursor = 0;
thiz->w_cursor = 0;
thiz->length = length;
}

return thiz;
}

第二种实现用 shmem_alloc分配内存。

FifoRing* fifo_ring_create(size_t length)
{
FifoRing* thiz = NULL;

return_val_if_fail(length > 1, NULL);

thiz = (FifoRing*)shmem_alloc(sizeof(FifoRing) + length * sizeof(void*));

if(thiz != NULL)
{
if(thiz->inited == 0)
{
thiz->r_cursor = 0;
thiz->w_cursor = 0;
thiz->length = length;
thiz->inited = 1;
}
}

return thiz;
}

只是一行代码之差,就要把整个队列重写一遍,不符合我们前面倡导的DRY(don’t repeat yourself)原则。这里可以看出,内存管理器有不同的实现,所以我们引入内存管理器这个接口来隔离变化。内存管理器的基本功能有:

o 分配内存
o 释放内存
o 扩展/缩小已经分配的内存
o 分配清零的内存

据此,我们定义Allocator接口如下:

struct  _Allocator;
typedef struct _Allocator Allocator;

typedef void* (*AllocatorCallocFunc)(Allocator* thiz, size_t nmemb, size_t size);
typedef void* (*AllocatorAllocFunc)(Allocator* thiz, size_t size);
typedef void (*AllocatorFreeFunc)(Allocator* thiz, void *ptr);
typedef void* (*AllocatorReallocFunc)(Allocator* thiz, void *ptr, size_t size);
typedef void (*AllocatorDestroyFunc)(Allocator* thiz);

struct _Allocator
{
AllocatorCallocFunc calloc;
AllocatorAllocFunc alloc;
AllocatorFreeFunc free;
AllocatorReallocFunc realloc;
AllocatorDestroyFunc destroy;

char priv[0];
};

基于malloc系统函数的实现:

static void*  allocator_normal_calloc(Allocator* thiz, size_t nmemb, size_t size)
{
return calloc(nmemb, size);
}
...
Allocator* allocator_normal_create(void)
{
Allocator* thiz = (Allocator*)calloc(1, sizeof(Allocator));

if(thiz != NULL)
{
thiz->calloc = allocator_normal_calloc;
thiz->alloc = allocator_normal_alloc;
thiz->realloc = allocator_normal_realloc;
thiz->free = allocator_normal_free;
thiz->destroy = allocator_normal_destroy;
}

return thiz;
}

这里函数与标准C函数一一对应,只需要简单包装就行了。

对于共享内存,通常的做法是先分配一大块内存,然后进行二次分配,此时需要编写自己的内存管理器。内存分配器是很奇特的,任何初学者都可以设计自己 的内存分配器,但同时任何高手都不敢说自己能设计出最好的内存分配器。为什么内存分配器很难写好呢?因为设计好的内存分配器需要考虑很多因素:

o 最大化兼容性

实现内存分配器时,先要定义出分配器的接口函数。接口函数没有必要标新立异,而是要遵循现有标准(如POSIX或者Win32),让使用者可以平滑的过度到新的内存分配器上。

o 最大化可移植性

通常情况下,内存分配器要向OS申请内存,然后进行二次分配,这要调用OS提供的函数才行。OS提供的函数则是因平台而异,尽量抽象出平台相关的代码,才能保证内存分配器的可移植性。

o 浪费最小的空间

内存分配器要管理内存,必然要使用一些自己的数据结构,这些数据结构本身也要占内存空间。在用户眼中,这些内存空间毫无疑问是浪费掉了,如果浪费在 内存分配器本身的内存太多,显然是不可以接受的。内存碎片也是浪费空间的罪魁祸首,内存碎片是一些不连续的小块内存,它们总量可能很大,但因为其非连续性 而无法使用,这些空间在某种程度上说也是浪费的。

o 最快的速度

内存分配/释放是常用的操作。按着2/8原则,常用函数的性能对系统的整体性能影响最大,所以内存分配器的速度越快越好。遗憾的是,最先匹配算法,最优匹配算法和最差匹配算法,谁也不能说谁更快,因为快与慢要依赖于具体的应用环境。

o 最大化可调性(以适应于不同的情况)

内存管理算法设计的难点就在于要适应不同的情况。事实上,如果缺乏应用的上下文,是无法评估内存管理算法的好坏的,因为专用算法总是在时/空性能上 有更优的表现。为每种情况都写一套内存管理算法,显然是不太合适的。我们不需要追求最优算法,那样代价太高,能达到次优就行了。设计一套通用内存管理算 法,通过一些参数对它进行配置,可以让它在特定情况也有相当出色的表现,这就是可调性。

o 最大化调试功能

作为一个C/C++程序员,内存错误可以说是我们的噩梦,上一次的内存错误一定还让你记忆犹新。内存分配器提供的调试功能,强大易用,特别对于嵌入式环境来说,内存错误检测工具缺乏的情况下,内存分配器提供的调试功能就更不可少了。

o 最大化适应性

前面说了最大化可调性,以便让内存分配器适用于不同的情况。但是,对于不同情况都要去调整配置,还是有点麻烦。尽量让内存分配器适用于很广的情况,只有极少情况下才去调整配置,这就是内存分配器的适应性。

设计是一个多目标优化的过程,而且有些目标之间存在着竞争。如何平衡这些竞争是设计的难点之一。在不同的情况下,这些目标的重要性是不一样的,所以根本不存在一个最好的内存分配器。换句话说,内存分配器的实现是变化的,要根据不同的应用环境而变化。

下面我们实现一个傻瓜型的内存管理器,按上面的标准来看,它没有什么实际的意义,但它的优点是简单,仅仅200多行代码,就展示了内存管理器的基本原理。

o 分配过程

所有空闲的内存块放在一个双向链表中,最初只有一块。分配时使用首次匹配算法,在第一个空闲块上进行分配。

static void*  allocator_self_manage_alloc(Allocator* thiz, size_t size)
{
FreeNode* iter = NULL;
size_t length = REAL_SIZE(size);
PrivInfo* priv = (PrivInfo*)thiz->priv;

/*查找第一个满足条件的空闲块*/
for(iter = priv->free_list; iter != NULL; iter = iter->next)
{
if(iter->length > length)
{
break;
}
}

return_val_if_fail(iter != NULL, NULL);

/*如果找到的空闲块刚好满足需求,就从空闲块链表中移出它*/
if(iter->length < (length + MIN_SIZE))
{
if(priv->free_list == iter)
{
priv->free_list = iter->next;
}

if(iter->prev != NULL)
{
iter->prev->next = iter->next;
}
if(iter->next != NULL)
{
iter->next->prev = iter->prev;
}
}
else
{
/*如果找到的空闲块比较大,就把它拆成两个块,把多余的空闲内存放回去*/
FreeNode* new_iter = (FreeNode*)((char*)iter + length);

new_iter->length = iter->length - length;
new_iter->next = iter->next;
new_iter->prev = iter->prev;

if(iter->prev != NULL)
{
iter->prev->next = new_iter;
}
if(iter->next != NULL)
{
iter->next->prev = new_iter;
}

if(priv->free_list == iter)
{
priv->free_list = new_iter;
}
iter->length = length;
}
/*返回可用的内存*/
return (char*)iter + sizeof(size_t);
}

这里对空闲块的管理不占用有效内存空间,它只是强制的把空闲块转换成 FreeNode结构,这要求任何空闲块的大小都不小于sizeof(FreeNode)。

o 释放内存

释放时把内存块放回空闲链表,然后对相邻居的内存块进行合并。

static void   allocator_self_manage_free(Allocator* thiz, void *ptr)
{
FreeNode* iter = NULL;
FreeNode* free_iter = NULL;
PrivInfo* priv = (PrivInfo*)thiz->priv;

return_if_fail(ptr != NULL);

free_iter = (FreeNode*)((char*)ptr - sizeof(size_t));

free_iter->prev = NULL;
free_iter->next = NULL;

if(priv->free_list == NULL)
{
priv->free_list = free_iter;

return;
}
/*根据内存块地址的大小,把它插入到适当的位置。*/
for(iter = priv->free_list; iter != NULL; iter = iter->next)
{
if((size_t)iter > (size_t)free_iter)
{
free_iter->next = iter;
free_iter->prev = iter->prev;
if(iter->prev != NULL)
{
iter->prev->next = free_iter;
}
iter->prev = free_iter;

if(priv->free_list == iter)
{
priv->free_list = free_iter;
}

break;
}

if(iter->next == NULL)
{
iter->next = free_iter;
free_iter->prev = iter;

break;
}
}

/*对相邻居的内存进行合并*/
allocator_self_manage_merge(thiz, free_iter);

return;
}

有了Allocator接口,我们也可以通过装饰模式,为内存管理器加上越界/泄露检查等其它辅助功能。下面的Allocator是检查内存越界的装饰。

o 实现函数

static void*  allocator_checkbo_alloc(Allocator* thiz, size_t size)
{
char* ptr = NULL;
size_t total = size + 3 * sizeof(void*);
PrivInfo* priv = (PrivInfo*)thiz->priv;

if((ptr = allocator_alloc(priv->real_allocator, total)) != NULL)
{
*(size_t*)ptr = size;
memset(ptr + sizeof(void*), BEGIN_MAGIC, sizeof(void*));
memset(ptr + 2 * sizeof(void*) + size, END_MAGIC, sizeof(void*));
}

return ptr + 2 * sizeof(void*);
}

分配内存时:多分配3 * sizeof(void*)个字节,分别用来记录内存块的长度及前后的Magic数据。然后调用实际的(即被装饰的)内存分配器分配内存,记录长度并填充Magic数据,最后返回内存指针给调用者。

static void   allocator_checkbo_free(Allocator* thiz, void *ptr)
{
PrivInfo* priv = (PrivInfo*)thiz->priv;

if(ptr != NULL)
{
char magic[sizeof(void*)];
char* real_ptr = (char*)ptr - 2 * sizeof(void*);
size_t size = *(size_t*)(real_ptr);

memset(magic, BEGIN_MAGIC, sizeof(void*));
assert(memcmp((char*)ptr - sizeof(void*), magic, sizeof(void*)) == 0);
memset(magic, END_MAGIC, sizeof(void*));
assert(memcmp((char*)ptr + size , magic, sizeof(void*)) == 0);

allocator_free(priv->real_allocator, real_ptr);
}

return;
}

释放内存时:取得内存块的长度,然后检查前后的magic数据是否被修改,如果被修改则认为存在写越界的错误。


http://www.niftyadmin.cn/n/1229827.html

相关文章

十四、应用监控(1)

本章概要 监控端点配置&#xff08;开启端点&#xff0c;暴露端点&#xff0c;端点保护&#xff0c;端点响应缓存&#xff0c;路径映射&#xff0c;CORS支持&#xff0c;健康信息&#xff0c;应用信息&#xff09; 当一个Spring Boot 项目运行时&#xff0c;开发者需要对 Spr…

十四、应用监控(2)

本章概要 监控信息可视化邮件报警 14.2 监控信息可视化 Spring Boot 中提供了监管信息管理段&#xff0c;用来实现监控信息的可视化&#xff0c;这样可以方便开发者快速查看系统运行情况&#xff0c;而不用一个一个地调用接口。 创建 Spring Boot Web 工程&#xff0c;添加以…

系统程序员成长计划-内存管理(四)

系统程序员成长计划&#xff0d;内存管理(四)转载时请注明出处和作者联系方式 文章出处&#xff1a;http://www.limodev.cn/blog 作者联系方式&#xff1a;李先静 <xianjimli at hotmail dot com> 惯用手法 《POSA(面向模式的软件架构)》中根据模式粒度把模式分为三类…

十五、项目构建与部署

本章概要 构建 JAR构建 WAR Spring Boot 项目可以内嵌 Servlet 容器&#xff0c;因此它的部署变得极为方便&#xff0c;可以直接打成可执行 JAR 包部署在有 Java 运行环境的服务器上&#xff0c;也可以像传统的 Java Web 应用程序那样打成 WAR 包运行。 15.1 JAR 15.1.1 项…

系统程序员成长计划-像机器一样思考(一)

系统程序员成长计划&#xff0d;像机器一样思考(一)Sunday, May 10th, 2009|Author: admin 转载时请注明出处和作者联系方式 文章出处&#xff1a;http://www.limodev.cn/blog 作者联系方式&#xff1a;李先静 <xianjimli at hotmail dot com> 变参函数的实现原理 C语言要…

一、Vue.js 概述

本章概要 Vue 2.x 响应式系统的实现原理Vue 3.0 响应式系统的实现原理体验 Vue 3.0 响应式系统Vue 3.0 带来的新变化 1.1 Web 前端技术的发展 略&#xff0c;自行百度。 1.2 MV* 模式 MVC 是 Web 开发中应用非常广泛的一种框架模式&#xff0c;之后又演变出 MVP 模式和 MV…

db_file_multiblock_read_count 的自动调整

转之Eygle的文章&#xff1a;http://www.eygle.com/archives/2009/03/db_file_multiblock_read_count_auto.html 关于这个参数&#xff0c;经过几多变化&#xff0c;在Oracle10gR2中终于修成了正果&#xff0c;实现了自动调整。 很久以前演过过这个参数&#xff0c;有过这样的…

openGauss安装

系统环境&#xff1a;CentOS 7.6 &#xff0c;openGauss版本&#xff1a;2.1.0 openGauss 下载地址&#xff1a;https://opengauss.obs.cn-south-1.myhuaweicloud.com/2.1.0/x86/openGauss-2.1.0-CentOS-64bit-all.tar.gz 1. 环境准备 1.1 安装依赖包 yum install -y libaio-…