如何來管理空閑資源,顯而易見的是組織成一個雙向鏈表,稱作freelist,然後每次從該鏈表上取出一個,釋放的時候再放回去。為了減少片段,最好的策略就是優先分配最近釋放掉的那個,如果能考慮合并的話,類似夥伴系統那樣,就再好不過了,本文給出的是一個通用的可以將資源地圖到一個整型ID的資源分派演算法,完全基於一個數組,不需要記憶體管理,也不需要分配結構體。
組織鏈表的時候,記憶體管理要耗去大量的工作,前向指標和後向指標的修改前提是必須有這些指標。典型的資料結構就是Linux核心的list_head結構體。但是對於靜態類似位元影像的資源並不適合用list_head來組織,因為這類資源本身可以映射到一塊連續的以自然數計數的ID,比較典型的就是磁碟的空閑塊,連續記憶體塊的分配。
既然資源位置是連續的,它就一定能用連續的自然數來表示,那麼所有的資源就可以表示成一個數組了-其映射成自然數的ID的數組,記為ArrayA。
接下來我們需要另外一個數組來表示空閑鏈表,記為ArrayB。
接下來的然後,就是構造ArrayB了...ArrayB的大小等於ArrayA大小加上1,多出來的這個元素可以作為不動點存在,它是不會被分配出去的。ArrayB的元素的大小是ArrayA數組大小佔據位元組數的兩倍,是為了在一個元素中儲存兩個INDEX,比如數組大小可以用8位元據表示,即最多256個元素,那麼ArrayB的元素就應該是2*8這麼大的,舉例說明:
數組大小:short-最多65536個元素
ArrayA的數組定義:int arrayA[MAX];MAX最大65536
ArrayB的數組定義:int arrayB[];int型為兩個short型
結構體形象化表示ArrayB:
#define NUM 8struct freeHL { short high_preindex; //表示前一個ArrayA數組中索引 short low_nextindex; //表示後一個ArrayA數組中索引};struct freeHL freelist[NUM+1];
相當於將ArrayB劈開成了兩半。
然後就可以在連續的數組空間進行連結操作了。實際上這個數組表示的freelist和指標表示的prev,next的freelist是一致的,數組下標也是一個指標,只是在數組表示的freelist中,使用的是相對指標位移而已,表示為下標!
下面就是一個演算法實現問題了,很簡單。在freelist中分配了一個index後,需要修改其前向index的後向index以及後向index的前向index,釋放過程和分配過程相反。代碼如下:
short data[NUM]; //是為ArrayAstruct freeHL freelist[NUM+1]; /是為ArrayB//表示一個下一個分配的index;unsigned int position = 0;//全域的一次性初始化,注意,如果是序列化到了檔案,//則不能再次初始化了,應該從檔案還原序列化來初始化。void list_init(){ int i = 0, j = -1; for (; i < NUM+1; i++) { freelist[i].high_preindex = (i + NUM)%(NUM+1); freelist[i].low_nextindex = (i + 1)%(NUM+1); } position = 0;}//分配介面int nalloc(){ int ret_index = -1, next_index = -1, pre_index = -1; ret_index = position; //儲存當前要分配index的前向index next_index = freelist[position].low_nextindex; //儲存當前要分配index的後向index pre_index = freelist[position].high_preindex; //分配當前index// http://www.bianceng.cn position = next_index; if (ret_index == next_index) { return -1; } //更新當前index前向index的後向index freelist[freelist[ret_index].high_preindex].low_nextindex = next_index; //更新當前index後向index的前向index freelist[freelist[ret_index].low_nextindex].high_preindex = pre_index; return ret_index;}//釋放介面int nfree(unsigned int id){ int pos_pre = -1, pos_next = -1; //儲存下一個要分配的index的前向index,減少片段以及更加容易命中cache pos_pre = freelist[position].high_preindex; //釋放index為id的元素 freelist[pos_pre].low_nextindex = id; freelist[id].high_preindex = pos_pre; freelist[id].low_nextindex = position; //下一個要分配的index為剛釋放的index position = id;}
下面是一個測試:
int main(int argc, char **argv){ list_init(); int i = 0; printf("begin\n"); for (; i < NUM+1; i++) { printf("\n%d \n", nalloc()); } nfree(5); nfree(0); nfree(7); for (i = 0; i < NUM+1; i++) { printf("\n%d \n", nalloc()); } printf("\nend\n");}
這個代碼用在何處呢?前面說過,用在資源可以表示為連續ID的場合,這種場合何在?在《編寫一個UNIX檔案系統》中,我說那個空閑i節點以及空閑塊的分配演算法不好,而上述的方法就可以用,效果比較好,也就是說,將一點點的工作施放於每次分配/釋放的時候,就可以避免在某個時間點做大量的積攢下來的繁重的工作。這個演算法省去了遍曆操作,代價就是佔用了一點連續的地址空間。