内存管理
我们经常在堆上进行内存的分配和释放时,就会产生内存碎片问题。
- 内部碎片的产生:因为所有的内存分配必须起始于可被 4、8 或 16 整除(视 处理器体系结构而定)的地址或者因为MMU的分页机制的限制,决定内存分配算法仅能把预定大小的内存块分配给客户。假设当某个客户请求一个 43 字节的内存块时,因为没有适合大小的内存,所以它可能会获得 44字节、48字节等稍大一点的字节,因此由所需大小四舍五入而产生的多余空间就叫内部碎片。
- 外部碎片的产生: 频繁的分配与回收物理页面会导致大量的、连续且小的页面块夹杂在已分配的页面中间,就会产生外部碎片。假 设有一块一共有100个单位的连续空闲内存空间,范围是0-99。如果你从中申请一块内存,如10个单位,那么申请出来的内存块就为0-9区间。这时候你 继续申请一块内存,比如说5个单位大,第二块得到的内存块就应该为10-14区间。如果你把第一块内存块释放,然后再申请一块大于10个单位的内存块,比 如说20个单位。因为刚被释放的内存块不能满足新的请求,所以只能从15开始分配出20个单位的内存块。现在整个内存空间的状态是0-9空闲,10-14 被占用,15-24被占用,25-99空闲。其中0-9就是一个内存碎片了。如果10-14一直被占用,而以后申请的空间都大于10个单位,那么0~9就 永远用不上了,变成外部碎片。
从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)。
1、brk是将数据段(.data)的最高地址指针_edata往高地址推;malloc分配的内存小于128kb,使用brk分配,将_edata往高地址推(只分配虚拟空间,不对应物理内存(因此没有初始化),第一次读/写数据时,引起内核缺页中断,内核才分配对应的物理内存,然后虚拟地址空间建立映射关系)。
2、mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。malloc大于128k的内存,使用mmap分配内存,在堆和栈之间找一块空闲内存分配(对应独立内存,而且初始化为0)
这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。
伙伴算法
**基本原理:**必须满足三个条件的称为伙伴。
1)两个块大小相同;
2)两个块地址连续;
3)两个块必须是同一个大块中分离出来的;
分配原理:
伙伴系统将所有的空闲页框分组为11个块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页框的页框块。假如现在需要大小为6的内存块,该算法就到定位到大小为8的链表中查找,如果链表中有空闲块,就直接从中摘下并分配出去。如果没有,算法将顺着数组向上查找大小为16的数组,如果16中有空闲块,那么将该空闲块分成相等的两部分,一部分分配出去,另一部分放入8中,如此重复寻找和分配,若到达数组最后也没有空闲块,则放弃分配。
释放原理:
内存的释放就是分配的逆过程。当当释放一个块时,先在其对应的链表中考查是否有伙伴存在,如果没有伙伴块,就直接把要释放的块挂入链表头;如果有,则从链表中摘下伙伴,合并成一个大块,然后继续考察合并后的块在更大一级链表中是否有伙伴存在,直到不能合并或者已经合并到了最大的块。
缺点:
虽然伙伴系统能够减少内存碎片的产生,但是在linux内核中伙伴系统用来管理物理内存,其分配的单位是页(4k),如果针对一些经常分配并释放的对象如进程描述符等,这些对象的大小一般比较小,如果直接采用伙伴系统来进行分配和释放,不仅会造成大量的内碎片,而且处理速度也太慢。
slab算法
一般来说,伙伴算法的改进算法用于操作系统分配和回收内存,而且内存块的单位较大,利于Linux使用的伙伴算法以页为单位.对于小块内存的分配和回收,伙伴算法就显得有些得不偿失了.
对于小块内存,一般采用slab算法,或者叫做slab机制.
slab分配器是基于对象进行管理的,相同类型的对象归为一类(如进程描述符就是一类),每当要申请这样一个对象,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统,从而避免这些内碎片。slab分配器并不丢弃已分配的对象,而是释放并把它们保存在内存中。当以后又要请求新的对象时,就可以从内存直接获取而不用重复初始化。
2
0
具
有
若
干
个
1
b
y
t
e
s
的
块
,
2
1
具
有
若
干
个
2
b
y
t
e
s
的
块
,
2
2
具
有
若
干
个
4
b
y
t
e
s
的
块
,
.
.
.
.
.
.
2
n
具
有
若
干
个
n
b
y
t
e
s
的
块
,
2^0 具有若干个 1 bytes的块, 2^1 具有若干个 2 bytes的块, 2^2 具有若干个 4 bytes的块, ...... 2^n 具有若干个 n bytes的块,
20具有若干个1bytes的块,21具有若干个2bytes的块,22具有若干个4bytes的块,......2n具有若干个nbytes的块,
Linux 的slab 可有三种状态:
满的:slab 中的所有对象被标记为使用。
空的:slab 中的所有对象被标记为空闲。
部分:slab 中的对象有的被标记为使用,有的被标记为空闲。
slab 分配器首先从部分空闲的slab 进行分配。如没有,则从空的slab 进行分配。如没有,则从物理连续页上分配新的slab,并把它赋给一个cache ,然后再从新slab 分配空间。
与传统的内存管理模式相比, slab 缓存分配器提供了很多优点。
1、内核通常依赖于对小对象的分配,它们会在系统生命周期内进行无数次分配。
2、slab 缓存分配器通过对类似大小的对象进行缓存而提供这种功能,从而避免了常见的碎片问题。
3、slab 分配器还支持通用对象的初始化,从而避免了为同一目的而对一个对象重复进行初始化。
4、slab 分配器还可以支持硬件缓存对齐和着色,这允许不同缓存中的对象占用相同的缓存行,从而提高缓存的利用率并获得更好的性能。
内存池的实现
我们的内存池的实现采用了slab算法的思想,我们以页(4096 byte)为单位。创建内存池的时候,预先分配一个一页的内存大小,对于小于4096字节的分配,我们直接以这个block进行分配,大于4096的内存块,我们重新分配一个大内存的内存块。为了避免开销,我们需要内存的结构体分配都使用block进行分配。而内存池的实现主要是分配、释放和扩容。
1. 分配
分配内存的结构体主要有两个,分配4k以下的内存:
struct mp_node_s {
unsigned char *last; // 已使用的内存的地址
unsigned char *end; // 一个大内存块的最后末尾地址
struct mp_node_s *next; //下一个大的内存块
size_t failed; //尝试分配的失败的次数
};
分配一个大的内存
struct mp_large_s {
struct mp_large_s *next; // 下一个内存块
void *alloc; //分配的内存块 指向的地址
};
内存池的结构体
struct mp_pool_s {
size_t max; //一个内存块的最大内存
struct mp_node_s *current; //当前内存块
struct mp_large_s *large; //指向的大的内存块
struct mp_node_s head[0]; //指向的第一块内存
};
创建内存池,使用posix_memalign函数进行分配,
在大多数情况下,编译器和C库透明地帮你处理对齐问题。POSIX 标明了通过malloc( ), calloc( ), 和 realloc( ) 返回的地址对于任何的C类型来说都是对齐的。在Linux中,这些函数返回的地址在32位系统是以8字节为边界对齐,在64位系统是以16字节为边界对齐的。有时候,对于更大的边界,例如页面,程序员需要动态的对齐。虽然动机是多种多样的,但最常见的是直接块I/O的缓存的对齐或者其它的软件对硬件的交互,因此,POSIX 1003.1d提供一个叫做**posix_memalign( )**的函数:
/* one or the other – either suffices */
#define _XOPEN_SOURCE 600
#define _GNU_SOURCE
#include <stdlib.h>
int posix_memalign (void **memptr, size_t alignment,size_t size);
调用posix_memalign( )成功时会返回size字节的动态内存,并且这块内存的地址是alignment的倍数。参数alignment必须是2的幂,还是void指针的大小的倍数。返回的内存块的地址放在了memptr里面,函数返回值是0.
调用失败时,没有内存会被分配,memptr的值没有被定义,返回如下错误码之一:
EINVAL
参数不是2的幂,或者不是void指针的倍数。
ENOMEM
没有足够的内存去满足函数的请求。
要注意的是,对于这个函数,errno不会被设置,只能通过返回值得到。
由posix_memalign( )获得的内存通过free( )释放。
把结构体mp_pool_s和mp_node_s分配的内存都考虑在内。
struct mp_pool_s *mp_create_pool(size_t size) {
struct mp_pool_s *p;
int ret = posix_memalign((void**)&p, MP_ALIGNMENT, size + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s));
if(ret) return NULL;
p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;
p->current = p->head;
p->large = NULL;
p->head->last = (unsigned char*)p + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
p->head->end = p->head->last + size;
p->head->failed = 0;
return p;
}
分配一个4k以内的大小的内存块:
static void *mp_alloc_block(struct mp_pool_s *pool, size_t size) {
unsigned char *m;
struct mp_node_s *h = pool->head;
size_t psize = (size_t)(h->end - (unsigned char*)h);
int ret = posix_memalign((void**)&m, MP_ALIGNMENT, psize);
if(ret) return NULL;
struct mp_node_s *p, *new_node, *current;
new_node = (struct mp_node_s*)m;
new_node->end = m + psize;
new_node->next = NULL;
new_node->failed = 0;
m +=sizeof(struct mp_node_s);
m = mp_align_ptr(m, MP_ALIGNMENT);
new_node->last = m + size;
current = pool->current;
for(p = current; p->next;p=p->next){
if(p->failed++ > 4)
current = p->next;
}
p->next = new_node;
pool->current = current?current : new_node;
return m;
}
分配一个超过4k的内存块:
static void *mp_alloc_large(struct mp_pool_s *pool, size_t size) {
void *p = malloc(size);
if(p==NULL) return NULL;
size_t n=0;
struct mp_large_s *large;
for(large = pool->large; large; large = large->next){
if(large->alloc == NULL) {
large->alloc = p;
return p;
}
if(n++ >3) break;
}
large = mp_alloc(pool, sizeof(struct mp_large_s));
if(large == NULL) {
free(p);
return NULL;
}
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
2. 释放
释放就比较简单了,我们只到最后的时候才会释放内存(程序结束)。
void mp_destory_pool(struct mp_pool_s *pool) {
struct mp_node_s *h, *n;
struct mp_large_s *l;
for (l = pool->large; l; l = l->next) {
if (l->alloc) {
free(l->alloc);
}
}
h = pool->head->next;
while (h) {
n = h->next;
free(h);
h = n;
}
free(pool);
}
3. 扩容
我们会判断分配的size的大小,看需要是扩容4k以上的内存块或超过4k的大内存块。
void *mp_alloc(struct mp_pool_s *pool, size_t size) {
unsigned char *m;
struct mp_node_s *p;
if(size <= pool->max) {
p = pool->current;
do{
m = mp_align_ptr(p->last, MP_ALIGNMENT);
if((size_t)(p->end - m) >= size) {
p->last = m+size;
return m;
}
p=p->next;
}while(p);
return mp_alloc_block(pool, size);
}
return mp_alloc_large(pool, size);
}
最后完整的代码:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define MP_ALIGNMENT 32
#define MP_PAGE_SIZE 4096
#define MP_MAX_ALLOC_FROM_POOL (MP_PAGE_SIZE - 1)
#define mp_align(n, alignment) (((n) + (alignment-1)) & ~ (alignment - 1))
#define mp_align_ptr(p, alignment) (void*)((((size_t)p) + (alignment - 1)) & ~(alignment -1))
struct mp_large_s {
struct mp_large_s *next;
void *alloc;
};
struct mp_node_s {
unsigned char *last;
unsigned char *end;
struct mp_node_s *next;
size_t failed;
};
struct mp_pool_s {
size_t max;
struct mp_node_s *current;
struct mp_large_s *large;
struct mp_node_s head[0];
};
struct mp_pool_s *mp_create_pool(size_t size);
void mp_destory_pool(struct mp_pool_s *pool);
void mp_reset_pool(struct mp_pool_s *pool);
static void *mp_alloc_block(struct mp_pool_s *pool, size_t size);
static void *mp_alloc_large(struct mp_pool_s *pool, size_t size);
void *mp_alloc(struct mp_pool_s *pool, size_t size);
void *mp_calloc(struct mp_pool_s *pool, size_t size);
struct mp_pool_s *mp_create_pool(size_t size) {
struct mp_pool_s *p;
int ret = posix_memalign((void**)&p, MP_ALIGNMENT, size + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s));
if(ret) return NULL;
p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;
p->current = p->head;
p->large = NULL;
p->head->last = (unsigned char*)p + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
p->head->end = p->head->last + size;
p->head->failed = 0;
return p;
}
void mp_destory_pool(struct mp_pool_s *pool) {
struct mp_node_s *h, *n;
struct mp_large_s *l;
for (l = pool->large; l; l = l->next) {
if (l->alloc) {
free(l->alloc);
}
}
h = pool->head->next;
while (h) {
n = h->next;
free(h);
h = n;
}
free(pool);
}
void mp_reset_pool(struct mp_pool_s *pool){
struct mp_node_s *h, *n;
struct mp_large_s *l;
for (l = pool->large; l; l=l->next) {
if(l->alloc) free(l->alloc);
}
pool->large = NULL;
for(h = pool->head; h; h=h->next) {
h->last = (unsigned char *)h + sizeof(struct mp_node_s);
}
}
static void *mp_alloc_block(struct mp_pool_s *pool, size_t size) {
unsigned char *m;
struct mp_node_s *h = pool->head;
size_t psize = (size_t)(h->end - (unsigned char*)h);
int ret = posix_memalign((void**)&m, MP_ALIGNMENT, psize);
if(ret) return NULL;
struct mp_node_s *p, *new_node, *current;
new_node = (struct mp_node_s*)m;
new_node->end = m + psize;
new_node->next = NULL;
new_node->failed = 0;
m +=sizeof(struct mp_node_s);
m = mp_align_ptr(m, MP_ALIGNMENT);
new_node->last = m + size;
current = pool->current;
for(p = current; p->next;p=p->next){
if(p->failed++ > 4)
current = p->next;
}
p->next = new_node;
pool->current = current?current : new_node;
return m;
}
static void *mp_alloc_large(struct mp_pool_s *pool, size_t size) {
void *p = malloc(size);
if(p==NULL) return NULL;
size_t n=0;
struct mp_large_s *large;
for(large = pool->large; large; large = large->next){
if(large->alloc == NULL) {
large->alloc = p;
return p;
}
if(n++ >3) break;
}
large = mp_alloc(pool, sizeof(struct mp_large_s));
if(large == NULL) {
free(p);
return NULL;
}
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
void *mp_alloc(struct mp_pool_s *pool, size_t size) {
unsigned char *m;
struct mp_node_s *p;
if(size <= pool->max) {
p = pool->current;
do{
m = mp_align_ptr(p->last, MP_ALIGNMENT);
if((size_t)(p->end - m) >= size) {
p->last = m+size;
return m;
}
p=p->next;
}while(p);
return mp_alloc_block(pool, size);
}
return mp_alloc_large(pool, size);
}
void *mp_calloc(struct mp_pool_s *pool, size_t size) {
void *p = mp_alloc(pool, size);
if (p) {
memset(p, 0, size);
}
return p;
}
void mp_free(struct mp_pool_s *pool, void *p) {
struct mp_large_s *l;
for (l = pool->large; l; l = l->next) {
if (p == l->alloc) {
free(l->alloc);
l->alloc = NULL;
return ;
}
}
}
int main(){
int size = 1<<12;
struct mp_pool_s *p = mp_create_pool(size);
int i = 0;
for(i =0; i < 10; ++i) {
void *mp = mp_alloc(p, 512);
}
printf("mp_align(123, 32): %d, mp_align(17, 32): %d\n", mp_align(24, 32), mp_align(17, 32));
int j = 0;
for (i = 0;i < 5;i ++) {
char *pp = mp_calloc(p, 32);
for (j = 0;j < 32;j ++) {
if (pp[j]) {
printf("calloc wrong\n");
}
printf("calloc success\n");
}
}
//printf("mp_reset_pool\n");
for (i = 0;i < 5;i ++) {
void *l = mp_alloc(p, 8192);
mp_free(p, l);
}
mp_reset_pool(p);
//printf("mp_destory_pool\n");
for (i = 0;i < 58;i ++) {
mp_alloc(p, 256);
}
mp_destory_pool(p);
return 0;
}