C接口与实现---内存管理(内存池的实现)

前面已经讲了一些C中基本的内存管理的方式,Fist-fit, Best-fit都是基于对象的大小来分配或者找到合适的大小的。当涉及到大量的malloc,free的时候频繁的系统调用确定会影响到系统的性能,这里有一种更有效的内存管理方式就是基于块的内存分配方式也就是咱们常常说的内存池(在软件的世界中你能够发现不少相似的池化设计),下面就来实现一个简单的内存池。git

咱们使用以下的数据结构来管理内存池:github

struct T {
  T prev;
  char *avail;
  char *limit;
};

prev指向存储块的头, avail指向第一个可用地址,limit指向存储块的末尾地址,其中从avail到limit的空间都是咱们可使用空间。下图就是一个有三个内存块的内存池的状态,其中阴影部分表示这部份内存已经被使用了。
图片描述缓存

下面咱们就来看看具体的代码怎么去实现。咱们能够从头开始看,首先有的确定是这个数据结构的头了,其实这就是咱们在数据结构书中常常看到的有头链表的实现方式。不一样点在于咱们在练习的时候每一个里面就是简单的存储了一些数据而这里每一个块都有一个很大的内存, 当有一个alloc请求的时候就从list里找到一个合适的块,而后在这上面进行分配。数据结构

T Arena_new() {
  T arena = malloc(sizeof(*arena));

  if (arena == NULL) {
    fprintf(stderr, "ALLOC MEM FAILED\n");
    return NULL;
  }
  arena->prev = NULL;
  arena->limit = arena->avail = NULL;
  return arena;
}

等有了header以后咱们接下来要作的就是分配内存块并对他进行管理了,下面仍是直接上代码:函数

void *Arena_alloc(T arena, long nbytes,const char *file,
                  int line){
  assert(arena);
  assert(nbytes > 0);

  // for memory align                                                                                                                                                                                       
  size_t ldsize = sizeof(long double);
  nbytes = (((nbytes + ldsize - 1) / ldsize) * ldsize);

  while(nbytes > arena->limit - arena->avail) {

    T ptr;
    char *limit;
    if ((ptr = freechunks) != NULL) {
      freechunks = freechunks->prev;
      nfree--;
      limit = ptr->limit;
    } else {
      long m = sizeof(struct T) + nbytes + 10 * 1024;
      ptr = (T)malloc(m);
      if (ptr == NULL) {
        fprintf(stderr, "ALLOC MEM FAILED %s:%d\n", file, line);
      }
      limit = (char *)ptr + m;
    }
    *ptr = *arena;
    arena->avail= (char *)((struct T *)ptr + 1);
    arena->limit = limit;
    arena->prev = ptr;
  }
  arena->avail += nbytes;
  return arena->avail - nbytes;
}

基于咱们这里每一个块是10k+的大小,因此大多的内存分配都是直接的从header->prev上面分配一块合适的内存,while体应该是不多有机会能执行到。若是header->prev上的空间没法知足咱们的请求的时候咱们才去寻找一个更合适的空间(代码中while部分),这里咱们也有一个相似缓存的设计,当free一块内存块的时候咱们并非立马的归还给操做系统,而是把他放到本地的freechuncks中保存起来,当有新的请求的时候咱们首先从freechunks里的第一个块加入到第一个内存块上,而后再去check这块内存是否知足request,若是合适就直接在该块上分配,不然循环直到freechunks里的块所有都加入进来,若是将所有的freechunks都加入到了linklist中仍然没法知足请求则经过malloc分配一个内存给他。下面是一个简单的图例,其中左边为从freechunks取出一块内存以前,虚线表示讲内存块加入到arena指向的linkList中。
图片描述性能

下面咱们来看看对应的free函数。有了以前的freechunks的概念以后理解free函数起来就相对简单不少了,这里只是简单的遍历整个arena,若是freechunks的大小小于threshold那么就把整个块放入freechunks里,负责就直接调用系统free释放之。spa

void Arena_free(T arena) {
  assert(arena);
  while(arena->prev) {
    struct T tmp = *arena->prev;
    if (nfree < THRESHOLD) {
      arena->prev->prev = freechunks;
      freechunks = arena->prev;
      nfree++;
      freechunks->limit = arena->limit;
    } else
      free(arena->prev);
    *arena = tmp;
  }

固然啦这里有不少问题,就如练习6.1 咱们只查找了arena描述的内存块,若是没有足够的空间就分配一块新的内存块,即便后面的内存块有足够的空间。个人理解是这里咱们使用了freechuncks来缓存了一些内存块,这些内存块的大小都是10k+,我以为10k+是足够新的请求的,若是一次请求的数据块大于10k的话我觉的咱们的设计是须要从新思考的。操作系统

这里就是一个简答的内存池的实现。所有代码能够参考github.
-END-设计

相关文章
相关标签/搜索