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-設計

相關文章
相關標籤/搜索