1. 程式人生 > >SGISTL原始碼閱讀二 空間配置器中(第二級配置器__default_alloc_template)

SGISTL原始碼閱讀二 空間配置器中(第二級配置器__default_alloc_template)

SGISTL原始碼閱讀二 空間配置器中(第二級配置器__default_alloc_template)

引入

SGI空間配置器的做法是,如果區塊夠大,超過了128bytes,就移交給第一級配置器處理。當區塊小於128bytes時就是第二級配置器要做的事情了。
SGI的第二級配置器維護了16個__free-lists__,各自管理的大小分別是8,16,24,32,···,128bytws的小額區塊,以記憶體池管理(後續文章中將詳細分析記憶體池)。
我們先從圖示瞭解第二級配置器的__free-lists__。

union obj {
        union obj * free_list_link;
        char client_data[1];    /* The client sees this. */
  };

在這裡插入圖片描述

深入原始碼

template <bool threads, int inst>
class __default_alloc_template {

private:
  // Really we should use static const int x = N
  // instead of enum { x = N }, but few compilers accept the former.
# ifndef __SUNPRO_CC
    enum {__ALIGN = 8};                          //每過8bytes上調一個free-list
    enum {__MAX_BYTES = 128};                    //上限值128bytes
    enum {__NFREELISTS = __MAX_BYTES/__ALIGN};   //free-list的個數(128/8=16)
# endif
	//將申請空間的大小上調至8的倍數
	static size_t ROUND_UP(size_t bytes) {
    	//先將申請大小+8,再通過與運算將後三位(二進位制111為十進位制8)抹去,則為8的倍數
      	return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
	}
__PRIVATE:
	//free-lists節點結構
    //因為使用的是聯合體,節省了記憶體的開銷
	union obj {
        union obj * free_list_link;
        //client_data的地址就是obj的地址,不同的是指向的物件的型別不一樣,一個是obj,一個是char。
        //而client_data是給使用者使用的,這樣就可以避免強制轉換了。
        char client_data[1];    /* The client sees this.        */
	};
private:
	//16個free-lists
    //__VOLATILE是指每次取值都從記憶體中取
    static obj * __VOLATILE free_list[__NFREELISTS];
    
	//該函式根據區塊大小,返回應該使用第幾號free-list(從0開始計數)
    static  size_t FREELIST_INDEX(size_t bytes) {
        return (((bytes) + __ALIGN-1)/__ALIGN - 1);
    }

  // Returns an object of size n, and optionally adds to size n free list.
  //但會一個大小為n的物件,並可能加入大小為n的其他區塊到free-list
  static void *refill(size_t n);
  
  // Allocates a chunk for nobjs of size "size".  nobjs may be reduced
  // if it is inconvenient to allocate the requested number.
  //配置一大塊空間,可容納nobjs各大小為size的區塊
  //如果無法配置nobjs個區塊,那麼nobjs可能會降低
  static char *chunk_alloc(size_t size, int &nobjs);

  // Chunk allocation state.
  static char *start_free;//記憶體池的起始位置,只在chunk_alloc()中變化
  static char *end_free;//記憶體池的結束為止,只在chunk_alloc()中變化
  static size_t heap_size;

(對加了下劃線的命名方式一直都有一種莫名的畏懼,這種命名代表的是會被內部呼叫,而不是給使用者直接使用)
通過以上原始碼的閱讀,我們對free-list有了一定的瞭解,它就是由obj聯合體構成的16個長度的陣列,每一個obj聯合體又連結起來(就是連結串列,只是這裡用的是聯合體而不是結構體,好處是能夠節省記憶體),在使用之前要將size ROUND_UP為8的倍數,FREELIST_INDEX可以幫我們找到具體位置。
在文章之後將詳述static void *refill(size_t n);

public:

    /* n must be > 0      */
    static void * allocate(size_t n)
    {
    obj * __VOLATILE * my_free_list;
    obj * __RESTRICT result;
    //如果n大於128bytes,則使用第一級空間配置器,否則使用第二級空間配置器
    if (n > (size_t) __MAX_BYTES) {
        return(malloc_alloc::allocate(n));
    }
    //獲取my_free_list的位置
    my_free_list = free_list + FREELIST_INDEX(n);

    result = *my_free_list;
    if (result == 0) {
        //如果沒有找到可用大小的free-list,就重新填充
        void *r = refill(ROUND_UP(n));
        return r;
    }
    //調整free-lists
    //將result所指向的區塊從free-lists中取出供使用
    *my_free_list = result -> free_list_link;
    return (result);
    };

    /* p may not be 0 */
    static void deallocate(void *p, size_t n)
    {
    obj *q = (obj *)p;
    obj * __VOLATILE * my_free_list;
	//如果n大於128bytes,則使用第一級空間配置器,否則使用第二級空間配置器
    if (n > (size_t) __MAX_BYTES) {
        malloc_alloc::deallocate(p, n);
        return;
    }
    //尋找對應的free-list
    my_free_list = free_list + FREELIST_INDEX(n);
    //調整free-lists,將釋放的區塊回收
    q -> free_list_link = *my_free_list;
    *my_free_list = q;
    }

以上原始碼為第二級空間配置器的空間配置函式allocate()和空間釋放函式deallocate()
這兩個函式的其實並不難理解,首先判斷區塊的大小,如果小於128bytes才使用第二級空間配置器,從free-list中取出(對應於allocate)或放回(對應於deallocate)區塊。
下面將為大家介紹refill,它用來解決free-lists中沒有可用區塊的情況。

template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
    int nobjs = 20;
    //預設申請20個新的節點
    //chunk_alloc函式是從記憶體池裡面取空間出來並返回首地址
    //nobjs傳的是引用,會根據記憶體池滿不滿足要求而改變,之後會詳細介紹記憶體池
    char * chunk = chunk_alloc(n, nobjs);
    obj * volatile * my_free_list;
    obj * result;
    obj * current_obj, * next_obj;
    int i;
    //如果記憶體池中只夠1個節點,那返回直接給使用者
    if (1 == nobjs) return (chunk);
    //尋找對應的free-list
    my_free_list = free_list + FREELIST_INDEX(n);
    /*
     * result指向從記憶體池中申請的空間的首地址(這個節點返回給使用者)
     * my_free_list以及next_obj指向下一個節點(剩下的節點,加入到free_list中)
     */
    result = (obj *)chunk;
    *my_free_list = next_obj = (obj*)(chunk + n);
    //將剩餘的節點依次加入到free_list中去
    for(i = 1; ; i++)
    {
        current_obj = next_obj;
        next_obj = (obj *)((char *)next_obj + n);
        if(nobjs - 1 == i)
        {
          current_obj->free_list_link = 0;
          break;
    }
    else
    {
      current_obj->free_list_link = next_obj;
    }
    return (result);
}

refill的做法是像記憶體池申請20個新的obj,但是我們很容易想到的是記憶體池中的容量也可能會不足,所以nobjs是通過傳引用,如果記憶體池中的容量不夠,那麼nobjs的大小也會隨之而改變。
如果只申請到了一個節點,那麼直接返回給使用者,如果有多,則將第一個節點返回給使用者,後續節點fill到free-lists中去。
你可能會疑惑,這裡一定能夠申請到節點嗎?如果申請失敗了怎麼辦?為什麼這裡沒有相應解決措施?
這是記憶體池所要解決的問題,後面會介紹到!

總結

我們瞭解了SGISTL第二級空間配置器,free-lists在當中扮演著相當重要的角色。