本文主要是介绍内存池中如何保存内存块大小信息,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
网络设备因为需要快速的处理数据包,所以与Linux内核不同,网络设备中数据包和session的内存一般都是预先分配的。由于业务的不同,需要内存的大小可能只是固定的几个值。因此也就有几个固定内存大小的内存池。
使用内存池的好处:
1. 可以避免频繁分配和释放内存,提高效率;
2. 可以避免内存碎片;
下面讨论一下内存池的实现:为了 保证接口与malloc/free的一致性,对上层应用透明:
那么以malloc调用为例,ptr = malloc(size),调用者传入一个size。内存模块需要根据size的大小选择合适的内存池来分配内存。释放内存的时候,调用为free(ptr)。这时需要将内存重新放入对应的内存池。但是释放的时候,调用者并不将size大小传给内存模块,这就需要内存模块自己保存该内存的大小。
1)最直接的方法:在分配的内存块加上head或者tail块。
内存在内存池的组织如下:
首部用于保存内存块的大小以及保护信息,控制信息等。尾部可有可无,一般用于作为两块内存之间的保护区域。将其填充为一些特殊的值,用于检测内存错误,如越界溢出等。返回给应用的地址为黄色区域的起始地址。在释放的时候,由于首部的大小是固定的,可以从参数指针得到首部地址,从而得到内存模块所需要的一切信息。
这样的实现比较简单,但是却有缺点:应用申请内存时,传入的内存大小可能是应用精心设计好的。比如应用为了提高cache的命中率,申请内存时特意将size与cache line对齐。但是这种内存池的实现,却破坏了应用的初衷。
如何解决这个问题呢?可以把首部对齐至cache line。但是这样无疑是对内存的极大的浪费!比如cache line为64 bytes,首部就要占到64 bytes。这样的解决方法无疑是不可以接受的。
2)利用内存地址判断
这是一个简单的示意图。所有同大小的内存都是位于连续的内存中,构成一个大块内存。然后后面跟随另外大小的内存块,依次类推。当然不同大小的内存块可以不连续。
这样的实现,在释放内存的时候,可以通过传入的内存地址确定其所属的内存池。因为该大小的内存池的起始和终止地址是确定的。
再说一下这种实现的缺点:通用的内存池实现应该支持内存块的分裂和合并。当对应大小的内存使用完后,将更大的内存块分裂,一个用于返回,另一个插入到合适的内存池中。释放内存时,需要在适当的时机,将连续的小内存块合并为大块内存,并移到大内存池中。
上面的实现无疑不支持这种机制。即使强制支持,代价也很大。
3)内存块中保存size信息
即直接占用应用申请的内存块的部分地址,用于保存size信息。为了节约内存,不需要保存内存的大小,可以使用两三个bit来表示size类型。在释放内存的时候,根据size类型,找到正确的内存池。
这样的设计,实现也比较简单,且比较容易支持内存块的分裂和合并。但是由于size类型直接占用了应用内存的空间。对于调用者来说,必须要明确这一size类型的存在。这对调用者增加了额外的要求。一旦调用者没有注意到这个size类型。在拿到内存指针后,对整个内存进行了初始化。那么在释放内存的时候,要么导致内存泄露,要么会造成内存的越界。前者是将内存错误的放入了小的内存池中,后者是将内存错误的放入了大的内存中。
4) 由调用者保存内存大小
这种实现也很简单。内存模块并不维护内存块的大小。那么调用者需要在释放内存的时候,告诉内存模块,该块内存的大小。如申请时ptr = malloc(size);释放时free(ptr, size);缺点就是与C库的内存接口不匹配。
转自:http://blog.chinaunix.net/uid-23629988-id-3264806.html
这篇关于内存池中如何保存内存块大小信息的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!