Linux怎么利用自带的内存池编写自己的系统vb调用vc编写的dll---内存分配

c语言编程|c语言入门|c语言教程|C语言程序设计|c语言学习|c语言门|c语言教程下载|如何学习c语言|c语言函数
字号:大 中 小
linux内在分配:内存池
8.2.2. 内存池 在内核中有不少地方内存分配不允许失败. 作为一个在这些情况下确保分配的方式, 内核开发者创建了一个已知为内存池(或者是 "mempool" )的抽象. 一个内存池真实地只是一类后备缓存, 它尽力一直保持一个空闲内存列表给紧急时使用. 一个内存池有一个类型 mempool_t ( 在 &linux/mempool.h& 中定义); 你可以使用 mempool_create 创建一个: mempool_t *mempool_create(int min_nr, &mempool_alloc_t *alloc_fn, &mempool_free_t *free_fn, &void *pool_data);& min_nr 参数是内存池应当一直保留的最小数量的分配的对象. 实际的分配和释放对象由 alloc_fn 和 free_fn 处理, 它们有这些原型: typedef void *(mempool_alloc_t)(int gfp_mask, void *pool_data); typedef void (mempool_free_t)(void *element, void *pool_data); 给 mempool_create 最后的参数 ( pool_data ) 被传递给 alloc_fn 和 free_fn. 如果需要, 你可编写特殊用途的函数来处理 mempool 的内存分配. 常常, 但是, 你只需要使内核 slab 分配器为你处理这个任务. 有 2 个函数 ( mempool_alloc_slab 和 mempool_free_slab) 来进行在内存池分配原型和 kmem_cache_alloc 和 kmem_cache_free 之间的感应淬火. 因此, 设置内存池的代码常常看来如此: cache = kmem_cache_create(. . .);& pool = mempool_create(MY_POOL_MINIMUM,mempool_alloc_slab, mempool_free_slab, cache);& 一旦已创建了内存池, 可以分配和释放对象,使用: void *mempool_alloc(mempool_t *pool, int gfp_mask); void mempool_free(void *element, mempool_t *pool); 当内存池创建了, 分配函数将被调用足够的次数来创建一个预先分配的对象池. 因此, 对 mempool_alloc 的调用试图从分配函数请求额外的对象; 如果那个分配失败, 一个预先分配的对象(如果有剩下的)被返回. 当一个对象被用 mempool_free 释放, 它保留在池中, 如果对齐预分配的对象数目小于最小量; 否则, 它将被返回给系统.
一个 mempool 可被重新定大小, 使用: int mempool_resize(mempool_t *pool, int new_min_nr, int gfp_mask); 这个调用, 如果成功, 调整内存池的大小至少有 new_min_nr 个对象. 如果你不再需要一个内存池, 返回给系统使用: void mempool_destroy(mempool_t *pool);& 你编写返回所有的分配的对象, 在销毁 mempool 之前, 否则会产生一个内核 oops. 如果你考虑在你的驱动中使用一个 mempool, 请记住一件事: mempools 分配一块内存在一个链表中, 对任何真实的使用是空闲和无用的. 容易使用 mempools 消耗大量的内存. 在几乎每个情况下, 首选的可选项是不使用 mempool 并且代替以简单处理分配失败的可能性. 如果你的驱动有任何方法以不危害到系统完整性的方式来响应一个分配失败, 就这样做. 驱动代码中的 mempools 的使用应当少.
function open_phone(e) {
var context = document.title.replace(/%/g, '%');
var url = document.location.
open("/ishare.do?m=t&u=" + encodeURIComponent(url) + "&t=" + encodeURIComponent(context) + "&sid=70cd6ed4a0");
!觉得精彩就顶一下,顶的多了,文章将出现在更重要的位置上。
大 名:&&[]&&[注册成为和讯用户]
(不填写则显示为匿名者)
(您的网址,可以不填)
请根据下图中的字符输入验证码:
(您的评论将有可能审核后才能发表)
已成功添加“”到
请不要超过6个字Linux的虚拟内存管理有几个关键概念:
Linux 虚拟地址空间如何分布?malloc和free是如何分配和释放内存?如何查看堆内内存的碎片情况?既然堆内内存brk和sbrk不能直接释放,为什么不全部使用 mmap 来分配,munmap直接释放呢 ?
Linux 的虚拟内存管理有几个关键概念: 1、每个进程都有独立的虚拟地址空间,进程访问的虚拟地址并不是真正的物理地址;
2、虚拟地址可通过每个进程上的页表(在每个进程的内核虚拟地址空间)与物理地址进行映射,获得真正物理地址;
3、如果虚拟地址对应物理地址不在物理内存中,则产生缺页中断,真正分配物理地址,同时更新进程的页表;如果此时物理内存已耗尽,则根据内存替换算法淘汰部分页面至物理磁盘中。
一、Linux 虚拟地址空间如何分布?
Linux 使用虚拟地址空间,大大增加了进程的寻址空间,由低地址到高地址分别为:
1、只读段:该部分空间只能读,不可写;(包括:代码段、rodata 段(C常量字符串和#define定义的常量) )
2、数据段:保存全局变量、静态变量的空间;
3、堆 :就是平时所说的动态内存, malloc/new 大部分都来源于此。其中堆顶的位置可通过函数 brk 和 sbrk 进行动态调整。
4、文件映射区域:如动态库、共享内存等映射物理空间的内存,一般是 mmap 函数所分配的虚拟地址空间。
5、栈:用于维护函数调用的上下文空间,一般为 8M ,可通过 ulimit &s 查看。
6、内核虚拟空间:用户代码不可见的内存区域,由内核管理(页表就存放在内核虚拟空间)。
下图是 32 位系统典型的虚拟地址空间分布(来自《深入理解计算机系统》)。
32 位系统有4G 的地址空间::
其中 0xxbfffffff 是用户空间,0xcxffffffff 是内核空间,包括内核代码和数据、与进程相关的数据结构(如页表、内核栈)等。另外,%esp 执行栈顶,往低地址方向变化;brk/sbrk 函数控制堆顶_edata往高地址方向变化。
64位系统结果怎样呢? 64 位系统是否拥有 2^64 的地址空间吗?
事实上, 64 位系统的虚拟地址空间划分发生了改变:
1、地址空间大小不是2^32,也不是2^64,而一般是2^48。
因为并不需要 2^64 这么大的寻址空间,过大空间只会导致资源的浪费。64位Linux一般使用48位来表示虚拟地址空间,40位表示物理地址,
这可通过#cat& /proc/cpuinfo 来查看:
2、其中,0x07fffffffffff 表示用户空间, 0xFFFF~ 0xFFFFFFFFFFFFFFFF 表示内核空间,共提供 256TB(2^48) 的寻址空间。
这两个区间的特点是,第 47 位与 48~63 位相同,若这些位为 0 表示用户空间,否则表示内核空间。
3、用户空间由低地址到高地址仍然是只读段、数据段、堆、文件映射区域和栈;
二、malloc和free是如何分配和释放内存?
如何查看进程发生缺页中断的次数?
用# ps -o majflt,minflt -C program 命令查看
majflt代表major fault,中文名叫大错误,minflt代表minor fault,中文名叫小错误。
这两个数值表示一个进程自启动以来所发生的缺页中断的次数。
可以用命令ps -o majflt minflt -C program来查看进程的majflt, minflt的值,这两个值都是累加值,从进程启动开始累加。在对高性能要求的程序做压力测试的时候,我们可以多关注一下这两个值。
如果一个进程使用了mmap将很大的数据文件映射到进程的虚拟地址空间,我们需要重点关注majflt的值,因为相比minflt,majflt对于性能的损害是致命的,随机读一次磁盘的耗时数量级在几个毫秒,而minflt只有在大量的时候才会对性能产生影响。
发成缺页中断后,执行了那些操作?
当一个进程发生缺页中断的时候,进程会陷入内核态,执行以下操作:
1、检查要访问的虚拟地址是否合法
2、查找/分配一个物理页
3、填充物理页内容(读取磁盘,或者直接置0,或者啥也不干)
4、建立映射关系(虚拟地址到物理地址)
重新执行发生缺页中断的那条指令
如果第3步,需要读取磁盘,那么这次缺页中断就是majflt,否则就是minflt。
内存分配的原理
从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)。
1、brk是将数据段(.data)的最高地址指针_edata往高地址推;
2、mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。
这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。
在标准C库中,提供了malloc/free函数分配释放内存,这两个函数底层是由brk,mmap,munmap这些系统调用实现的。
下面以一个例子来说明内存分配的原理:
情况一、malloc小于128k的内存,使用brk分配内存,将_edata往高地址推(只分配虚拟空间,不对应物理内存(因此没有初始化),第一次读/写数据时,引起内核缺页中断,内核才分配对应的物理内存,然后虚拟地址空间建立映射关系),如下图:
1、进程启动的时候,其(虚拟)内存空间的初始布局如图1所示。
其中,mmap内存映射文件是在堆和栈的中间(例如libc-2.2.93.so,其它数据文件等),为了简单起见,省略了内存映射文件。
_edata指针(glibc里面定义)指向数据段的最高地址。
2、进程调用A=malloc(30K)以后,内存空间如图2:
&&&&& malloc函数会调用brk系统调用,将_edata指针往高地址推30K,就完成虚拟内存分配。
你可能会问:只要把_edata+30K就完成内存分配了?
事实是这样的,_edata+30K只是完成虚拟地址的分配,A这块内存现在还是没有物理页与之对应的,等到进程第一次读写A这块内存的时候,发生缺页中断,这个时候,内核才分配A这块内存对应的物理页。也就是说,如果用malloc分配了A这块内容,然后从来不访问它,那么,A对应的物理页是不会被分配的。
3、进程调用B=malloc(40K)以后,内存空间如图3。
情况二、malloc大于128k的内存,使用mmap分配内存,在堆和栈之间找一块空闲内存分配(对应独立内存,而且初始化为0),如下图:
4、进程调用C=malloc(200K)以后,内存空间如图4:
默认情况下,malloc函数分配内存,如果请求内存大于128K(可由M_MMAP_THRESHOLD选项调节),那就不是去推_edata指针了,而是利用mmap系统调用,从堆和栈的中间分配一块虚拟内存。
这样子做主要是因为::
brk分配的内存需要等到高地址内存释放以后才能释放(例如,在B释放之前,A是不可能释放的,这就是内存碎片产生的原因,什么时候紧缩看下面),而mmap分配的内存可以单独释放。
当然,还有其它的好处,也有坏处,再具体下去,有兴趣的同学可以去看glibc里面malloc的代码了。
5、进程调用D=malloc(100K)以后,内存空间如图5;
6、进程调用free(C)以后,C对应的虚拟内存和物理内存一起释放。
7、进程调用free(B)以后,如图7所示:
&&&&&&& B对应的虚拟内存和物理内存都没有释放,因为只有一个_edata指针,如果往回推,那么D这块内存怎么办呢?
当然,B这块内存,是可以重用的,如果这个时候再来一个40K的请求,那么malloc很可能就把B这块内存返回回去了。
8、进程调用free(D)以后,如图8所示:
&&&&&&& B和D连接起来,变成一块140K的空闲内存。
9、默认情况下:
当最高地址空间的空闲内存超过128K(可由M_TRIM_THRESHOLD选项调节)时,执行内存紧缩操作(trim)。在上一个步骤free的时候,发现最高地址空闲内存超过128K,于是内存紧缩,变成图9所示。
说完内存分配的原理,那么被测模块在内核态cpu消耗高的原因就很清楚了:每次请求来都malloc一块2M的内存,默认情况下,malloc调用 mmap分配内存,请求结束的时候,调用munmap释放内存。假设每个请求需要6个物理页,那么每个请求就会产生6个缺页中断,在2000的压力下,每 秒就产生了10000多次缺页中断,这些缺页中断不需要读取磁盘解决,所以叫做minflt;缺页中断在内核态执行,因此进程的内核态cpu消耗很大。缺 页中断分散在整个请求的处理过程中,所以表现为分配语句耗时(10us)相对于整条请求的处理时间(1000us)比重很小。
将动态内存改为静态分配,或者启动的时候,用malloc为每个线程分配,然后保存在threaddata里面。但是,由于这个模块的特殊性,静态分配,或者启动时候分配都不可行。另外,Linux下默认栈的大小限制是10M,如果在栈上分配几M的内存,有风险。
禁止malloc调用mmap分配内存,禁止内存紧缩。
在进程启动时候,加入以下两行代码:
mallopt(M_MMAP_MAX, 0); // 禁止malloc调用mmap分配内存
mallopt(M_TRIM_THRESHOLD, -1); // 禁止内存紧缩
效果:加入这两行代码以后,用ps命令观察,压力稳定以后,majlt和minflt都为0。进程的系统态cpu从20降到10。
三、如何查看堆内内存的碎片情况 ?
glibc 提供了以下结构和接口来查看堆内内存和 mmap 的使用情况。
struct mallinfo {
&&&&&&&&&&&& /* non-mmapped space allocated from system */
&&&&&&&&& /* number of free chunks */
&&&&&&&&&& /* number of fastbin blocks */
&&&&&&&&&&&&& /* number of mmapped regions */
&&&&&&&&&&& /* space in mmapped regions */
&&&&&&&& /* maximum total allocated space */
&&&&&&&&& /* space available in freed fastbin blocks */
&&&&&&&& /* total allocated space */
&&&&&&&&& /* total free space */
&&&&&&& /* top-most, releasable (via malloc_trim) space */
/*返回heap(main_arena)的内存使用情况,以 mallinfo 结构返回 */
struct mallinfo mallinfo();
/* 将heap和mmap的使用情况输出到stderr*/
void malloc_stats();
可通过以下例子来验证mallinfo和malloc_stats输出结果。
#include &stdlib.h&
#include &stdio.h&
#include &string.h&
#include &unistd.h&
#include &sys/mman.h&
#include &malloc.h&
size_t& heap_malloc_total, heap_free_total,mmap_total, mmap_
void print_info()
&&& struct mallinfo mi = mallinfo();
printf("count by itself:\n");
&&& printf("\theap_malloc_total=%lu heap_free_total=%lu heap_in_use=%lu\n\tmmap_total=%lu mmap_count=%lu\n",
&&&&&&&&&&&&& heap_malloc_total*1024, heap_free_total*1024, heap_malloc_total*1024-heap_free_total*1024,
&&&&&&&&&&&&& mmap_total*1024, mmap_count);
printf("count by mallinfo:\n");
printf("\theap_malloc_total=%lu heap_free_total=%lu heap_in_use=%lu\n\tmmap_total=%lu mmap_count=%lu\n",
&&&&&&&&&&&& mi.arena, mi.fordblks, mi.uordblks,
&&&&&&&&&&&& mi.hblkhd, mi.hblks);
printf("from malloc_stats:\n");
malloc_stats();
#define ARRAY_SIZE 200
int main(int argc, char** argv)
&&& char** ptr_arr[ARRAY_SIZE];
&&&& &&& for( i = 0; i & ARRAY_SIZE; i++)
&&&&&&&&&&& ptr_arr[i] = malloc(i * 1024);& &&&&&&&&&&& if ( i & 128)&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //glibc默认128k以上使用mmap
&&&&&&&&&&& {
&&&&&&&&&&&&&&&&&&& heap_malloc_total +=
&&&&&&&&&&& }
&&&&&&&&&&& else
&&&&&&&&&&& {
&&&&&&&&&&&&&&&&&&& mmap_total +=
&&&&&&&&&&&&&&&&&& mmap_count++;
&&&&&&&&&&& }
&&& }& &&& print_info();
&&& for( i = 0; i & ARRAY_SIZE; i++)
&&&&&&&&&& if ( i % 2 == 0)
&&&&&&&&&&&&&&&
&&&&&&&&&& free(ptr_arr[i]);
&&&&&&&&&& if ( i & 128)
&&&&&&&&&& {
&&&&&&&&&&&&&&&&&& heap_free_total +=
&&&&&&&&&& }
&&&&&&&&&& else
&&&&&&&&&& {
&&&&&&&&&&&&&&&&& mmap_total -=
&&&&&&&&&&&&&&&&& mmap_count--;
&&&&&&&&&& }
&&& }& &&& printf("\nafter free\n");
&&& print_info();
&&& return 1;
该例子第一个循环为指针数组每个成员分配索引位置 (KB) 大小的内存块,并通过 128 为分界分别对 heap 和 mmap 内存分配情况进行计数;
第二个循环是 free 索引下标为奇数的项,同时更新计数情况。通过程序的计数与mallinfo/malloc_stats 接口得到结果进行对比,并通过 print_info打印到终端。
下面是一个执行结果:
count by itself:
&&&&&&& heap_malloc_total=8323072 heap_free_total=0 heap_in_use=8323072
&&&&&&& mmap_total= mmap_count=72
count by mallinfo:
&&&&&&& heap_malloc_total=8327168 heap_free_total=2032 heap_in_use=8325136
&&&&&&& mmap_total= mmap_count=72
from malloc_stats:
system bytes&&&& =&&& 8327168
in use bytes&&&& =&&& 8325136
Total (incl. mmap):
system bytes&&&& =&&
in use bytes&&&& =&&
max mmap regions =&&&&&&&& 72
max mmap bytes&& =&&
after free
count by itself:
&&&&&&& heap_malloc_total=8323072 heap_free_total=4194304 heap_in_use=4128768
&&&&&&& mmap_total=6008832 mmap_count=36
count by mallinfo:
&&&&&&& heap_malloc_total=8327168 heap_free_total=4197360 heap_in_use=4129808
&&&&&&& mmap_total=6119424 mmap_count=36
from malloc_stats:
system bytes&&&& =&&& 8327168
in use bytes&&&& =&&& 4129808
Total (incl. mmap):
system bytes&&&& =&&
in use bytes&&&& =&&
max mmap regions =&&&&&&&& 72
max mmap bytes&& =&&
由上可知,程序统计和mallinfo 得到的信息基本吻合,其中 heap_free_total 表示堆内已释放的内存碎片总和。
如果想知道堆内究竟有多少碎片,可通过 mallinfo 结构中的 fsmblks 、smblks 、ordblks 值得到,这些值表示不同大小区间的碎片总个数,这些区间分别是 0~80 字节,80~512 字节,512~128k。如果 fsmblks 、 smblks 的值过大,那碎片问题可能比较严重了。
不过, mallinfo 结构有一个很致命的问题,就是其成员定义全部都是 int ,在 64 位环境中,其结构中的 uordblks/fordblks/arena/usmblks 很容易就会导致溢出,应该是历史遗留问题,使用时要注意!
四、既然堆内内存brk和sbrk不能直接释放,为什么不全部使用 mmap 来分配,munmap直接释放呢?
既然堆内碎片不能直接释放,导致疑似&内存泄露&问题,为什么 malloc 不全部使用 mmap 来实现呢(mmap分配的内存可以会通过 munmap 进行 free ,实现真正释放)?而是仅仅对于大于 128k 的大块内存才使用 mmap ?
其实,进程向 OS 申请和释放地址空间的接口 sbrk/mmap/munmap 都是系统调用,频繁调用系统调用都比较消耗系统资源的。并且, mmap 申请的内存被 munmap 后,重新申请会产生更多的缺页中断。例如使用 mmap 分配 1M 空间,第一次调用产生了大量缺页中断 (1M/4K 次 ) ,当munmap 后再次分配 1M 空间,会再次产生大量缺页中断。缺页中断是内核行为,会导致内核态CPU消耗较大。另外,如果使用 mmap 分配小内存,会导致地址空间的分片更多,内核的管理负担更大。
同时堆是一个连续空间,并且堆内碎片由于没有归还 OS ,如果可重用碎片,再次访问该内存很可能不需产生任何系统调用和缺页中断,这将大大降低 CPU 的消耗。 因此, glibc 的 malloc 实现中,充分考虑了 sbrk 和 mmap 行为上的差异及优缺点,默认分配大块内存 (128k) 才使用 mmap 获得地址空间,也可通过 mallopt(M_MMAP_THRESHOLD, &SIZE&) 来修改这个临界值。
五、如何查看进程的缺页中断信息?
可通过以下命令查看缺页中断信息
ps -o majflt,minflt -C &program_name&
ps -o majflt,minflt -p &pid&
其中:: majflt 代表 major fault ,指大错误;
&&&&&&&&&& minflt 代表 minor fault ,指小错误。
这两个数值表示一个进程自启动以来所发生的缺页中断的次数。
其中 majflt 与 minflt 的不同是::
&&&&&&& majflt 表示需要读写磁盘,可能是内存对应页面在磁盘中需要load 到物理内存中,也可能是此时物理内存不足,需要淘汰部分物理页面至磁盘中。
六、除了 glibc 的 malloc/free ,还有其他第三方实现吗?
其实,很多人开始诟病 glibc 内存管理的实现,特别是高并发性能低下和内存碎片化问题都比较严重,因此,陆续出现一些第三方工具来替换 glibc 的实现,最著名的当属 google 的tcmalloc和facebook 的jemalloc 。
网上有很多资源,可以自己查(只用使用第三方库,代码不用修改,就可以使用第三方库中的malloc)。
参考资料:
《深入理解计算机系统》第 10 章
http://www.man7.org/linux/man-pages/man3/mallinfo.3.html
原文地址:
阅读(...) 评论()C语言内存池使用模型_Linux编程_Linux公社-Linux系统门户网站
你好,游客
C语言内存池使用模型
来源:Linux社区&
作者:zmxiangde
在用C语言开发时,特别是在服务器端,内存的使用会成为系统性能的一个瓶颈,如频繁的分配和释放内存,会不断的增加系统的内存碎片,影响内核之后分配内存的效率,这个时候一个比较可行的做法是采用内存池,先分配好比较多的内存,然后在这个已经分配的内存里使用内存,这样就不需要内核过多的参与内存分配和释放的过程。
内存池根据应用不同有多种实现的策略,如有些分配很大的内存,然后将内存分配成大小相等的块,并将每个块链接起来进行管理。
下面对模型介绍的时候,为了简单,不加入用于调试的编写技巧和为之准备的结构,其实主要是省去间接调用,有时为了调试,会将文件及所在行以及主要的变量状态输出。
一,内存池访问接口
创建大小为size的新的内存池。
pool_t&_pool_new_heap(int&size);&&从指定内存池中分配大小为size的内存空间, 这些空间会在内存池释放时,被自动的释放。
void&*pool_malloc(pool_t,&int&size);&&&内存池的大小,返回内存池中所有内存块的大小总和
int&pool_size(pool_t&p);&&释放内存池,这会导致所有内存被释放,同时内存池本身也被释放
void&pool_free(pool_t&p);&&还有其它的一些接口,但这些是主要的接口。
二,数据结构
struct&pheap&&
&&&&void&*&&
&&&&int&size,&&&
};&&该结构表示内存池中一个内存块的抽象表示,
block 用于指向由malloc所分配的内存地址。
size & 表示block所指向地址的内存大小。
used 表示多少处于已经使用的状态。在分配内存时,这个域很重要,它表示内存块可以被分配的偏移值,也就是从used开始的内存都是可以被从内存池中分配出去的。
struct&pfree&&
&&&&pool_cleanup_t&f;&&
&&&&void&*&&
&&&&struct&pheap&*&&
&&&&struct&pfree&*&&
typedef&void&(*pool_cleanup_t)(void&*arg);&&这个结构用于实现一个链表,将所有的内存块链接起来。每一个内存块,对映一个这个结构,也就是每个struct pheap结构,都有一个struct pfree结构将其封装起来,这个结构主要实现下面几个功能:
实现内存块的链表,用struct pfree *next连接起来,这是一个单链表。
内存块释放的回调。注册在释放内存时,如果释放这个内存块,主要是通过pool_cleanup_t f和void *arg两个域来完成这个功能。
pheap域用于指向需要被放入链表的内存块,就是前面的结构。
typedef&struct&pool_struct&&
&&&&int&&&
&&&&struct&pfree&*&&
&&&&struct&pfree&*cleanup_&&
&&&&struct&pheap&*&&
}&_pool,&*pool_t;&&
结构中的域代表如下:
heap:指向内存池中最新申请的内存块,在每次申请内存块时,都会将其指向新的内存块。
cleanup和cleanup_tail:指向链表的头和尾的指针。
size:表示内存池中内存的大小,包括所有的内存块。
这个结构的主要功能如下:
管理内存块。通过cleanup和clean_tail两个指针,因为内存块在内存池中是以单链表的形式组织的,这两个指针分别指向链表的头和尾指针。
内存池中可用的内存大小。通过size域来统计完成。
获取最新的内存块指针。通过heap指针在每次分配内存块时重新赋值来实现。
这个内存池的实现,主要是依靠上面三个数据结构来完成,其实估计已经知道的差不多了。下面再讨论一些基本的
相关资讯 & & &
& (02月25日)
& (11/11/:24)
& (03月06日)
& (12/29/:28)
& (10/19/:30)
图片资讯 & & &
   同意评论声明
   发表
尊重网上道德,遵守中华人民共和国的各项有关法律法规
承担一切因您的行为而直接或间接导致的民事或刑事法律责任
本站管理人员有权保留或删除其管辖留言中的任意内容
本站有权在网站内转载或引用您的评论
参与本评论即表明您已经阅读并接受上述条款一、概述:& &&&动态内存分配,特别是开发者经常接触的Malloc/Free接口的实现,对许多开发者来说,是一个永远的话题,而且有时候也是一个比较迷惑的问题,本文根据自己的理解,尝试简单的探究一下在嵌入式系统中,两类典型系统中动态内存分配以及Malloc/Free的实现机制。
二、内存分配方式& && &Malloc/Free主要实现的是动态内存分配,要理解它们的工作机制,就必须先了解操作系统内存分配的基本原理。  在操作系统中,内存分配主要以下面三种方式存在: &&(1)静态存储区域分配。内存在程序编译的时候或者在操作系统初始化的时候就已经分配好,这块内存在程序的整个运行期间都存在,而且其大小不会改变,也不会被重新分配。例如全局变量,static变量等。  (2)栈上的内存分配。栈是系统数据结构,对于进程/线程是唯一的,它的分配与释放由操作系统来维护,不需要开发者来管理。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时,这些存储单元会被自动释放。栈内存分配运算内置于处理器的指令集中,效率很高,不同的操作系统对栈都有一定的限制。  (3) 堆上的内存分配,亦称动态内存分配。程序在运行的期间用malloc申请的内存,这部分内存由程序员自己负责管理,其生存期由开发者决定:在何时分配,分配多少,并在何时用free来释放该内存。这是唯一可以由开发者参与管理的内存。使用的好坏直接决定系统的性能和稳定。三、动态内存分配概述& & 首先,对于支持虚拟内存的操作系统,动态内存分配(包括内核加载,用户进程加载,动态库加载等等)都是建立在操作系统的虚拟内存分配之上的,虚拟内存分配主要包括:& & 1、进程使用的内存地址是虚拟的(每个进程感觉自己拥有所有的内存资源),需要经过页表的映射才能最终指向系统实际的物理地址。& & 2、主内存和磁盘采用页交换的方式加载进程和相关数据,而且数据何时加载到主内存,何时缓存到磁盘是OS调度的,对应用程序是透明的。& & 3、虚拟存储器给用户程序提供了一个基于页面的内存大小,在32位系统中,用户可以页面大小为单位,分配到最大可以到4G(内核要使用1G或2G等内存地址)字节的虚拟内存。& & 4、对于虚拟内存的分配,操作系统一般先分配出应用要求大小的虚拟内存,只有当应用实际使用时,才会调用相应的操作系统接口,为此应用程序分配大小以页面为单位的实际物理内存。& & 5、不是所有计算机系统都有虚拟内存机制,一般在有MMU硬件支持的系统中才有虚拟内存的实现。许多嵌入式操作系统中是没有虚拟内存机制的,程序的动态分配实际是直接针对物理内存进行操作的。许多典型的实时嵌入式系统如Vxworks、Uc/OS 等就是这样。四、动态内存分配的实现& && & 由于频繁的进行动态内存分配会造成内存碎片的产生,影响系统性能,所以在不同的系统中,对于动态内存管理,开发了许多不同的算法(具体的算法实现不想在这里做详细的介绍,有兴趣的读者可以参考Glib C 的源代码和附录中的资料)。不同的操作系统有不同的实现方式,为了程序的可移植性,一般在开发语言的库中都提供了统一接口。对于C语言,在标准C库和Glib 中,都实现了以malloc/free为接口的动态内存分配功能。也就是说,malloc/free库函索包装了不同操作系统对动态内存管理的不同实现,为开发者提供了一个统一的开发环境。对于我们前面提到的一些嵌入式操作系统,因为实时系统的特殊要求(实时性要求和开发者订制嵌入式系统),可能没有提供相应的接口。一般C 库中的malloc/free函数会实现应用层面的内存管理算法,在系统真正需要内存时,才通过操作系统的API(系统调用)来获取实际的物理内存,当然,你也可以使用第三方的内存管理器,或者通过自己改写malloc/free函数来实现应用层面的内存管理。
4.1、动态内存管理的一般机制:& &动态内存管理机制会随操作系统和系统架构的不同而不同,一些操作系统利用malloc等分配器,在支持虚拟内存的操作系统中就利用这种方式来实现进程动态内存管理的。而另外一些系统利用预先分配的内存区间来进行动态内存管理,该方式主要应用于不支持虚拟内存机制的嵌入式操作系统中。对于进程动态内存管理机制的具体实现,许多系统提供了统一的接口,并由malloc/free函数来具体实现。下面以嵌入式Linux和Uc/OS 为例,简单解析一下动态内存管理的具体实现。
4.2、Linux下动态内存分配的实现:& &&&在Linux下,glibc 的malloc提供了下面两种动态内存管理的方法:堆内存分配和mmap的内存分配,此两种分配方法都是通过相应的Linux 系统调用来进行动态内存管理的。具体使用哪一种方式分配,根据glibc的实现,主要取决于所需分配内存的大小。一般情况中,应用层面的内存从进程堆中分配,当进程堆大小不够时,可以通过系统调用brk来改变堆的大小,但是在以下情况,一般由mmap系统调用来实现应用层面的内存分配:A、应用需要分配大于1M的内存,B、在没有连续的内存空间能满足应用所需大小的内存时。&&(1)、调用brk实现进程里堆内存分配& & 在glibc中,当进程所需要的内存较小时,该内存会从进程的堆中分配,但是堆分配出来的内存空间,系统一般不会回收,只有当进程的堆大小到达最大限额时或者没有足够连续大小的空间来为进程继续分配所需内存时,才会回收不用的堆内存。在这种方式下,glibc会为进程堆维护一些固定大小的内存池以减少内存脆片。&&(2)、使用mmap的内存分配& & 在glibc中,一般在比较大的内存分配时使用mmap系统调用,它以页为单位来分配内存的(在Linux中,一般一页大小定义为4K),这不可避免会带来内存浪费,但是当进程调用free释放所分配的内存时,glibc会立即调用unmmap,把所分配的内存空间释放回系统。注意:这里我们讨论的都是虚拟内存的分配(即应用层面上的内存分配),主要由glibc来实现,它与内核中实际物理内存的分配是不同的层面,进程所分配到的虚拟内存可能没有对应的物理内存。如果所分配的虚拟内存没有对应的物理内存时,操作系统会利用缺页机制来为进程分配实际的物理内存。
4.3、Uc/OS 下内存分配的实现:& & 在一般的实时嵌入式系统中,由于实时性的要求,很少使用虚拟内存机制。所有的内存都需要开发人员参与分配,他们直接操作物理内存,所分配的内存不能超过系统的物理内存,于系统的堆栈的管理,都由开发者显式进行。& & 在Uc/OS 中,主要利用内存分区来管理系统内存,系统中一般有多个分区,每个分区相当于一些固定大小内存块的内存池,应用可以从这些内存池中分配内存,当内存使用完成后,也需要把该内存释放回对应的内存池中。Uc/OS 的内存管理可以分为以下过程:(1)、创建内存分区:在使用内存之前,开发者必须首先调用OSMemCreare()函数来创建相应的内存分区,在创建内存分区成功后,就会在系统中存在一个以开发者指定内存大小,指定内存块数目的内存池。在此过程中,开发者需要明确的知道系统的内存分布,并指明内存池的基址。(2)、申请内存:当系统内存分区创建好了后,系统就可以从相应的内存分区中获取内存了。在Uc/OS 中,主要利用OSMemGet()来申请内存,应用程序会根据所需要内存的大小,从开发者指定的内存池中申请内存。(3)、释放内存:因为内存是系统的紧缺资源,当应用不再需要使用所申请的内存时,应该及时释放该内存。在Uc/OS 中,主要利用OSMemPut()来释放不再需要的内存,在此过程中,开发者应该保证把该内存释放回原内存的分区。& & 在一些实时嵌入式系统中,系统也会提供一些比较复杂的内存管理机制,并为应用提供类malloc/free接口供开发者使用,如Vxworks等。不管怎么样,在这些系统中,都得由开发者显式的参与内存的管理,而且应用程序只能使用实际物理内存大小的内存。
五、小结:& &&&动态内存管理是开发者唯一能够参与管理的内存分配机制,这给开发者灵活使用系统主存提供了一个手段,但是由于内存的分配和释放都得依靠开发者显式进行,很容易出现内存泄露的问题。另外,不好的动态内存管理算法对系统的性能影响有着也不可忽视影响。在本文简单解析了两种动态内存管理实现,它们互有忧缺点:对于以虚拟内存机制和分页机制为基础的动态内存管理(如Linux等),由于请求分页机制的存在,不能满足系统实时性方面的要求,但是它能为应用提供最多能到4G的内存空间,而且由于应用虚拟内存机制,可以为进程空间提供保护,一个进程的崩溃不会影响其他的进程。对于基于非虚拟内存管理机制的系统,由于可以直接操作物理内存,提高了系统实时性,在这样的系统中,开发者的参与度比基于虚拟内存机制的要高。其缺点是没有进程间的保护机制,一个进程或任务的错误很容易导致整个系统的崩溃。
阅读(...) 评论()

我要回帖

更多关于 c 调用c 编写的dll 的文章

 

随机推荐