作者****:李先靜
記憶體管理器
在前面學習共享記憶體的時候,我們重新實現了迴圈佇列,兩個實現的不同之處只是在於記憶體分配和釋放上。對比一下 fifo_ring_create的實現:
第一種實現用malloc分配記憶體。
fiforing* fifo_ring_create(size_t length)第二種實現用 shmem_alloc分配記憶體。return thiz;
}
fiforing* fifo_ring_create(size_t length)只是一行**之差,就要把整個佇列重寫一遍,不符合我們前面倡導的dry(don』t repeat yourself)原則。這裡可以看出,記憶體管理器有不同的實現,所以我們引入記憶體管理器這個介面來隔離變化。記憶體管理器的基本功能有:} return thiz;
}
o 分配記憶體
o 釋放記憶體
o 擴充套件/縮小已經分配的記憶體
o 分配清零的記憶體
據此,我們定義allocator介面如下:
struct _allocator;基於malloc系統函式的實現:typedef struct _allocator allocator;
typedef void* (*allocatorcallocfunc)(allocator* thiz, size_t nmemb, size_t size);
typedef void* (*allocatorallocfunc)(allocator* thiz, size_t size);
typedef void (*allocatorfreefunc)(allocator* thiz, void *ptr);
typedef void* (*allocatorreallocfunc)(allocator* thiz, void *ptr, size_t size);
typedef void (*allocatordestroyfunc)(allocator* thiz);
struct _allocator
;
static void* allocator_normal_calloc(allocator* thiz, size_t nmemb, size_t size)這裡函式與標準c函式一一對應,只需要簡單包裝就行了。...allocator* allocator_normal_create(void)
return thiz;
}
對於共享記憶體,通常的做法是先分配一大塊記憶體,然後進行二次分配,此時需要編寫自己的記憶體管理器。記憶體分配器是很奇特的,任何初學者都可以設計自己 的記憶體分配器,但同時任何高手都不敢說自己能設計出最好的記憶體分配器。為什麼記憶體分配器很難寫好呢?因為設計好的記憶體分配器需要考慮很多因素:
o 最大化相容性
實現記憶體分配器時,先要定義出分配器的介面函式。介面函式沒有必要標新立異,而是要遵循現有標準(如posix或者win32),讓使用者可以平滑的過度到新的記憶體分配器上。
o 最大化可移植性
通常情況下,記憶體分配器要向os申請記憶體,然後進行二次分配,這要呼叫os提供的函式才行。os提供的函式則是因平台而異,盡量抽象出平台相關的**,才能保證記憶體分配器的可移植性。
o 浪費最小的空間
記憶體分配器要管理記憶體,必然要使用一些自己的資料結構,這些資料結構本身也要佔記憶體空間。在使用者眼中,這些記憶體空間毫無疑問是浪費掉了,如果浪費在 記憶體分配器本身的記憶體太多,顯然是不可以接受的。記憶體碎片也是浪費空間的罪魁禍首,記憶體碎片是一些不連續的小塊記憶體,它們總量可能很大,但因為其非連續性 而無法使用,這些空間在某種程度上說也是浪費的。
o 最快的速度
記憶體分配/釋放是常用的操作。按著2/8原則,常用函式的效能對系統的整體效能影響最大,所以記憶體分配器的速度越快越好。遺憾的是,最先匹配演算法,最優匹配演算法和最差匹配演算法,誰也不能說誰更快,因為快與慢要依賴於具體的應用環境。
o 最大化可調性(以適應於不同的情況)
記憶體管理演算法設計的難點就在於要適應不同的情況。事實上,如果缺乏應用的上下文,是無法評估記憶體管理演算法的好壞的,因為專用演算法總是在時/空效能上 有更優的表現。為每種情況都寫一套記憶體管理演算法,顯然是不太合適的。我們不需要追求最優演算法,那樣代價太高,能達到次優就行了。設計一套通用記憶體管理算 法,通過一些引數對它進行配置,可以讓它在特定情況也有相當出色的表現,這就是可調性。
o 最大化除錯功能
作為乙個c/c++程式設計師,記憶體錯誤可以說是我們的噩夢,上一次的記憶體錯誤一定還讓你記憶猶新。記憶體分配器提供的除錯功能,強大易用,特別對於嵌入式環境來說,記憶體錯誤檢測工具缺乏的情況下,記憶體分配器提供的除錯功能就更不可少了。
o 最大化適應性
前面說了最大化可調性,以便讓記憶體分配器適用於不同的情況。但是,對於不同情況都要去調整配置,還是有點麻煩。盡量讓記憶體分配器適用於很廣的情況,只有極少情況下才去調整配置,這就是記憶體分配器的適應性。
設計是乙個多目標優化的過程,而且有些目標之間存在著競爭。如何平衡這些競爭是設計的難點之一。在不同的情況下,這些目標的重要性是不一樣的,所以根本不存在乙個最好的記憶體分配器。換句話說,記憶體分配器的實現是變化的,要根據不同的應用環境而變化。
下面我們實現乙個傻瓜型的記憶體管理器,按上面的標準來看,它沒有什麼實際的意義,但它的優點是簡單,僅僅200多行**,就展示了記憶體管理器的基本原理。
o 分配過程
所有空閒的記憶體塊放在乙個雙向鍊錶中,最初只有一塊。分配時使用首次匹配演算法,在第乙個空閒塊上進行分配。
static void* allocator_self_manage_alloc(allocator* thiz, size_t size)這裡對空閒塊的管理不占用有效記憶體空間,它只是強制的把空閒塊轉換成 freenode結構,這要求任何空閒塊的大小都不小於sizeof(freenode)。} return_val_if_fail(iter != null, null);
/*如果找到的空閒塊剛好滿足需求,就從空閒塊鍊錶中移出它*/
if(iter->length < (length + min_size))
if(iter->prev != null)
if(iter->next != null)
}else
if(iter->next != null)
if(priv->free_list == iter)
iter->length = length;
}/*返回可用的記憶體*/
return (char*)iter + sizeof(size_t);
}
o 釋放記憶體
釋放時把記憶體塊放回空閒鍊錶,然後對相鄰居的記憶體塊進行合併。
static void allocator_self_manage_free(allocator* thiz, void *ptr)有了allocator介面,我們也可以通過裝飾模式,為記憶體管理器加上越界/洩露檢查等其它輔助功能。下面的allocator是檢查記憶體越界的裝飾。/*根據記憶體塊位址的大小,把它插入到適當的位置。*/
for(iter = priv->free_list; iter != null; iter = iter->next)
iter->prev = free_iter;
if(priv->free_list == iter)
break;
} if(iter->next == null)
} /*對相鄰居的記憶體進行合併*/
allocator_self_manage_merge(thiz, free_iter);
return;
}
o 實現函式
static void* allocator_checkbo_alloc(allocator* thiz, size_t size)分配記憶體時:多分配3 * sizeof(void*)個位元組,分別用來記錄記憶體塊的長度及前後的magic資料。然後呼叫實際的(即被裝飾的)記憶體分配器分配記憶體,記錄長度並填充magic資料,最後返回記憶體指標給呼叫者。return ptr + 2 * sizeof(void*);
}
static void allocator_checkbo_free(allocator* thiz, void *ptr)釋放記憶體時:取得記憶體塊的長度,然後檢查前後的magic資料是否被修改,如果被修改則認為存在寫越界的錯誤。return;
}
系統程式設計師成長計畫 記憶體管理 三
作者 李先靜 記憶體管理器 在前面學習共享記憶體的時候,我們重新實現了迴圈佇列,兩個實現的不同之處只是在於記憶體分配和釋放上。對比一下 fifo ring create的實現 第一種實現用malloc分配記憶體。fiforing fifo ring create size t length retu...
系統程式設計師成長計畫005
1.這個變成大寫的函式,就不需要用函式指標來給foreach做引數了。因為他沒有什麼其他變種,不像print那樣,既要print int又要print str。函式指標,或者說 函式,別瞎用!2.書裡的寫法 dlist foreach dlist,str toupper,null 看來還是堅持了 函...
系統程式設計師成長計畫 併發 五
文章出處 作者 李先靜 無鎖 lock free 資料結構 多執行緒併發執行時,雖然有共享資料,如果所有執行緒只是讀取共享資料而不修改它,也是不用加鎖的,比如 段就是共享的 資料 每個執行緒都會讀取,但是不用加鎖。排除所有這些情況,多執行緒之間有共享資料,有的執行緒要修改這些共享資料,有的執行緒要讀...