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在當中扮演著相當重要的角色。